diff --git a/include/ace/managers/blit.h b/include/ace/managers/blit.h index e2dd3f03..7d55df07 100644 --- a/include/ace/managers/blit.h +++ b/include/ace/managers/blit.h @@ -73,7 +73,7 @@ void blitManagerDestroy(void); /** * @brief Checks if blitter is idle. * - * Polls 2 times, taing A1000 Agnus bug workaround into account. + * Polls 2 times, taking A1000 Agnus bug workaround into account. * * @return 1 if blitter is idle, otherwise 0. * @@ -84,6 +84,8 @@ UBYTE blitIsIdle(void); /** * @brief Waits until blitter finishes its work. * + * Polls at least 2 times, taking A1000 Agnus bug workaround into account. + * * @todo Investigate if autosetting BLITHOG inside it is beneficial. * * @see blitIsIdle() diff --git a/include/ace/managers/bob.h b/include/ace/managers/bob.h index 06757a46..29b33404 100644 --- a/include/ace/managers/bob.h +++ b/include/ace/managers/bob.h @@ -245,6 +245,15 @@ void bobEnd(void); void bobDiscardUndraw(void); +/** + * @brief Sets the current buffer to given bitmap in case it loses sync. + * Usually used in tandem with bobDiscardUndraw() when bob system was disabled + * for some time. + * + * @param pCurrent Current buffer to use. Must be same as one of passed + * in bobManagerCreate(). + */ +void bobSetCurrentBuffer(tBitMap *pCurrent); #ifdef __cplusplus } diff --git a/include/ace/managers/ptplayer.h b/include/ace/managers/ptplayer.h index 823d1beb..d59b0bd4 100644 --- a/include/ace/managers/ptplayer.h +++ b/include/ace/managers/ptplayer.h @@ -65,6 +65,7 @@ typedef struct tPtplayerSamplePack { } tPtplayerSamplePack; typedef void (*tPtplayerCbSongEnd)(void); +typedef void (*tPtplayerCbE8)(UBYTE ubE8); /** * @brief Install a CIA-B interrupt for calling _mt_music or mt_sfxonly. @@ -346,6 +347,14 @@ tPtplayerSamplePack *ptplayerSampleDataCreateFromFd(tFile *pFileSamples); */ void ptplayerSamplePackDestroy(tPtplayerSamplePack *pSamplePack); +/** + * @brief Sets the function to call on parsing the E8 command. + * + * @param cbOnE8 Function to be called. E8 argument nibble is passed + * as argument. Set to zero if not needed. + */ +void ptplayerSetE8Callback(tPtplayerCbE8 cbOnE8); + #ifdef __cplusplus } #endif diff --git a/include/ace/managers/system.h b/include/ace/managers/system.h index 7f201e72..66f7ccb5 100644 --- a/include/ace/managers/system.h +++ b/include/ace/managers/system.h @@ -21,6 +21,8 @@ typedef void (*tAceIntHandler)( REGARG(volatile tCustom *pCustom, "a0"), REGARG(volatile void *pData, "a1") ); +typedef void (*tKeyInputHandler)(UBYTE ubRawKeyCode); + //-------------------------------------------------------------------- FUNCTIONS /** @@ -54,10 +56,12 @@ UBYTE systemBlitterIsUsed(void); void systemDump(void); +void systemSetKeyInputHandler(tKeyInputHandler cbKeyInputHandler); + void systemSetInt(UBYTE ubIntNumber, tAceIntHandler pHandler, void *pIntData); void systemSetCiaInt( - UBYTE ubCia, UBYTE ubIntBit, tAceIntHandler pHandler, void *pIntData + UBYTE ubCia, UBYTE ubIntBit, tAceIntHandler cbHandler, void *pIntData ); void systemSetCiaCr(UBYTE ubCia, UBYTE isCrB, UBYTE ubCrValue); diff --git a/include/ace/types.h b/include/ace/types.h index e8f89c66..3433b9b4 100644 --- a/include/ace/types.h +++ b/include/ace/types.h @@ -52,24 +52,6 @@ typedef int32_t LONG; #define FN_HOTSPOT #define FN_COLDSPOT #define BITFIELD_STRUCT struct __attribute__((packed)) -#elif defined(__VBCC__) -#if defined(CONFIG_SYSTEM_OS_FRIENDLY) -#define INTERRUPT __amigainterrupt __saveds -#define INTERRUPT_END do {} while(0) -#elif defined(CONFIG_SYSTEM_OS_TAKEOVER) -#define INTERRUPT -#define INTERRUPT_END do {} while(0) -#endif - -#define HWINTERRUPT __interrupt __saveds -#define UNUSED_ARG -#define REGARG(arg, reg) __reg(reg) arg -#define CHIP __chip -#define FAR -#define INTERRUPT_END do {} while(0) -#define FN_HOTSPOT -#define FN_COLDSPOT -#define BITFIELD_STRUCT struct #elif defined(BARTMAN_GCC) #define INTERRUPT #define INTERRUPT_END do {} while(0) diff --git a/include/ace/utils/palette.h b/include/ace/utils/palette.h index d2d7cc85..ad99b62d 100644 --- a/include/ace/utils/palette.h +++ b/include/ace/utils/palette.h @@ -26,6 +26,14 @@ extern "C" { */ void paletteLoadFromPath(const char *szPath, UWORD *pPalette, UBYTE ubMaxLength); +/** + * @brief Saves given palette into .plt file. + * @param pPalette Palette to save. + * @param ubColorCnt Number of colors in palette. + * @param szPath Destination path. + */ +void paletteSave(UWORD *pPalette, UBYTE ubColorCnt, char *szPath); + /** * @brief Loads palette from supplied .plt file to given address. * @param pFile Handle to the palette file. Will be closed on function return. @@ -62,9 +70,23 @@ void paletteDim( * @brief Dims a single input color to given brightness level. * @param uwFullColor Full color used as a base to calculate percentage. * @param ubLevel Brightness level - 15 for no dim, 0 for total blackness. + * + * @see paletteColorMix() */ UWORD paletteColorDim(UWORD uwFullColor, UBYTE ubLevel); +/** + * @brief Interpolates two colors at given level. + * @param uwColorPrimary Primary color in the mix. + * @param uwColorSecondary Secondary color in the mix. + * @param ubLevel Mix ratio - 15 results in primary color, 0 in secondary. + * @return Mixed color between uwColorPrimary and uwColorSecondary. + * + * @note This function is slower than paletteColorDim(). + * @see paletteColorDim() + */ +UWORD paletteColorMix(UWORD uwColorPrimary, UWORD uwColorSecondary, UBYTE ubLevel); + /** * @brief Writes given palette to debug .bmp file. * diff --git a/src/ace/managers/blit.c b/src/ace/managers/blit.c index aed04b1b..e90de005 100644 --- a/src/ace/managers/blit.c +++ b/src/ace/managers/blit.c @@ -101,20 +101,20 @@ UBYTE _blitCheck( #endif // defined(ACE_DEBUG) void blitWait(void) { - while(!blitIsIdle()) continue; + // A1000 Blitter done bug: + // The solution is to read hardware register before testing the bit. + (void)g_pCustom->dmaconr; + while(g_pCustom->dmaconr & DMAF_BLTDONE) continue; } -/** - * Checks if blitter is idle - * Polls 2 times - A1000 Agnus bug workaround - */ UBYTE blitIsIdle(void) { - if(!(g_pCustom->dmaconr & DMAF_BLTDONE)) { - if(!(g_pCustom->dmaconr & DMAF_BLTDONE)) { - return 1; - } + // A1000 Blitter done bug: + // The solution is to read hardware register before testing the bit. + (void)g_pCustom->dmaconr; + if(g_pCustom->dmaconr & DMAF_BLTDONE) { + return 0; } - return 0; + return 1; } UBYTE blitUnsafeCopy( diff --git a/src/ace/managers/bob.c b/src/ace/managers/bob.c index 11b1e122..f46435a6 100644 --- a/src/ace/managers/bob.c +++ b/src/ace/managers/bob.c @@ -208,6 +208,10 @@ void bobInit( tBob *pBob, UWORD uwWidth, UWORD uwHeight, UBYTE isUndrawRequired, UBYTE *pFrameData, UBYTE *pMaskData, UWORD uwX, UWORD uwY ) { + logBlockBegin( + "bobInit(pBob: %p, uwWidth: %hu, uwHeight: %hu, isUndrawRequired: %hhu, pFrameData: %p, pMaskData: %p, uwX: %hu, uwY: %hu)", + pBob, uwWidth, uwHeight, isUndrawRequired, pFrameData, pMaskData, uwX, uwY + ); #if defined(ACE_DEBUG) pBob->_uwOriginalWidth = uwWidth; pBob->_uwOriginalHeight = uwHeight; @@ -238,7 +242,7 @@ void bobInit( } #endif ++s_ubMaxBobCount; - // logWrite("Added bob, now max: %hhu\n", s_ubMaxBobCount); + logBlockEnd("bobInit()"); } void bobSetFrame(tBob *pBob, UBYTE *pFrameData, UBYTE *pMaskData) { @@ -279,6 +283,9 @@ void bobSetHeight(tBob *pBob, UWORD uwHeight) } UBYTE *bobCalcFrameAddress(tBitMap *pBitmap, UWORD uwOffsetY) { + if(uwOffsetY >= pBitmap->Rows) { + logWrite("ERR: bobCalcFrameAddress() OffsY %hu > bitmap height: %hu", uwOffsetY, pBitmap->Rows); + } return &pBitmap->Planes[0][pBitmap->BytesPerRow * uwOffsetY]; } @@ -550,3 +557,9 @@ void bobDiscardUndraw(void) { s_pQueues[0].ubUndrawCount = 0; s_pQueues[1].ubUndrawCount = 0; } + +void bobSetCurrentBuffer(tBitMap *pCurrent) { + if(s_pQueues[!s_ubBufferCurr].pDst == pCurrent) { + s_ubBufferCurr = !s_ubBufferCurr; + } +} diff --git a/src/ace/managers/key.c b/src/ace/managers/key.c index f6b3db5e..a44f91e6 100644 --- a/src/ace/managers/key.c +++ b/src/ace/managers/key.c @@ -8,7 +8,8 @@ #include #include #include // INTB_PORTS -#define KEY_RELEASED_BIT 1 +#define KEY_INTERRUPT_RELEASED_BIT 1 +#define KEY_INPUT_HANDLER_RELEASED_MASK BV(7) #if defined ACE_DEBUG static UBYTE s_bInitCount = 0; @@ -35,13 +36,19 @@ void keySetState(UBYTE ubKeyCode, UBYTE ubKeyState) { keyIntSetState(&g_sKeyManager, ubKeyCode, ubKeyState); } +void onRawKeyInput(UBYTE ubRawKey) { + UBYTE isKeyReleased = ubRawKey & KEY_INPUT_HANDLER_RELEASED_MASK; + ubRawKey &= ~KEY_INPUT_HANDLER_RELEASED_MASK; + keySetState(ubRawKey, isKeyReleased ? KEY_NACTIVE : KEY_ACTIVE); +} + /** * Key interrupt server * Gets key press/release from kbd controller and confirms reception * by handshake */ FN_HOTSPOT -void INTERRUPT keyIntServer( +void INTERRUPT onKeyInterrupt( REGARG(volatile tCustom *pCustom, "a0"), REGARG(volatile void *pData, "a1") ) { @@ -54,7 +61,7 @@ void INTERRUPT keyIntServer( UWORD uwStart = pRayPos->bfPosY; // Get keypress flag and shift key code - UBYTE ubKeyReleased = ubKeyCode & KEY_RELEASED_BIT; + UBYTE ubKeyReleased = ubKeyCode & KEY_INTERRUPT_RELEASED_BIT; ubKeyCode >>= 1; keyIntSetState( pKeyManager, ubKeyCode, ubKeyReleased ? KEY_NACTIVE : KEY_ACTIVE @@ -99,7 +106,8 @@ void keyCreate(void) { logWrite("ERR: Keyboard already initialized\n"); } #endif - systemSetCiaInt(CIA_A, CIAICRB_SERIAL, keyIntServer, &g_sKeyManager); + systemSetCiaInt(CIA_A, CIAICRB_SERIAL, onKeyInterrupt, &g_sKeyManager); + systemSetKeyInputHandler(onRawKeyInput); logBlockEnd("keyCreate()"); } @@ -112,6 +120,7 @@ void keyDestroy(void) { } #endif systemSetCiaInt(CIA_A, CIAICRB_SERIAL, 0, 0); + systemSetKeyInputHandler(0); logBlockEnd("keyDestroy()"); } diff --git a/src/ace/managers/ptplayer.c b/src/ace/managers/ptplayer.c index b6a99c2f..6a936e82 100644 --- a/src/ace/managers/ptplayer.c +++ b/src/ace/managers/ptplayer.c @@ -62,7 +62,7 @@ #define MOD_NOTES_PER_ROW 4 #define MOD_BYTES_PER_NOTE 4 // Length of single pattern. -#define MOD_PATTERN_LENGTH (MOD_ROWS_IN_PATTERN * MOD_NOTES_PER_ROW * MOD_BYTES_PER_NOTE) +#define MOD_PATTERN_BYTE_SIZE (MOD_ROWS_IN_PATTERN * MOD_NOTES_PER_ROW * MOD_BYTES_PER_NOTE) // Size of period table. #define MOD_PERIOD_TABLE_LENGTH 36 @@ -934,6 +934,7 @@ static volatile UBYTE s_isPendingPlay, s_isPendingSetRep, s_isPendingDmaOn; #endif static UBYTE s_isRepeat; static tPtplayerCbSongEnd s_cbSongEnd; +static tPtplayerCbE8 s_cbOnE8; static UBYTE s_isPal; #if defined(PTPLAYER_USE_AUDIO_INT_HANDLERS) @@ -974,7 +975,7 @@ static volatile UBYTE s_isNextTimerBSetRep; static tChannelStatus mt_chan[4]; static UWORD *mt_SampleStarts[PTPLAYER_MOD_SAMPLE_COUNT]; ///< Start address of each sample -static tPtplayerMod *mt_mod; ///< Currently played MOD. +static tPtplayerMod *s_pCurrentMod; ///< Currently played MOD. static ULONG mt_timerval; ///< Base interrupt frequency of CIA-B timer A used to advance the song. Equals 125*50Hz. static const UBYTE * mt_MasterVolTab; static UWORD mt_PatternPos; @@ -999,8 +1000,6 @@ static UBYTE mt_SilCntValid; */ static UWORD mt_dmaon = 0; -static tPtplayerMod *s_pModCurr; - static void clearAudioDone(void) { #if defined(PTPLAYER_USE_AUDIO_INT_HANDLERS) // When channel is idle, original ptplayer loops playback of the first word @@ -1132,7 +1131,7 @@ static void ptSongStep(void) { UBYTE ubNextPos = (mt_SongPos + 1) & 0x7F; // End of song reached? - if(ubNextPos >= mt_mod->ubArrangementLength) { + if(ubNextPos >= s_pCurrentMod->ubArrangementLength) { ubNextPos = 0; if(!s_isRepeat) { ptplayerEnableMusic(0); @@ -1275,7 +1274,7 @@ static void mt_playvoice( --uwSampleIdx; // Read length, volume and repeat from sample info table UWORD *pSampleStart = mt_SampleStarts[uwSampleIdx]; - tPtplayerSampleHeader *pSampleDef = &mt_mod->pSampleHeaders[uwSampleIdx]; + tPtplayerSampleHeader *pSampleDef = &s_pCurrentMod->pSampleHeaders[uwSampleIdx]; UWORD uwSampleLength = pSampleDef->uwLength; if(!uwSampleLength) { // Use the first two bytes from the first sample for empty samples @@ -1571,8 +1570,8 @@ static void mt_music(void) { mt_Counter = 0; if(mt_PattDelTime2 <= 0) { // determine pointer to current pattern line - UBYTE *pPatternData = mt_mod->pPatterns; - UBYTE *pArrangement = mt_mod->pArrangement; + UBYTE *pPatternData = s_pCurrentMod->pPatterns; + UBYTE *pArrangement = s_pCurrentMod->pArrangement; UBYTE ubPatternIdx = pArrangement[mt_SongPos]; UBYTE *pCurrentPattern = &pPatternData[ubPatternIdx * 1024]; tModVoice *pLineVoices = (tModVoice*)&pCurrentPattern[mt_PatternPos]; @@ -1660,7 +1659,7 @@ void ptplayerStop(void) { } resetChannel(&mt_chan[i]); } - s_pModCurr = 0; + s_pCurrentMod = 0; // Free the channels taken by SFX. // Typically they would release themselves but turning off DMA prevents this. @@ -1694,6 +1693,7 @@ static void mt_reset(void) { mt_Speed = 6; mt_Counter = 0; mt_PatternPos = 0; + mt_SongPos = 0; mt_PattDelTime = 0; mt_PattDelTime2 = 0; mt_PBreakPos = 0; @@ -1761,7 +1761,8 @@ void ptplayerDestroy(void) { void ptplayerCreate(UBYTE isPal) { s_isRepeat = 1; s_cbSongEnd = 0; - s_pModCurr = 0; + s_cbOnE8 = 0; + s_pCurrentMod = 0; ptplayerEnableMusic(0); #if defined(PTPLAYER_USE_AUDIO_INT_HANDLERS) for(UBYTE i = sizeof(s_pAudioChannelPendingDisable); i--;) { @@ -1821,17 +1822,16 @@ void ptplayerLoadMod( // Initialize new module. // Reset speed to 6, tempo to 125 and start at given song position. // Master volume is at 64 (maximum). - mt_mod = pMod; + s_pCurrentMod = pMod; logWrite( "Song name: '%s', arrangement length: %hhu, end pos: %hhu\n", - mt_mod->szSongName, mt_mod->ubArrangementLength, mt_mod->ubSongEndPos + s_pCurrentMod->szSongName, s_pCurrentMod->ubArrangementLength, s_pCurrentMod->ubSongEndPos ); // set initial song position if(uwInitialSongPos >= 950) { uwInitialSongPos = 0; } - mt_SongPos = uwInitialSongPos; // sample data location is given? if(pExternalSamples) { @@ -1841,7 +1841,7 @@ void ptplayerLoadMod( ULONG ulOffs = 0; for(UBYTE i = 0; i < PTPLAYER_MOD_SAMPLE_COUNT; ++i) { mt_SampleStarts[i] = &pExternalSamples->pData[ulOffs]; - ulOffs += mt_mod->pSampleHeaders[i].uwLength; + ulOffs += s_pCurrentMod->pSampleHeaders[i].uwLength; }; } else { @@ -1851,8 +1851,8 @@ void ptplayerLoadMod( } for(UBYTE i = 0; i < PTPLAYER_MOD_SAMPLE_COUNT; ++i) { - const tPtplayerSampleHeader *pHeader = &mt_mod->pSampleHeaders[i]; - if(mt_mod->pSampleHeaders[i].uwLength > 0) { + const tPtplayerSampleHeader *pHeader = &s_pCurrentMod->pSampleHeaders[i]; + if(s_pCurrentMod->pSampleHeaders[i].uwLength > 0) { // Make sure each sample starts with two 0-bytes mt_SampleStarts[i][0] = 0; logWrite( @@ -1867,7 +1867,8 @@ void ptplayerLoadMod( } mt_reset(); - s_pModCurr = pMod; + s_pCurrentMod = pMod; + mt_SongPos = uwInitialSongPos; logBlockEnd("ptplayerLoadMod()"); } @@ -2468,6 +2469,9 @@ static void mt_e8( ) { // cmd 0x0E'8X (x = trigger value) mt_E8Trigger = ubArg; + if(s_cbOnE8) { + s_cbOnE8(ubArg); + } } static void ptDoRetrigger( @@ -2740,8 +2744,8 @@ void ptplayerProcess(void) { } const tModVoice *ptplayerGetCurrentVoices(void) { - UBYTE *pPatternData = mt_mod->pPatterns; - UBYTE *pArrangement = mt_mod->pArrangement; + UBYTE *pPatternData = s_pCurrentMod->pPatterns; + UBYTE *pArrangement = s_pCurrentMod->pArrangement; UBYTE ubPatternIdx = pArrangement[mt_SongPos]; UBYTE *pCurrentPattern = &pPatternData[ubPatternIdx * 1024]; tModVoice *pLineVoices = (tModVoice*)&pCurrentPattern[mt_PatternPos]; @@ -2767,7 +2771,7 @@ void ptplayerReserveChannelsForMusic(UBYTE ubChannelCount) { } void ptplayerSetSampleVolume(UBYTE ubSampleIndex, UBYTE ubVolume) { - mt_mod->pSampleHeaders[ubSampleIndex].ubVolume = ubVolume; + s_pCurrentMod->pSampleHeaders[ubSampleIndex].ubVolume = ubVolume; } tPtplayerMod *ptplayerModCreateFromPath(const char *szPath) { @@ -2809,7 +2813,7 @@ tPtplayerMod *ptplayerModCreateFromFd(tFile *pFileMod) { logWrite("Pattern count: %hhu\n", ubPatternCount); // Read pattern data - pMod->ulPatternsSize = (ubPatternCount * MOD_PATTERN_LENGTH); + pMod->ulPatternsSize = (ubPatternCount * MOD_PATTERN_BYTE_SIZE); pMod->pPatterns = memAllocFast(pMod->ulPatternsSize); if(!pMod->pPatterns) { logWrite("ERR: Couldn't allocate memory for pattern data"); @@ -2860,7 +2864,7 @@ tPtplayerMod *ptplayerModCreateFromFd(tFile *pFileMod) { } void ptplayerModDestroy(tPtplayerMod *pMod) { - if(s_pModCurr == pMod) { + if(s_pCurrentMod == pMod) { ptplayerStop(); } memFree(pMod->pPatterns, pMod->ulPatternsSize); @@ -3036,7 +3040,7 @@ void ptplayerSfxPlay( } // Did we already calculate the n_freecnt values for all channels? - if(!mt_SilCntValid && mt_mod) { + if(!mt_SilCntValid && s_pCurrentMod) { // Look at the next 8 pattern steps to find the longest sequence // of silence (no new note or instrument). UBYTE ubSteps = 8; @@ -3054,15 +3058,15 @@ void ptplayerSfxPlay( mt_chan[3].n_freecnt = 0; // get pattern pointer - UBYTE *pPatterns = mt_mod->pPatterns; + UBYTE *pPatterns = s_pCurrentMod->pPatterns; UBYTE ubSongPos = mt_SongPos; UWORD uwPatternPos = mt_PatternPos; UBYTE isEnd = 0; UBYTE *pPatternStart = &pPatterns[ - mt_mod->pArrangement[ubSongPos] * MOD_PATTERN_LENGTH + s_pCurrentMod->pArrangement[ubSongPos] * MOD_PATTERN_BYTE_SIZE ]; - tModVoice *pPatternEnd = (tModVoice*)(pPatternStart + MOD_PATTERN_LENGTH); + tModVoice *pPatternEnd = (tModVoice*)(pPatternStart + MOD_PATTERN_BYTE_SIZE); tModVoice *pPatternPos = (tModVoice*)(pPatternStart + uwPatternPos); do { UBYTE ubFreeChannelCnt = 4; @@ -3088,13 +3092,13 @@ void ptplayerSfxPlay( if(!isEnd && pPatternPos >= pPatternEnd) { uwPatternPos = 0; ubSongPos = (mt_SongPos + 1) & 127; - if(ubSongPos >= mt_mod->ubArrangementLength) { + if(ubSongPos >= s_pCurrentMod->ubArrangementLength) { ubSongPos = 0; } pPatternStart = &pPatterns[ - mt_mod->pArrangement[ubSongPos] * MOD_PATTERN_LENGTH + s_pCurrentMod->pArrangement[ubSongPos] * MOD_PATTERN_BYTE_SIZE ]; - pPatternEnd = (tModVoice*)(pPatternStart + MOD_PATTERN_LENGTH); + pPatternEnd = (tModVoice*)(pPatternStart + MOD_PATTERN_BYTE_SIZE); pPatternPos = (tModVoice*)pPatternStart; } } while(!isEnd); @@ -3265,3 +3269,7 @@ void ptplayerSamplePackDestroy(tPtplayerSamplePack *pSamplePack) { memFree(pSamplePack, sizeof(*pSamplePack)); logBlockEnd("ptplayerSamplePackDestroy()"); } + +void ptplayerSetE8Callback(tPtplayerCbE8 cbOnE8) { + s_cbOnE8 = cbOnE8; +} diff --git a/src/ace/managers/system.c b/src/ace/managers/system.c index 91a7cf16..4585a98a 100644 --- a/src/ace/managers/system.c +++ b/src/ace/managers/system.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -58,6 +60,7 @@ static const UWORD s_pGetVbrCode[] = {0x4e7a, 0x0801, 0x4e73}; // Saved regs static UWORD s_uwOsIntEna; +static UWORD s_uwOsInitialIntEna; ///< Before ACE's OS handlers got installed static UWORD s_uwOsDmaCon; static UWORD s_uwAceDmaCon = 0; static UWORD s_uwOsInitialDma; @@ -87,6 +90,7 @@ static tHwIntVector s_pOsHwInterrupts[SYSTEM_INT_VECTOR_COUNT] = {0}; static tAceInterrupt s_pAceInterrupts[SYSTEM_INT_HANDLER_COUNT] = {{0}}; static tAceInterrupt s_pAceCiaInterrupts[CIA_COUNT][5] = {{{0}}}; static UWORD s_uwAceIntEna = INTF_VERTB | INTF_PORTS | INTF_EXTER; +static tKeyInputHandler s_cbKeyInputHandler; // Manager logic vars static WORD s_wSystemUses; @@ -96,10 +100,19 @@ struct GfxBase *GfxBase = 0; struct View *s_pOsView; static const UWORD s_uwOsMinDma = DMAF_DISK | DMAF_BLITTER; static struct IOAudio s_sIoAudio = {0}; +static struct IOStdReq s_sIoRequestInput = {0}; static struct Library *s_pCiaResource[CIA_COUNT]; static struct Process *s_pProcess; static struct MsgPort *s_pCdtvIOPort; static struct IOStdReq *s_pCdtvIOReq; +static struct Interrupt s_sOsHandlerIo; +static struct Interrupt s_pOsCiaIcrHandlers[CIA_COUNT][5]; +static struct Interrupt *s_pOsOldInterruptHandlers[14]; +static struct Interrupt s_pOsInterruptHandlers[14]; +static UWORD s_uwOsVectorInts = ( + INTF_TBE | INTF_DSKBLK | INTF_SOFTINT | + INTF_AUD0 | INTF_AUD1 | INTF_AUD2 | INTF_AUD3 | INTF_RBF | INTF_DSKSYNC +); #if defined(BARTMAN_GCC) struct DosLibrary *DOSBase = 0; @@ -304,6 +317,74 @@ void HWINTERRUPT int7Handler(void) { logPopInt(); } +//------------------------------------------------------------------ OS HANDLERS + +static ULONG aceInputHandler(void) { + register volatile ULONG regOldEventChain __asm("a0"); + // register volatile ULONG regData __asm("a1"); + + if(s_cbKeyInputHandler) { + struct InputEvent *pEvent = (struct InputEvent *)regOldEventChain; + while(pEvent) { + if(pEvent->ie_Class == IECLASS_RAWKEY) { + // logWrite("OS key event: %hu\n", pEvent->ie_Code); + s_cbKeyInputHandler(pEvent->ie_Code); + } + // TODO: handle IECLASS_DISKREMOVED, IECLASS_DISKINSERTED ? + + pEvent = pEvent->ie_NextEvent; + } + } + + // Don't propagate any input events to other stuff - ensure full takeover by clearing d0 + return 0; +} + +ULONG ciaIcrHandler(void) { + register volatile ULONG regData __asm("a1"); + + tAceInterrupt *pAceInt = (tAceInterrupt *)regData; + if(pAceInt->pHandler) { + pAceInt->pHandler(g_pCustom, pAceInt->pData); + } + + // set the Z flag + return 0; +} + +void osIntVector(void) { + register volatile ULONG regData __asm("a1"); + + UBYTE ubIntBit = regData; + if(s_pAceInterrupts[ubIntBit].pHandler) { + s_pAceInterrupts[ubIntBit].pHandler( + g_pCustom, s_pAceInterrupts[ubIntBit].pData + ); + } + + UWORD uwIntMask = BV(ubIntBit); + g_pCustom->intreq = uwIntMask; + g_pCustom->intreq = uwIntMask; +} + +ULONG osIntServer(void) { + register volatile ULONG regData __asm("a1"); + + UBYTE ubIntBit = regData; + if(s_pAceInterrupts[ubIntBit].pHandler) { + s_pAceInterrupts[ubIntBit].pHandler( + g_pCustom, s_pAceInterrupts[ubIntBit].pData + ); + } + + UWORD uwIntMask = BV(ubIntBit); + g_pCustom->intreq = uwIntMask; + g_pCustom->intreq = uwIntMask; + + // End chain for non-VERTB ints + return ubIntBit == INTB_VERTB ? 0 : 1; +} + //-------------------------------------------------------------------- FUNCTIONS // Messageport creation for KS1.3 @@ -363,39 +444,37 @@ static void ioRequestDestroy(struct IORequest *pIoReq) { FreeMem(pIoReq, pIoReq->io_Message.mn_Length); } -static UBYTE audioChannelAlloc(struct IOAudio *pIoAudio) { - struct MsgPort *pMsgPort = 0; - - pMsgPort = msgPortCreate("audio alloc", ADALLOC_MAXPREC); +static UBYTE audioChannelAlloc(void) { + struct MsgPort *pMsgPort = msgPortCreate("audio alloc", ADALLOC_MAXPREC); if(!pMsgPort) { logWrite("ERR: Couldn't open message port for audio alloc\n"); goto fail; } - memset(pIoAudio, 0, sizeof(*pIoAudio)); - ioRequestInitialize(&pIoAudio->ioa_Request, pMsgPort, sizeof(*pIoAudio)); + memset(&s_sIoAudio, 0, sizeof(s_sIoAudio)); + ioRequestInitialize(&s_sIoAudio.ioa_Request, pMsgPort, sizeof(s_sIoAudio)); UBYTE isError = OpenDevice( - (CONST_STRPTR)"audio.device", 0, (struct IORequest *)pIoAudio, 0 + (CONST_STRPTR)"audio.device", 0, (struct IORequest *)&s_sIoAudio, 0 ); if(isError) { logWrite( "ERR: Couldn't alloc Audio channels, code: %d\n", - pIoAudio->ioa_Request.io_Error + s_sIoAudio.ioa_Request.io_Error ); goto fail; } UBYTE ubChannelMask = 0b1111; // Allocate all 4 channels. - pIoAudio->ioa_Data = &ubChannelMask; - pIoAudio->ioa_Length = sizeof(ubChannelMask); - pIoAudio->ioa_Request.io_Command = ADCMD_ALLOCATE; - pIoAudio->ioa_Request.io_Flags = ADIOF_NOWAIT; - isError = DoIO((struct IORequest *)pIoAudio); + s_sIoAudio.ioa_Data = &ubChannelMask; + s_sIoAudio.ioa_Length = sizeof(ubChannelMask); + s_sIoAudio.ioa_Request.io_Command = ADCMD_ALLOCATE; + s_sIoAudio.ioa_Request.io_Flags = ADIOF_NOWAIT; + isError = DoIO((struct IORequest *)&s_sIoAudio); if(isError) { logWrite( - "ERR: io audio request fail, code: %d\n", pIoAudio->ioa_Request.io_Error + "ERR: io audio request fail, code: %d\n", s_sIoAudio.ioa_Request.io_Error ); goto fail; } @@ -408,20 +487,20 @@ static UBYTE audioChannelAlloc(struct IOAudio *pIoAudio) { return 0; } -void audioChannelFree(struct IOAudio *pIoAudio) { - struct MsgPort *pMsgPort = pIoAudio->ioa_Request.io_Message.mn_ReplyPort; +static void audioChannelFree(void) { + struct MsgPort *pMsgPort = s_sIoAudio.ioa_Request.io_Message.mn_ReplyPort; - pIoAudio->ioa_Request.io_Command = ADCMD_FINISH; - pIoAudio->ioa_Request.io_Flags = 0; - pIoAudio->ioa_Request.io_Unit = (APTR)1; - UBYTE isError = DoIO((struct IORequest *)pIoAudio); + s_sIoAudio.ioa_Request.io_Command = ADCMD_FINISH; + s_sIoAudio.ioa_Request.io_Flags = 0; + s_sIoAudio.ioa_Request.io_Unit = (APTR)1; + UBYTE isError = DoIO((struct IORequest *)&s_sIoAudio); if(isError) { logWrite( - "ERR: io audio request fail, code: %d\n", pIoAudio->ioa_Request.io_Error + "ERR: io audio request fail, code: %d\n", s_sIoAudio.ioa_Request.io_Error ); } - CloseDevice((struct IORequest *)pIoAudio); + CloseDevice((struct IORequest *)&s_sIoAudio); msgPortDelete(pMsgPort); } @@ -456,6 +535,208 @@ static void cdtvPortFree(void) { } } +static UBYTE inputHandlerAdd(void) { + struct MsgPort *pMsgPort = msgPortCreate("input handler register", ADALLOC_MAXPREC); + if(!pMsgPort) { + logWrite("ERR: Couldn't open message port for audio alloc\n"); + goto fail; + } + + // We don't have CreateIORequest on ks1.3, so just zero stuff + memset(&s_sIoRequestInput, 0, sizeof(s_sIoRequestInput)); + UBYTE isError = OpenDevice( + (CONST_STRPTR)"input.device", 0, (struct IORequest *)&s_sIoRequestInput, 0 + ); + if(isError) { + logWrite( + "ERR: Couldn't open input device, code: %d\n", + s_sIoRequestInput.io_Error + ); + goto fail; + } + + memset(&s_sOsHandlerIo, 0, sizeof(s_sOsHandlerIo)); + s_sOsHandlerIo.is_Code = (void(*)(void))aceInputHandler; + s_sOsHandlerIo.is_Data = 0; + s_sOsHandlerIo.is_Node.ln_Pri = 127; + s_sOsHandlerIo.is_Node.ln_Name = "ACE Input handler"; + s_sIoRequestInput.io_Data = (APTR)&s_sOsHandlerIo; + s_sIoRequestInput.io_Command = IND_ADDHANDLER; + s_sIoRequestInput.io_Message.mn_ReplyPort = pMsgPort; + isError = DoIO((struct IORequest *)&s_sIoRequestInput); + + if(isError) { + logWrite( + "ERR: io audio request fail, code: %d\n", s_sIoRequestInput.io_Error + ); + goto fail; + } + return 1; + +fail: + if(pMsgPort) { + msgPortDelete(pMsgPort); + } + return 0; +} + +static void inputHandlerRemove(void) { + // http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node04E2.html + + struct MsgPort *pMsgPort = s_sIoRequestInput.io_Message.mn_ReplyPort; + + s_sIoRequestInput.io_Command = IND_REMHANDLER; + s_sIoRequestInput.io_Data=(APTR)&s_sOsHandlerIo; + UBYTE isError = DoIO((struct IORequest *)&s_sIoRequestInput); + if(isError) { + logWrite( + "ERR: io audio request fail, code: %d\n", s_sIoRequestInput.io_Error + ); + } + + // NOTE: IND_REMHANDLER is not immediate + + CloseDevice((struct IORequest *)&s_sIoRequestInput); + msgPortDelete(pMsgPort); +} + +static void interruptHandlerAdd(UBYTE ubIntBit) { + struct Interrupt *pInterrupt = &s_pOsInterruptHandlers[ubIntBit]; + memset(pInterrupt, 0, sizeof(*pInterrupt)); + pInterrupt->is_Data = (void*)(ULONG)ubIntBit; + pInterrupt->is_Node.ln_Name = "ACE interrupt handler"; + pInterrupt->is_Node.ln_Type = NT_INTERRUPT; + if(BV(ubIntBit) & s_uwOsVectorInts) { + pInterrupt->is_Code = osIntVector; + + // Disable interrupt and swap the int vector + g_pCustom->intena = BV(ubIntBit); + s_pOsOldInterruptHandlers[ubIntBit] = SetIntVector(ubIntBit, pInterrupt); + } + else { + // VERTB servers should always return Z set and can't be sole handler + // NOTE: VERTB server with priority of 10 or greater must set a0 to 0xDFF000 + pInterrupt->is_Code = (void(*)(void))osIntServer; + pInterrupt->is_Node.ln_Pri = (ubIntBit == INTB_VERTB) ? 9 : 127; + // Auto-enables intena bit if first server is adder + AddIntServer(ubIntBit, pInterrupt); + } +} + +static void interruptHandlerRemove(UBYTE ubIntBit) { + if(BV(ubIntBit) & s_uwOsVectorInts) { + // Disable interrupt in case handler is set to null + g_pCustom->intena = BV(ubIntBit); + SetIntVector(ubIntBit, s_pOsOldInterruptHandlers[ubIntBit]); + + // A 3rd party handler could be installed but be inactive + if(s_uwOsInitialIntEna & BV(ubIntBit)) { + g_pCustom->intena = INTF_SETCLR | BV(ubIntBit); + } + } + else { + // Auto-disables intena bit if sole server is removed + RemIntServer(ubIntBit, &s_pOsInterruptHandlers[ubIntBit]); + } +} + +static void systemOsDisable(void) { + // Disable interrupts (this is the actual "kill system/OS" part) + g_pCustom->intena = 0x7FFF; + g_pCustom->intreq = 0x7FFF; + + // Query CIA ICR bits set by OS for later CIA takeover restore + s_pOsCiaIcr[CIA_A] = AbleICR(s_pCiaResource[CIA_A], 0); + s_pOsCiaIcr[CIA_B] = AbleICR(s_pCiaResource[CIA_B], 0); + + // Disable CIA interrupts + g_pCia[CIA_A]->icr = 0x7F; + g_pCia[CIA_B]->icr = 0x7F; + + // Save CRA bits + s_pOsCiaCra[CIA_A] = g_pCia[CIA_A]->cra; + s_pOsCiaCrb[CIA_A] = g_pCia[CIA_A]->crb; + s_pOsCiaCra[CIA_B] = g_pCia[CIA_B]->cra; + s_pOsCiaCrb[CIA_B] = g_pCia[CIA_B]->crb; + + // Disable timers and trigger reload of value to read preset val + g_pCia[CIA_A]->cra = CIACRA_LOAD; // CIACRA_START=0, timer is stopped + g_pCia[CIA_A]->crb = CIACRB_LOAD; + g_pCia[CIA_B]->cra = CIACRA_LOAD; // CIACRA_START=0, timer is stopped + g_pCia[CIA_B]->crb = CIACRB_LOAD; + + // Save OS CIA timer values + s_pOsCiaTimerA[CIA_A] = ciaGetTimerA(g_pCia[CIA_A]); + s_pOsCiaTimerB[CIA_A] = ciaGetTimerB(g_pCia[CIA_A]); + s_pOsCiaTimerA[CIA_B] = ciaGetTimerA(g_pCia[CIA_B]); + s_pOsCiaTimerB[CIA_B] = ciaGetTimerB(g_pCia[CIA_B]); + + // set ACE CIA timers + ciaSetTimerA(g_pCia[CIA_A], s_pAceCiaTimerA[CIA_A]); + ciaSetTimerB(g_pCia[CIA_A], s_pAceCiaTimerB[CIA_A]); + ciaSetTimerA(g_pCia[CIA_B], s_pAceCiaTimerA[CIA_B]); + ciaSetTimerB(g_pCia[CIA_B], s_pAceCiaTimerB[CIA_B]); + + // Enable ACE CIA interrupts + g_pCia[CIA_A]->icr = ( + CIAICRF_SETCLR | CIAICRF_SERIAL | CIAICRF_TIMER_A | CIAICRF_TIMER_B + ); + g_pCia[CIA_B]->icr = ( + CIAICRF_SETCLR | CIAICRF_SERIAL | CIAICRF_TIMER_A | CIAICRF_TIMER_B + ); + + // Restore ACE CIA CRA/CRB state + g_pCia[CIA_A]->cra = CIACRA_LOAD | s_pAceCiaCra[CIA_A]; + g_pCia[CIA_A]->crb = CIACRA_LOAD | s_pAceCiaCrb[CIA_A]; + g_pCia[CIA_B]->cra = CIACRA_LOAD | s_pAceCiaCra[CIA_B]; + g_pCia[CIA_B]->crb = CIACRA_LOAD | s_pAceCiaCrb[CIA_B]; + + // Game's bitplanes & copperlists are still used so don't disable them + // Wait for vbl before disabling sprite DMA + while (!(g_pCustom->intreqr & INTF_VERTB)) continue; + g_pCustom->dmacon = s_uwOsMinDma; + + // Save OS interrupt vectors and enable ACE's + g_pCustom->intreq = 0x7FFF; + for(UWORD i = 0; i < SYSTEM_INT_VECTOR_COUNT; ++i) { + s_pOsHwInterrupts[i] = s_pHwVectors[SYSTEM_INT_VECTOR_FIRST + i]; + s_pHwVectors[SYSTEM_INT_VECTOR_FIRST + i] = s_pAceHwInterrupts[i]; + } + + // Enable needed DMA (and interrupt) channels + g_pCustom->dmacon = DMAF_SETCLR | DMAF_MASTER | s_uwAceDmaCon; + // Everything that's supported by ACE to simplify things for now, + // but not audio channels since ptplayer relies on polling them, and I'm not + // currently being able to debug audio interrupt handler variant. + g_pCustom->intena = INTF_SETCLR | INTF_INTEN | s_uwAceIntEna; + + // HACK: OS resets potgo in vblank int, preventing scanning fire2 on most joysticks. + // TODO: make sure this doesn't break anything, e.g. mice + // TODO: add setting this value to OS-friendly vblank handler with proper priority + // https://eab.abime.net/showthread.php?t=74981 + g_pCustom->potgo = 0xFF00; + +} + +void ciaIcrHandlerAdd(UBYTE ubCia, UBYTE ubIcrBit) { + memset(&s_pOsCiaIcrHandlers[ubCia][ubIcrBit], 0, sizeof(s_pOsCiaIcrHandlers[ubCia][ubIcrBit])); + s_pOsCiaIcrHandlers[ubCia][ubIcrBit].is_Code = (void(*)(void))ciaIcrHandler; + s_pOsCiaIcrHandlers[ubCia][ubIcrBit].is_Data = (void*)&s_pAceCiaInterrupts[ubCia][ubIcrBit]; + s_pOsCiaIcrHandlers[ubCia][ubIcrBit].is_Node.ln_Name = "ACE CIA ICR handler"; + s_pOsCiaIcrHandlers[ubCia][ubIcrBit].is_Node.ln_Pri = 127; + s_pOsCiaIcrHandlers[ubCia][ubIcrBit].is_Node.ln_Type = NT_INTERRUPT; + if(AddICRVector(s_pCiaResource[ubCia], ubIcrBit, &s_pOsCiaIcrHandlers[ubCia][ubIcrBit])) { + logWrite("CIA %c ICR %d add failed", CIA_A ? 'A' : 'B', ubIcrBit); + } + + // Disable interrupt since AddICRVector auto-enables it + // AbleICR(s_pCiaResource[CIA_B], CIAICRF_TIMER_A); +} + +void ciaIcrHandlerRemove(UBYTE ubCia, UBYTE ubIcrBit) { + RemICRVector(s_pCiaResource[ubCia], ubIcrBit, &s_pOsCiaIcrHandlers[ubCia][ubIcrBit]); +} + /** * @brief Flushes FDD disk activity. * Originally written by Asman in asm, known as osflush @@ -548,7 +829,20 @@ void systemCreate(void) { // Reserve all audio channels - apparantly this allows for int flag polling // From https://gist.github.com/johngirvin/8fb0c4bb83b7c80427e2f439bb074e95 - audioChannelAlloc(&s_sIoAudio); + audioChannelAlloc(); + + // prepare disabling/enabling CDTV device as per Alpine9000 code + // from https://eab.abime.net/showthread.php?t=89836 + cdtvPortAlloc(); + + inputHandlerAdd(); + + s_uwOsInitialIntEna = g_pCustom->intenar; + interruptHandlerAdd(INTB_AUD0); + interruptHandlerAdd(INTB_AUD1); + interruptHandlerAdd(INTB_AUD2); + interruptHandlerAdd(INTB_AUD3); + interruptHandlerAdd(INTB_VERTB); // prepare disabling/enabling CDTV device as per Alpine9000 code // from https://eab.abime.net/showthread.php?t=89836 @@ -557,6 +851,9 @@ void systemCreate(void) { s_pCiaResource[CIA_A] = OpenResource((CONST_STRPTR)CIAANAME); s_pCiaResource[CIA_B] = OpenResource((CONST_STRPTR)CIABNAME); + ciaIcrHandlerAdd(CIA_B, CIAICRB_TIMER_A); + ciaIcrHandlerAdd(CIA_B, CIAICRB_TIMER_B); + // Disable as much of OS stuff as possible so that it won't trash stuff when // re-enabled periodically. // Save the system copperlists and flush the view @@ -590,12 +887,11 @@ void systemCreate(void) { while (!(g_pCustom->intreqr & INTF_VERTB)) continue; g_pCustom->dmacon = 0x07FF; - // Unuse system so that it gets backed up once and then re-enable - // as little as needed - s_wSystemUses = 1; + // Disable system so that it gets backed up once, skip saving intena from systemUnuse() + s_wSystemUses = 0; s_wSystemBlitterUses = 1; - systemUnuse(); - systemUse(); + systemOsDisable(); + systemUse(); // Re-enable as little as needed systemGetBlitterFromOs(); } @@ -622,12 +918,20 @@ void systemDestroy(void) { systemReleaseBlitterToOs(); g_pCustom->dmacon = DMAF_SETCLR | DMAF_MASTER | s_uwOsInitialDma; - // Free audio channels - audioChannelFree(&s_sIoAudio); + ciaIcrHandlerRemove(CIA_B, CIAICRB_TIMER_A); + ciaIcrHandlerRemove(CIA_B, CIAICRB_TIMER_B); - // Free CDTV request port + audioChannelFree(); cdtvPortFree(); + inputHandlerRemove(); + + interruptHandlerRemove(INTB_AUD0); + interruptHandlerRemove(INTB_AUD1); + interruptHandlerRemove(INTB_AUD2); + interruptHandlerRemove(INTB_AUD3); + interruptHandlerRemove(INTB_VERTB); + // restore old view WaitTOF(); LoadView(s_pOsView); @@ -667,80 +971,10 @@ void systemUnuse(void) { DoIO((struct IORequest *)s_pCdtvIOReq); } - // Disable interrupts (this is the actual "kill system/OS" part) - g_pCustom->intena = 0x7FFF; - g_pCustom->intreq = 0x7FFF; - - // Query CIA ICR bits set by OS for later CIA takeover restore - s_pOsCiaIcr[CIA_A] = AbleICR(s_pCiaResource[CIA_A], 0); - s_pOsCiaIcr[CIA_B] = AbleICR(s_pCiaResource[CIA_B], 0); - - // Disable CIA interrupts - g_pCia[CIA_A]->icr = 0x7F; - g_pCia[CIA_B]->icr = 0x7F; - - // Save CRA bits - s_pOsCiaCra[CIA_A] = g_pCia[CIA_A]->cra; - s_pOsCiaCrb[CIA_A] = g_pCia[CIA_A]->crb; - s_pOsCiaCra[CIA_B] = g_pCia[CIA_B]->cra; - s_pOsCiaCrb[CIA_B] = g_pCia[CIA_B]->crb; + // save the state of the hardware registers (INTENA) + s_uwOsIntEna = g_pCustom->intenar; - // Disable timers and trigger reload of value to read preset val - g_pCia[CIA_A]->cra = CIACRA_LOAD; // CIACRA_START=0, timer is stopped - g_pCia[CIA_A]->crb = CIACRB_LOAD; - g_pCia[CIA_B]->cra = CIACRA_LOAD; // CIACRA_START=0, timer is stopped - g_pCia[CIA_B]->crb = CIACRB_LOAD; - - // Save OS CIA timer values - s_pOsCiaTimerA[CIA_A] = ciaGetTimerA(g_pCia[CIA_A]); - s_pOsCiaTimerB[CIA_A] = ciaGetTimerB(g_pCia[CIA_A]); - s_pOsCiaTimerA[CIA_B] = ciaGetTimerA(g_pCia[CIA_B]); - s_pOsCiaTimerB[CIA_B] = ciaGetTimerB(g_pCia[CIA_B]); - - // set ACE CIA timers - ciaSetTimerA(g_pCia[CIA_A], s_pAceCiaTimerA[CIA_A]); - ciaSetTimerB(g_pCia[CIA_A], s_pAceCiaTimerB[CIA_A]); - ciaSetTimerA(g_pCia[CIA_B], s_pAceCiaTimerA[CIA_B]); - ciaSetTimerB(g_pCia[CIA_B], s_pAceCiaTimerB[CIA_B]); - - // Enable ACE CIA interrupts - g_pCia[CIA_A]->icr = ( - CIAICRF_SETCLR | CIAICRF_SERIAL | CIAICRF_TIMER_A | CIAICRF_TIMER_B - ); - g_pCia[CIA_B]->icr = ( - CIAICRF_SETCLR | CIAICRF_SERIAL | CIAICRF_TIMER_A | CIAICRF_TIMER_B - ); - - // Restore ACE CIA CRA/CRB state - g_pCia[CIA_A]->cra = CIACRA_LOAD | s_pAceCiaCra[CIA_A]; - g_pCia[CIA_A]->crb = CIACRA_LOAD | s_pAceCiaCrb[CIA_A]; - g_pCia[CIA_B]->cra = CIACRA_LOAD | s_pAceCiaCra[CIA_B]; - g_pCia[CIA_B]->crb = CIACRA_LOAD | s_pAceCiaCrb[CIA_B]; - - // Game's bitplanes & copperlists are still used so don't disable them - // Wait for vbl before disabling sprite DMA - while (!(g_pCustom->intreqr & INTF_VERTB)) continue; - g_pCustom->dmacon = s_uwOsMinDma; - - // Save OS interrupt vectors and enable ACE's - g_pCustom->intreq = 0x7FFF; - for(UWORD i = 0; i < SYSTEM_INT_VECTOR_COUNT; ++i) { - s_pOsHwInterrupts[i] = s_pHwVectors[SYSTEM_INT_VECTOR_FIRST + i]; - s_pHwVectors[SYSTEM_INT_VECTOR_FIRST + i] = s_pAceHwInterrupts[i]; - } - - // Enable needed DMA (and interrupt) channels - g_pCustom->dmacon = DMAF_SETCLR | DMAF_MASTER | s_uwAceDmaCon; - // Everything that's supported by ACE to simplify things for now, - // but not audio channels since ptplayer relies on polling them, and I'm not - // currently being able to debug audio interrupt handler variant. - g_pCustom->intena = INTF_SETCLR | INTF_INTEN | s_uwAceIntEna; - - // HACK: OS resets potgo in vblank int, preventing scanning fire2 on most joysticks. - // TODO: make sure this doesn't break anything, e.g. mice - // TODO: add setting this value to OS-friendly vblank handler with proper priority - // https://eab.abime.net/showthread.php?t=74981 - g_pCustom->potgo = 0xFF00; + systemOsDisable(); } #if defined(ACE_DEBUG) if(s_wSystemUses < 0) { @@ -821,13 +1055,15 @@ UBYTE systemIsUsed(void) { } void systemGetBlitterFromOs(void) { - --s_wSystemBlitterUses; - if(!s_wSystemBlitterUses) { + if(s_wSystemBlitterUses == 1) { // Make OS finish its pending operations before it loses blitter! systemFlushIo(); OwnBlitter(); WaitBlit(); } + // This must be decremented after OwnBlitter() so that systemBlitterIsUsed() + // checked in interrupt won't return 0 during OS blitter op still in progress. + --s_wSystemBlitterUses; #if defined(ACE_DEBUG) if(s_wSystemBlitterUses < 0) { @@ -839,8 +1075,8 @@ void systemGetBlitterFromOs(void) { void systemReleaseBlitterToOs(void) { if (!s_wSystemBlitterUses){ - DisownBlitter(); WaitBlit(); + DisownBlitter(); } ++s_wSystemBlitterUses; } @@ -849,13 +1085,15 @@ UBYTE systemBlitterIsUsed(void) { return s_wSystemBlitterUses > 0; } +void systemSetKeyInputHandler(tKeyInputHandler cbKeyInputHandler) { + s_cbKeyInputHandler = cbKeyInputHandler; +} + void systemSetInt( UBYTE ubIntNumber, tAceIntHandler pHandler, void *pIntData ) { // Disable interrupt during data swap to not get stuck inside ACE's ISR - if(!s_wSystemUses) { - g_pCustom->intena = BV(ubIntNumber); - } + g_pCustom->intena = BV(ubIntNumber); // Disable handler or re-enable it if 0 was passed if(pHandler == 0) { @@ -866,9 +1104,7 @@ void systemSetInt( s_pAceInterrupts[ubIntNumber].pHandler = pHandler; s_pAceInterrupts[ubIntNumber].pData = pIntData; s_uwAceIntEna |= BV(ubIntNumber); - if(!s_wSystemUses) { - g_pCustom->intena = INTF_SETCLR | BV(ubIntNumber); - } + g_pCustom->intena = INTF_SETCLR | BV(ubIntNumber); } } @@ -886,15 +1122,13 @@ void systemSetCiaInt( void systemSetCiaCr(UBYTE ubCia, UBYTE isCrB, UBYTE ubCrValue) { if(isCrB) { s_pAceCiaCrb[ubCia] = ubCrValue; - if(!s_wSystemUses) { - g_pCia[ubCia]->crb = ubCrValue; - } + s_pOsCiaCrb[ubCia] = ubCrValue; + g_pCia[ubCia]->crb = ubCrValue; } else { s_pAceCiaCra[ubCia] = ubCrValue; - if(!s_wSystemUses) { - g_pCia[ubCia]->cra = ubCrValue; - } + s_pOsCiaCra[ubCia] = ubCrValue; + g_pCia[ubCia]->cra = ubCrValue; } } diff --git a/src/ace/managers/viewport/tilebuffer.c b/src/ace/managers/viewport/tilebuffer.c index 1b3336b3..cff07be5 100644 --- a/src/ace/managers/viewport/tilebuffer.c +++ b/src/ace/managers/viewport/tilebuffer.c @@ -24,19 +24,31 @@ static UBYTE shiftFromPowerOfTwo(UWORD uwPot) { #define BLIT_WORDS_NON_INTERLEAVED_BIT (0b1 << 5) // tileSize is UBYTE, top bit of width is definitely free -static void tileBufferResetRedrawState(tRedrawState *pState) { +static void tileBufferResetRedrawState( + tRedrawState *pState, WORD wStartX, WORD wEndX, WORD wStartY, WORD wEndY +) { #if defined(ACE_SCROLLBUFFER_ENABLE_SCROLL_X) memset(&pState->sMarginL, 0, sizeof(tMarginState)); memset(&pState->sMarginR, 0, sizeof(tMarginState)); + pState->sMarginL.wTilePos = wStartX; + pState->sMarginR.wTilePos = wEndX; pState->pMarginX = &pState->sMarginR; pState->pMarginOppositeX = &pState->sMarginL; +#else + (void)wStartX; + (void)wEndX; #endif #if defined(ACE_SCROLLBUFFER_ENABLE_SCROLL_Y) memset(&pState->sMarginU, 0, sizeof(tMarginState)); memset(&pState->sMarginD, 0, sizeof(tMarginState)); + pState->sMarginU.wTilePos = wStartY; + pState->sMarginD.wTilePos = wEndY; pState->pMarginY = &pState->sMarginD; pState->pMarginOppositeY = &pState->sMarginU; +#else + (void)wStartY; + (void)wEndY; #endif pState->ubPendingCount = 0; @@ -295,9 +307,10 @@ void tileBufferReset( pManager->ubMarginXLength, pManager->ubMarginYLength ); - // Reset margin redraw structs - tileBufferResetRedrawState(&pManager->pRedrawStates[0]); - tileBufferResetRedrawState(&pManager->pRedrawStates[1]); + // Reset margin redraw structs - margin positions will be set correctly + // by tileBufferRedrawAll() + tileBufferResetRedrawState(&pManager->pRedrawStates[0], 0, 0, 0, 0); + tileBufferResetRedrawState(&pManager->pRedrawStates[1], 0, 0, 0, 0); logBlockEnd("tileBufferReset()"); } @@ -400,16 +413,14 @@ static inline void tileBufferContinueTileDraw( FN_HOTSPOT void tileBufferProcess(tTileBufferManager *pManager) { #if defined(ACE_SCROLLBUFFER_ENABLE_SCROLL_X) || defined(ACE_SCROLLBUFFER_ENABLE_SCROLL_Y) - WORD wMarginXPos, wMarginYPos; - UWORD uwTileOffsX, uwTileOffsY; tRedrawState *pState = &pManager->pRedrawStates[pManager->ubStateIdx]; - UBYTE ubTileSize = pManager->ubTileSize; UBYTE ubTileShift = pManager->ubTileShift; #endif #if defined(ACE_SCROLLBUFFER_ENABLE_SCROLL_X) // X movement + WORD wMarginXPos; WORD wDeltaX = cameraGetDeltaX(pManager->pCamera); if (wDeltaX) { // determine movement direction - right or left @@ -429,10 +440,10 @@ void tileBufferProcess(tTileBufferManager *pManager) { if (wMarginXPos != pState->pMarginX->wTilePos) { // Not finished redrawing all column tiles? if(pState->pMarginX->wTileCurr < pState->pMarginX->wTileEnd) { - uwTileOffsY = SCROLLBUFFER_HEIGHT_MODULO( + UWORD uwTileOffsY = SCROLLBUFFER_HEIGHT_MODULO( pState->pMarginX->wTileCurr << ubTileShift, pManager->uwMarginedHeight ); - uwTileOffsX = (pState->pMarginX->wTilePos << ubTileShift); + UWORD uwTileOffsX = (pState->pMarginX->wTilePos << ubTileShift); // Redraw remaining tiles UWORD uwBltsize = tileBufferSetupTileDraw(pManager); UWORD uwTileCurr = pState->pMarginX->wTileCurr; @@ -523,6 +534,7 @@ void tileBufferProcess(tTileBufferManager *pManager) { #if defined(ACE_SCROLLBUFFER_ENABLE_SCROLL_Y) // Y movement + WORD wMarginYPos; WORD wDeltaY = cameraGetDeltaY(pManager->pCamera); if (wDeltaY) { // determine redraw row - down or up @@ -542,10 +554,10 @@ void tileBufferProcess(tTileBufferManager *pManager) { if (wMarginYPos != pState->pMarginY->wTilePos) { // Not finished redrawing all row tiles? if(pState->pMarginY->wTileCurr < pState->pMarginY->wTileEnd) { - uwTileOffsY = SCROLLBUFFER_HEIGHT_MODULO( + UWORD uwTileOffsY = SCROLLBUFFER_HEIGHT_MODULO( pState->pMarginY->wTilePos << ubTileShift, pManager->uwMarginedHeight ); - uwTileOffsX = (pState->pMarginY->wTileCurr << ubTileShift); + UWORD uwTileOffsX = (pState->pMarginY->wTileCurr << ubTileShift); // Redraw remaining tiles UWORD uwBltsize = tileBufferSetupTileDraw(pManager); UWORD uwTileCurr = pState->pMarginY->wTileCurr; @@ -617,10 +629,6 @@ void tileBufferProcess(tTileBufferManager *pManager) { void tileBufferRedrawAll(tTileBufferManager *pManager) { logBlockBegin("tileBufferRedrawAll(pManager: %p)", pManager); - // Reset margin redraw structs as we're redrawing everything anyway - tileBufferResetRedrawState(&pManager->pRedrawStates[0]); - tileBufferResetRedrawState(&pManager->pRedrawStates[1]); - UBYTE ubTileSize = pManager->ubTileSize; UBYTE ubTileShift = pManager->ubTileShift; @@ -635,6 +643,13 @@ void tileBufferRedrawAll(tTileBufferManager *pManager) { pManager->uTileBounds.uwY, wStartY + (pManager->uwMarginedHeight >> ubTileShift) ); + // Reset margin redraw structs as we're redrawing everything anyway + tileBufferResetRedrawState( + &pManager->pRedrawStates[0], wStartX, uwEndX, wStartY, uwEndY + ); + tileBufferResetRedrawState( + &pManager->pRedrawStates[1], wStartX, uwEndX, wStartY, uwEndY + ); UWORD uwTileOffsY = SCROLLBUFFER_HEIGHT_MODULO( wStartY << ubTileShift, pManager->uwMarginedHeight diff --git a/src/ace/utils/disk_file.c b/src/ace/utils/disk_file.c index 6965861d..fb1de5ab 100644 --- a/src/ace/utils/disk_file.c +++ b/src/ace/utils/disk_file.c @@ -22,13 +22,33 @@ static const tFileCallbacks s_sDiskFileCallbacks = { //------------------------------------------------------------------ PRIVATE FNS +// TODO: move to system to be able to use for dir functions? + +static void fileAccessEnable(void) { + systemUse(); + + // Needed only for KS1.3 + // TODO: do only when reading from floppy + if(systemGetVersion() < 36) { + systemReleaseBlitterToOs(); + } +} + +static void fileAccessDisable(void) { + if(systemGetVersion() < 36) { + systemGetBlitterFromOs(); + } + + systemUnuse(); +} + DISKFILE_PRIVATE void diskFileClose(void *pData) { FILE *pFile = (FILE*)pData; systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); fclose(pFile); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); } @@ -36,9 +56,9 @@ DISKFILE_PRIVATE ULONG diskFileRead(void *pData, void *pDest, ULONG ulSize) { FILE *pFile = (FILE*)pData; systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); ULONG ulReadCount = fread(pDest, ulSize, 1, pFile); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); return ulReadCount; @@ -48,10 +68,10 @@ DISKFILE_PRIVATE ULONG diskFileWrite(void *pData, const void *pSrc, ULONG ulSize FILE *pFile = (FILE*)pData; systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); ULONG ulResult = fwrite(pSrc, ulSize, 1, pFile); fflush(pFile); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); return ulResult; @@ -61,9 +81,9 @@ DISKFILE_PRIVATE ULONG diskFileSeek(void *pData, LONG lPos, WORD wMode) { FILE *pFile = (FILE*)pData; systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); ULONG ulResult = fseek(pFile, lPos, wMode); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); return ulResult; @@ -73,9 +93,9 @@ DISKFILE_PRIVATE ULONG diskFileGetPos(void *pData) { FILE *pFile = (FILE*)pData; systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); ULONG ulResult = ftell(pFile); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); return ulResult; @@ -85,9 +105,9 @@ DISKFILE_PRIVATE UBYTE diskFileIsEof(void *pData) { FILE *pFile = (FILE*)pData; systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); UBYTE ubResult = feof(pFile); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); return ubResult; @@ -97,9 +117,9 @@ DISKFILE_PRIVATE void diskFileFlush(void *pData) { FILE *pFile = (FILE*)pData; systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); fflush(pFile); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); } @@ -109,7 +129,7 @@ tFile *diskFileOpen(const char *szPath, const char *szMode) { logBlockBegin("diskFileOpen(szPath: '%s', szMode: '%s')", szPath, szMode); // TODO check if disk is read protected when szMode has 'a'/'r'/'x' systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); tFile *pFile = 0; FILE *pFileHandle = fopen(szPath, szMode); if(pFileHandle == 0) { @@ -125,7 +145,7 @@ tFile *diskFileOpen(const char *szPath, const char *szMode) { logWrite("File handle: %p, data: %p\n", pFile, pFile->pData); #endif } - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); logBlockEnd("diskFileOpen()"); @@ -134,14 +154,14 @@ tFile *diskFileOpen(const char *szPath, const char *szMode) { UBYTE diskFileExists(const char *szPath) { systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); UBYTE isExisting = 0; - tFile *pFile = diskFileOpen(szPath, "r"); - if(pFile) { + FILE *pFileHandle = fopen(szPath, "r"); + if(pFileHandle) { isExisting = 1; - fileClose(pFile); + fclose(pFileHandle); } - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); return isExisting; @@ -149,18 +169,18 @@ UBYTE diskFileExists(const char *szPath) { UBYTE diskFileDelete(const char *szFilePath) { systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); UBYTE isSuccess = remove(szFilePath); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); return isSuccess; } UBYTE diskFileMove(const char *szSource, const char *szDest) { systemUse(); - systemReleaseBlitterToOs(); + fileAccessEnable(); UBYTE isSuccess = rename(szSource, szDest); - systemGetBlitterFromOs(); + fileAccessDisable(); systemUnuse(); return isSuccess; } diff --git a/src/ace/utils/palette.c b/src/ace/utils/palette.c index 27870b08..04665916 100644 --- a/src/ace/utils/palette.c +++ b/src/ace/utils/palette.c @@ -57,6 +57,28 @@ UWORD paletteColorDim(UWORD uwFullColor, UBYTE ubLevel) { return (r << 8) | (g << 4) | b; } +UWORD paletteColorMix( + UWORD uwColorPrimary, UWORD uwColorSecondary, UBYTE ubLevel +) { + UBYTE r1,g1,b1; + UBYTE r2,g2,b2; + + r1 = (uwColorPrimary >> 8) & 0xF; + g1 = (uwColorPrimary >> 4) & 0xF; + b1 = (uwColorPrimary) & 0xF; + r2 = (uwColorSecondary >> 8) & 0xF; + g2 = (uwColorSecondary >> 4) & 0xF; + b2 = (uwColorSecondary) & 0xF; + + // Dim color + r1 = ((r1 * ubLevel + (r2 * (0xF - ubLevel)))/15) & 0xF; + g1 = ((g1 * ubLevel + (g2 * (0xF - ubLevel)))/15) & 0xF; + b1 = ((b1 * ubLevel + (b2 * (0xF - ubLevel)))/15) & 0xF; + + // Output + return (r1 << 8) | (g1 << 4) | b1; +} + void paletteDump(UWORD *pPalette, UBYTE ubColorCnt, char *szPath) { UBYTE ubLastColor = ubColorCnt - 1; UBYTE ubBpp = 0; diff --git a/tools/src/audio_conv.cpp b/tools/src/audio_conv.cpp index fc7e1092..104e65f7 100644 --- a/tools/src/audio_conv.cpp +++ b/tools/src/audio_conv.cpp @@ -17,7 +17,7 @@ void printUsage(const std::string &szAppName) { print("Extra options:\n"); print("\t-o outPath Specify output file path. If ommited, it will perform default conversion\n"); print("\t-d N Specify amplitude division. Useful for some audio-mixing libraries\n"); - print("\t-cd N Ensure that sound effect fits max amplitude divided by specified factor. Useful for some audio-mixing libraries\n"); + print("\t-cd N Check if sound effect fits max amplitude divided by specified factor and raise error otherwise. Useful for some audio-mixing libraries\n"); print("\t-strict Treat warinings as errors (recommended)\n"); print("\t-n Normalize audio files\n"); print("\t-fpt Enforce ptplayer-friendly mode: adds empty sample at the beginning, if missing\n"); diff --git a/tools/src/bitmap_transform.cpp b/tools/src/bitmap_transform.cpp index 3ee148f4..0867ba0a 100644 --- a/tools/src/bitmap_transform.cpp +++ b/tools/src/bitmap_transform.cpp @@ -23,12 +23,17 @@ class tOpExtract: public tOp { class tOpRotate: public tOp { public: - tOpRotate(double dDeg, tRgb Bg); + tOpRotate( + double dDeg, tRgb Bg, + double dCenterX, double dCenterY + ); virtual tChunkyBitmap execute(const tChunkyBitmap &Src); virtual std::string toString(void); private: double m_dDeg; tRgb m_Bg; + double m_dCenterX; + double m_dCenterY; }; class tOpMirror: public tOp { @@ -50,7 +55,7 @@ void printUsage(const std::string &szAppName) print("Usage:\n\t{} in.png out.png [transforms]\n", szAppName); print("\nAvailable transforms, done in passed order:\n"); print("\t-extract x y width height\tExtract rectangle at x,y of size width*height.\n"); - print("\t-rotate deg bgcolor \tRotate clockwise by given number of degrees. Can be negative.\n"); + print("\t-rotate deg bgcolor cx cy\tRotate clockwise by given number of degrees around cx,cy. Values can be floats/negative.\n"); print("\t-mirror x|y \tMirror image along x or y axis.\n"); print("\nCurrently only PNG is supported, sorry!\n"); } @@ -102,7 +107,7 @@ int main(int lArgCount, char *pArgs[]) } } else if(szOp == "-rotate") { - if(ArgIndex + 2 >= lArgCount) { + if(ArgIndex + 4 >= lArgCount) { nLog::error( "Too few args for {} - first arg at pos {}, arg count: {}", szOp, ArgIndex + 1, lArgCount @@ -112,10 +117,12 @@ int main(int lArgCount, char *pArgs[]) try { auto Deg = std::stod(pArgs[++ArgIndex]); auto Bg = tRgb(pArgs[++ArgIndex]); - vOps.push_back(std::make_unique(Deg, Bg)); + auto CenterX = std::stod(pArgs[++ArgIndex]); + auto CenterY = std::stod(pArgs[++ArgIndex]); + vOps.push_back(std::make_unique(Deg, Bg, CenterX, CenterY)); } catch(std::exception Ex) { - nLog::error("Couldn't parse rotate degrees: '{}'", pArgs[ArgIndex]); + nLog::error("Couldn't parse arg: '{}'", pArgs[ArgIndex]); return EXIT_FAILURE; } } @@ -222,16 +229,17 @@ std::string tOpMirror::toString(void) //---------------------------------------------------------------------OP ROTATE -tOpRotate::tOpRotate(double dDeg, tRgb Bg): - m_dDeg(dDeg), m_Bg(Bg) +tOpRotate::tOpRotate( + double dDeg, tRgb Bg, + double dCenterX, double dCenterY +): + m_dDeg(dDeg), m_Bg(Bg), m_dCenterX(dCenterX), m_dCenterY(dCenterY) { // Nothing here for now } tChunkyBitmap tOpRotate::execute(const tChunkyBitmap &Source) { tChunkyBitmap Dst(Source.m_uwWidth, Source.m_uwHeight); - auto CenterX = (Source.m_uwWidth - 1) / 2.0; - auto CenterY = (Source.m_uwHeight - 1) / 2.0; auto Rad = (m_dDeg * 2 * M_PI) / 360; auto CalcCos = cos(Rad); @@ -239,11 +247,11 @@ tChunkyBitmap tOpRotate::execute(const tChunkyBitmap &Source) { // For each of new bitmap's pixel sample color from rotated source x,y for(auto Y = 0; Y < Dst.m_uwHeight; ++Y) { - auto Dy = Y - CenterY; + auto Dy = Y - m_dCenterY; for(auto X = 0; X < Dst.m_uwWidth; ++X) { - auto Dx = X - CenterX; - auto U = uint16_t(round(CalcCos * Dx + CalcSin * Dy + (CenterX))); - auto V = uint16_t(round(-CalcSin * Dx + CalcCos * Dy + (CenterY))); + auto Dx = X - m_dCenterX; + auto U = uint16_t(round(CalcCos * Dx + CalcSin * Dy + (m_dCenterX))); + auto V = uint16_t(round(-CalcSin * Dx + CalcCos * Dy + (m_dCenterY))); if(U < 0 || V < 0 || U >= Dst.m_uwWidth || V >= Dst.m_uwHeight) { // fmt::print("can't sample for {:2d},{:2d} from {:.1f},{:.1f}\n", X, Y, U, V); diff --git a/tools/src/tileset_conv.cpp b/tools/src/tileset_conv.cpp index 4c3f99ee..9a705cdc 100644 --- a/tools/src/tileset_conv.cpp +++ b/tools/src/tileset_conv.cpp @@ -49,7 +49,6 @@ tConfig::tConfig(const std::vector &vArgs) m_isInterleaved = true; } else if(vArgs[ArgIndex] == std::string("-vh")) { - fmt::print("VARIABLE HEIGHT ON\n\n\n\n"); m_isVaryingHeight = true; } else if(vArgs[ArgIndex] == std::string("-plt") && ArgIndex < ArgCount - 1) {