From 7f4cefdc36bbc5e6821b6197cf6f75852acd6c8a Mon Sep 17 00:00:00 2001 From: CrashOveride95 Date: Wed, 9 Dec 2020 09:05:40 -0500 Subject: [PATCH] Get HVQM working --- Makefile | 26 +-- data/hvqm.s | 2 +- include/hvqm.inc.c | 340 +++++++++++++++++++++++++++++++ include/timekeeper.inc.c | 423 +++++++++++++++++++++++++++++++++++++++ src/game/crash_screen.c | 37 ---- src/game/game_init.c | 7 +- src/game/main.c | 6 +- src/game/main.h | 1 - 8 files changed, 772 insertions(+), 70 deletions(-) create mode 100644 include/hvqm.inc.c create mode 100644 include/timekeeper.inc.c diff --git a/Makefile b/Makefile index 49e179fb..91cef3f7 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ TARGET_N64 ?= 1 # ido - uses the SGI IRIS Development Option compiler, which is used to build # an original matching N64 ROM # gcc - uses the GNU C Compiler -COMPILER ?= ido +COMPILER ?= gcc $(eval $(call validate-option,COMPILER,ido gcc)) @@ -660,30 +660,6 @@ ifeq ($(COMPILER),ido) $(BUILD_DIR)/levels/%/leveldata.o: OPT_FLAGS := -g $(BUILD_DIR)/actors/%.o: OPT_FLAGS := -g $(BUILD_DIR)/bin/%.o: OPT_FLAGS := -g - $(BUILD_DIR)/src/goddard/%.o: OPT_FLAGS := -g - $(BUILD_DIR)/src/goddard/%.o: MIPSISET := -mips1 - $(BUILD_DIR)/lib/src/%.o: OPT_FLAGS := - $(BUILD_DIR)/lib/src/math/%.o: OPT_FLAGS := -O2 - $(BUILD_DIR)/lib/src/math/ll%.o: OPT_FLAGS := - $(BUILD_DIR)/lib/src/math/ll%.o: MIPSISET := -mips3 -32 - $(BUILD_DIR)/lib/src/ldiv.o: OPT_FLAGS := -O2 - $(BUILD_DIR)/lib/src/string.o: OPT_FLAGS := -O2 - $(BUILD_DIR)/lib/src/gu%.o: OPT_FLAGS := -O3 - $(BUILD_DIR)/lib/src/al%.o: OPT_FLAGS := -O3 - # For the asm-processor, since it doesn't support -O3. Probably not actually compiled with these flags. - ifeq ($(VERSION),sh) - $(BUILD_DIR)/lib/src/unk_shindou_file.o: OPT_FLAGS := -O1 - $(BUILD_DIR)/lib/src/func_sh_80304D20.o: OPT_FLAGS := -O1 - $(BUILD_DIR)/lib/src/_Printf.o: OPT_FLAGS := -O3 - $(BUILD_DIR)/lib/src/contramread.o: OPT_FLAGS := -O1 - $(BUILD_DIR)/lib/src/osPfsIsPlug.o: OPT_FLAGS := -O1 - $(BUILD_DIR)/lib/src/osAiSetFrequency.o: OPT_FLAGS := -O1 - $(BUILD_DIR)/lib/src/contramwrite.o: OPT_FLAGS := -O1 - $(BUILD_DIR)/lib/src/sprintf.o: OPT_FLAGS := -O3 - $(BUILD_DIR)/lib/src/_Litob.o: OPT_FLAGS := -O3 - $(BUILD_DIR)/lib/src/_Ldtob.o: OPT_FLAGS := -O3 - $(BUILD_DIR)/lib/src/osDriveRomInit.o: OPT_FLAGS := -g - endif ifeq ($(VERSION),eu) $(BUILD_DIR)/lib/src/_Litob.o: OPT_FLAGS := -O3 $(BUILD_DIR)/lib/src/_Ldtob.o: OPT_FLAGS := -O3 diff --git a/data/hvqm.s b/data/hvqm.s index 3b63d916..8f52250c 100644 --- a/data/hvqm.s +++ b/data/hvqm.s @@ -3,5 +3,5 @@ .section .data glabel _hvqmSampleRomStart -.incbin "data/joe.hvqm" +.incbin "data/terry.hvqm" glabel _hvqmSampleRomEnd diff --git a/include/hvqm.inc.c b/include/hvqm.inc.c new file mode 100644 index 00000000..af6edfa1 --- /dev/null +++ b/include/hvqm.inc.c @@ -0,0 +1,340 @@ +#include +#include +#include +/* + * Size of the data area for the HVQ microcode + */ +#define HVQ_SPFIFO_SIZE 30000 + +/* + * Size of buffer for video records + */ +#define HVQ_DATASIZE_MAX 40000 + +/* + * Size of buffer for audio records + */ +#define AUDIO_RECORD_SIZE_MAX 5000 + +/* + * Size of data area for HVQM2 microcode + */ +#define HVQ_SPFIFO_SIZE 30000 + +#define SCREEN_WD 320 /* Screen width [pixel] */ +#define SCREEN_HT 240 /* Screen height [pixel] */ + +#define APP_GFX_UCODE_HVQ 6 /* HVQ2 microcode */ + +#define AUDIO_DMA_MSG_SIZE 1 + +u8 hvqbuf[HVQ_DATASIZE_MAX]; + +static OSIoMesg audioDmaMesgBlock; +static OSMesgQueue audioDmaMessageQ; +static OSMesg audioDmaMessages[AUDIO_DMA_MSG_SIZE]; + +/*********************************************************************** + * Message queue for receiving message blocks and end of DMA + * notifications when requesting that video records be read from + * the HVQM2 data (ROM). + ***********************************************************************/ +#define VIDEO_DMA_MSG_SIZE 1 +static OSIoMesg videoDmaMesgBlock; +static OSMesgQueue videoDmaMessageQ; +static OSMesg videoDmaMessages[VIDEO_DMA_MSG_SIZE]; + +/*********************************************************************** + * SP event (SP task end) message queue + ***********************************************************************/ +static OSMesgQueue spMesgQ; +static OSMesg spMesgBuf; + +/*********************************************************************** + * RSP task data and parameter for the HVQM2 microcode + ***********************************************************************/ +OSTask hvqtask; /* RSP task data */ +HVQM2Arg hvq_sparg; /* Parameter for the HVQM2 microcode */ + +/*********************************************************************** + * Buffer for the HVQM2 header ***********************************************************************/ +u8 hvqm_headerBuf[sizeof(HVQM2Header) + 16]; + +/*********************************************************************** + * Other data + ***********************************************************************/ +static u32 total_frames; /* Total number of video records (frames) */ +static u32 total_audio_records; /* Total number of audio records */ +static void *video_streamP; /* Video record read-in pointer */ +static void *audio_streamP; /* Audio record read-in pointer */ +static u32 audio_remain; /* Counter for remaining number of audio records to read */ +static u32 video_remain; /* Counter for remaining number of video records to read */ +static u64 disptime; /* Counter for scheduled display time of next video frame */ +static ADPCMstate adpcm_state; /* Buffer for state information passed to the ADPCM decoder */ + +u64 hvq_yieldbuf[HVQM2_YIELD_DATA_SIZE/8]; +u8 adpcmbuf[AUDIO_RECORD_SIZE_MAX]; + +/* + * Macro for loading multi-byte data from buffer holding data from stream + */ +#define load32(from) (*(u32*)&(from)) +#define load16(from) (*(u16*)&(from)) + +extern u8 _hvqmSegmentRomStart[]; + +u16 hvqwork[(SCREEN_WD/8)*(SCREEN_HT/4)*4]; +// Data area for the HVQ microcode +HVQM2Info hvq_spfifo[HVQ_SPFIFO_SIZE]; +// Buffer for RSP task yield +u64 hvq_yieldbuf[HVQM2_YIELD_DATA_SIZE/8]; + +u32 cfb_status[NUM_CFBs]; + +// Clears all frame buffers and initializes state of frame buffer +void init_cfb(void) { + int i, j; + + for ( i = 0; i < NUM_CFBs; i++ ) { + for ( j = 0; j < SCREEN_WD*SCREEN_HT; j++ ) gFrameBuffers[i][j] = 0; + cfb_status[i] = CFB_FREE; + } +} + +// Frame buffer indicated by the index cfbno is protected by being made unavailable. +void keep_cfb( int cfbno ) { + cfb_status[cfbno] |= CFB_PRECIOUS; +} + +//Frame buffer indicated by the index cfbno is released from protection. +void release_cfb( int bufno ) { + if ( bufno >= 0 ) cfb_status[bufno] &= ~CFB_PRECIOUS; +} + +// All frame buffers are released from protection. +void release_all_cfb(void) { + int i; + for ( i = 0; i < NUM_CFBs; i++ ) cfb_status[i] &= ~CFB_PRECIOUS; +} + +// Search for available frame buffer and return that index. +// Thread yields until an available frame buffer is found. +int get_cfb() { + int cfbno; + + for ( ; ; ) { + for ( cfbno = 0; cfbno < NUM_CFBs; cfbno++ ) + if ( cfb_status[cfbno] == CFB_FREE ) + return cfbno; + osYieldThread(); + } +} + +void romcpy(void *dest, void *src, u32 len, s32 pri, OSIoMesg *mb, OSMesgQueue *mq) { + osInvalDCache(dest, (s32)len); + while (osPiStartDma(mb, pri, OS_READ, (u32)src, dest, len, mq) == -1) {} + osRecvMesg(mq, (OSMesg *)NULL, OS_MESG_BLOCK); +} + +u8 *get_record(HVQM2Record *headerbuf, void *bodybuf, u16 type, u8 *stream, OSIoMesg *mb, OSMesgQueue *mq) { + u16 record_type; + u32 record_size; + s32 pri, i; + + pri = (type == HVQM2_AUDIO) ? OS_MESG_PRI_HIGH : OS_MESG_PRI_NORMAL; + + while (1) { + romcpy(headerbuf, stream, sizeof(HVQM2Record), pri, mb, mq); + stream += sizeof(HVQM2Record); + record_type = load16(headerbuf->type); + record_size = load32(headerbuf->size); + if (record_type == type) break; + stream += record_size; + } + + if (record_size > 0) { + romcpy(bodybuf, stream, record_size, pri, mb, mq); + stream += record_size; + } + + return stream; +} + +static u32 next_audio_record( void *pcmbuf ) { + u8 header_buffer[sizeof(HVQM2Record)+16]; + HVQM2Record *record_header; + HVQM2Audio *audio_headerP; + u32 samples; + + if ( audio_remain == 0 ) return 0; + + record_header = OS_DCACHE_ROUNDUP_ADDR(header_buffer); + audio_streamP = get_record(record_header, adpcmbuf, HVQM2_AUDIO, audio_streamP, &audioDmaMesgBlock, &audioDmaMessageQ); + --audio_remain; + + audio_headerP = (HVQM2Audio *)adpcmbuf; + samples = load32(audio_headerP->samples); + adpcmDecode(&audio_headerP[1], (u32)load16(record_header->format), samples, pcmbuf, 1, &adpcm_state); + + return samples; +} + +static tkAudioProc rewind( void ) { + video_streamP = audio_streamP = (u32)_hvqmSegmentRomStart + sizeof(HVQM2Header); + audio_remain = total_audio_records; + video_remain = total_frames; + disptime = 0; + return &next_audio_record; +} + +HVQM2Header *hvqm_header; + +static OSMesgQueue hvqmMesgQ; +static OSMesg hvqmMesgBuf; + +static OSThread hvqmThread; +static u64 hvqmStack[STACKSIZE/sizeof(u64)]; + +void hvqm_main_proc() { + int h_offset, v_offset; /* Position of image display */ + int screen_offset; /* Number of pixels from start of frame buffer to display position */ + u32 usec_per_frame; + int prev_bufno = -1; + + + hvqm_header = OS_DCACHE_ROUNDUP_ADDR( hvqm_headerBuf ); + + osCreateMesgQueue( &spMesgQ, &spMesgBuf, 1 ); + osSetEventMesg( OS_EVENT_SP, &spMesgQ, NULL ); + + osCreateMesgQueue( &audioDmaMessageQ, audioDmaMessages, AUDIO_DMA_MSG_SIZE ); + osCreateMesgQueue( &videoDmaMessageQ, videoDmaMessages, VIDEO_DMA_MSG_SIZE ); + createTimekeeper(); + + hvqm2InitSP1(0xff); + hvqtask.t.ucode = (u64 *)hvqm2sp1TextStart; + hvqtask.t.ucode_size = (int)hvqm2sp1TextEnd - (int)hvqm2sp1TextStart; + hvqtask.t.ucode_data = (u64 *)hvqm2sp1DataStart; + hvqtask.t.type = M_HVQM2TASK; + hvqtask.t.flags = 0; + hvqtask.t.ucode_boot = (u64 *)rspbootTextStart; + hvqtask.t.ucode_boot_size = (int)rspbootTextEnd - (int)rspbootTextStart; + hvqtask.t.ucode_data_size = HVQM2_UCODE_DATA_SIZE; + hvqtask.t.data_ptr = (u64 *)&hvq_sparg; + hvqtask.t.yield_data_ptr = (u64 *)hvq_yieldbuf; + hvqtask.t.yield_data_size = HVQM2_YIELD_DATA_SIZE; + + init_cfb(); + osViSwapBuffer( gFrameBuffers[NUM_CFBs-1] ); + + romcpy(hvqm_header, (void *)_hvqmSegmentRomStart, sizeof(HVQM2Header), OS_MESG_PRI_NORMAL, &videoDmaMesgBlock, &videoDmaMessageQ); + + total_frames = load32(hvqm_header->total_frames); + usec_per_frame = load32(hvqm_header->usec_per_frame); + total_audio_records = load32(hvqm_header->total_audio_records); + + hvqm2SetupSP1(hvqm_header, SCREEN_WD); + + release_all_cfb(); + tkStart( &rewind, load32( hvqm_header->samples_per_sec ) ); + + for ( ; ; ) { + + //while ( video_remain > 0 ) { + u8 header_buffer[sizeof(HVQM2Record)+16]; + HVQM2Record *record_header; + u16 frame_format; + int bufno; + OSMesg msg; + + if ( disptime > 0 && tkGetTime() > 0) { + if ( tkGetTime() < (disptime - (usec_per_frame * 2)) ) { + tkPushVideoframe( gFrameBuffers[prev_bufno], &cfb_status[prev_bufno], disptime ); + continue; + //if ( video_remain == 0 ) break; + } + } + + record_header = OS_DCACHE_ROUNDUP_ADDR( header_buffer ); + + video_streamP = get_record(record_header, hvqbuf, + HVQM2_VIDEO, video_streamP, + &videoDmaMesgBlock, &videoDmaMessageQ); + + //! SYNC VIDEO code + + if ( disptime > 0 && tkGetTime() > 0) { + if ( tkGetTime() > (disptime + (usec_per_frame * 2)) ) { + release_all_cfb(); + do { + disptime += usec_per_frame; + if ( --video_remain == 0 ) break; + video_streamP = get_record( record_header, hvqbuf, + HVQM2_VIDEO, video_streamP, + &videoDmaMesgBlock, &videoDmaMessageQ ); + } while (load16( record_header->format ) != HVQM2_VIDEO_KEYFRAME || tkGetTime() > disptime ); + if ( video_remain == 0 ) break; + } + } + + frame_format = load16(record_header->format); + if (frame_format == HVQM2_VIDEO_HOLD) { + /* + * Just like when frame_format != HVQM2_VIDEO_HOLD you + * could call hvqm2Decode*() and decode in a new frame + * buffer (in this case, just copying from the buffer of + * the preceding frame). But here we make use of the + * preceding frame's buffer for the next frame in order + * to speed up the process. + */ + bufno = prev_bufno; + } else { + int status; + bufno = get_cfb(); /* Get the frame buffer */ + + /* + * Process first half in the CPU + */ + hvqtask.t.flags = 0; + status = hvqm2DecodeSP1( hvqbuf, frame_format, + &gFrameBuffers[bufno][screen_offset], + &gFrameBuffers[prev_bufno][screen_offset], + hvqwork, &hvq_sparg, hvq_spfifo ); + + osWritebackDCacheAll(); + + /* + * Process last half in the RSP + */ + if ( status > 0 ) { + osInvalDCache( (void *)gFrameBuffers[bufno], sizeof gFrameBuffers[bufno] ); + osSpTaskStart( &hvqtask ); + osRecvMesg( &spMesgQ, NULL, OS_MESG_BLOCK ); + } + } + + keep_cfb( bufno ); + + if ( prev_bufno >= 0 && prev_bufno != bufno ) + release_cfb( prev_bufno ); + + tkPushVideoframe( gFrameBuffers[bufno], &cfb_status[bufno], disptime ); + + prev_bufno = bufno; + disptime += usec_per_frame; + --video_remain; + + //if (1) { + //osAiSetFrequency(gAudioSessionPresets[0].frequency); + //osSendMesg(&gDmaMesgQueue, 0, OS_MESG_BLOCK); + //osDestroyThread(&hvqmMesgQ); + //} + } +} + +void createHvqmThread(void) { + osCreateMesgQueue( &hvqmMesgQ, &hvqmMesgBuf, 1 ); + osCreateThread( &hvqmThread, HVQM_THREAD_ID, hvqm_main_proc, + NULL, hvqmStack + (STACKSIZE/sizeof(u64)), + (OSPri)HVQM_PRIORITY ); +} \ No newline at end of file diff --git a/include/timekeeper.inc.c b/include/timekeeper.inc.c new file mode 100644 index 00000000..96d67a85 --- /dev/null +++ b/include/timekeeper.inc.c @@ -0,0 +1,423 @@ +#include +#include "audio/data.h" +#include "buffers/framebuffers.h" + +#define NUM_CFBs 3 +#define STACKSIZE 0x2000 +#define PCM_CHANNELS 2 /* Number of channels */ +#define PCM_CHANNELS_SHIFT 1 /* log2(PCM_CHANNELS) */ +#define PCM_ALIGN 2 /* Alignment of number of samples to send */ +#define PCM_BYTES_PER_SAMPLE (2 * PCM_CHANNELS) /* Number of bytes in one sample */ +#define PCM_BYTES_PER_SAMPLE_SHIFT 2 /* log2(PCM_BYTES_PER_SAMPLE) */ + +/* + * Audio record definitions + */ +#define AUDIO_SAMPLE_BITS 4 +#define AUDIO_SAMPLES_MAX (((AUDIO_RECORD_SIZE_MAX-sizeof(HVQM2Audio))*8/AUDIO_SAMPLE_BITS)+1) /* Maximum number of records per sample */ + +/* + * PCM buffer specifications + */ +#define NUM_PCMBUFs 3 /* Number of PCM buffers (2 or more, at least 3 recommended) */ +#define PCMBUF_SPREAD ((PCM_ALIGN-1)+AUDIO_SAMPLES_MAX) /* Minimum required capacity for PCM buffer = Number of samples carried forward from last time + number of samples newly decoded */ +#define PCMBUF_ALIGNED ((PCMBUF_SPREAD+(PCM_ALIGN-1))&(~(PCM_ALIGN-1))) /* pcmbuf[i] address is aligned */ +#define PCMBUF_SIZE 0xA00 + +/* + * Thread ID and priority + */ +#define IDLE_THREAD_ID 1 +#define MAIN_THREAD_ID 3 +#define HVQM_THREAD_ID 7 +#define TIMEKEEPER_THREAD_ID 8 +#define DA_COUNTER_THREAD_ID 9 + +#define IDLE_PRIORITY 10 +#define MAIN_PRIORITY 10 +#define HVQM_PRIORITY 11 +#define TIMEKEEPER_PRIORITY 12 +#define DA_COUNTER_PRIORITY 13 + +#define PI_COMMAND_QUEUE_SIZE 4 + +typedef u32 (*tkAudioProc)(void *pcmbuf); +typedef tkAudioProc (*tkRewindProc)(void); + +/*********************************************************************** + * Timekeeper thread + ***********************************************************************/ +static OSThread tkThread; +static u64 tkStack[STACKSIZE/sizeof(u64)]; + +/*********************************************************************** + * Audio DA counter thread + ***********************************************************************/ +static OSThread daCounterThread; +static u64 daCounterStack[STACKSIZE/sizeof(u64)]; + +/*********************************************************************** + * Timekeeper's command message queue + ***********************************************************************/ +static OSMesgQueue tkCmdMesgQ; +static OSMesg tkCmdMesgBuf; + +/*********************************************************************** + * Message queue for responses from timekeeper + ***********************************************************************/ +static OSMesgQueue tkResMesgQ; +static OSMesg tkResMesgBuf; + +/*********************************************************************** + * Timekeeper's command structure + ***********************************************************************/ +typedef struct { + tkRewindProc rewind; /* Pointer to stream rewind function */ + u32 samples_per_sec; /* Audio sampling rate */ +} TKCMD; + +/*********************************************************************** + * VI event (retrace) message queue + ***********************************************************************/ +#define VI_MSG_SIZE 2 +static OSMesgQueue viMessageQ; +static OSMesg viMessages[VI_MSG_SIZE]; + +/*********************************************************************** + * AI event (FIFO free) message queue + ***********************************************************************/ +#define AI_MSG_SIZE 2 +static OSMesgQueue aiMessageQ; +static OSMesg aiMessages[AI_MSG_SIZE]; + +/*********************************************************************** + * Ring buffer for reserving display of image + * + * As soon as image (frame buffer) is complete, put into this + * ring buffer, wait for display time to come and set in VI. + ***********************************************************************/ +#define VIDEO_RING_SIZE NUM_CFBs + +static struct { + u64 disptime; /* Display time */ + void *vaddr; /* Frame buffer address */ + u32 *statP; /* Pointer to frame buffer state flag */ +} videoRing[VIDEO_RING_SIZE]; + +static int videoRingCount; /* Amount of data remaining in ring buffer */ +static int videoRingWrite; /* Ring buffer write index */ +static int videoRingRead; /* Ring buffer read index */ + +/*********************************************************************** + * Ring buffer for reserving playback of audio + * + * As soon as audio (PCM data) is complete, put into this + * ring buffer and push as soon as AI FIFO is free. + ***********************************************************************/ +#define AUDIO_RING_BUFFER_SIZE NUM_PCMBUFs + +static struct { + void *buf; /* PCM data buffer */ + u32 len; /* PCM data length */ +} audioRing[AUDIO_RING_BUFFER_SIZE]; + +static int audioRingCount; /* Amount of data remaining in ring buffer */ +static int audioRingWrite; /* Ring buffer write index */ +static int audioRingRead; /* Ring buffer read index */ + +/*********************************************************************** + * PCM data buffer information + ***********************************************************************/ +static int pcmBufferCount; /* Number of PCM data buffers being used */ +static u32 aiFIFOsamples; /* Number of PCM samples waiting in AI FIFO */ +static u32 aiDAsamples; /* Number of samples AI is DMA transferring to DA */ + +/*********************************************************************** + * Buffer for matching PCM transfer data length to PCM_ALIGN + ***********************************************************************/ +static s16 pcmModBuf[(PCM_ALIGN-1)*PCM_CHANNELS]; + +/*********************************************************************** + * Flag enabling measurement of passage of time + ***********************************************************************/ +static int clock_alive; + +/*********************************************************************** + * Audio sampling rate + ***********************************************************************/ +static u32 samples_per_sec; + +#define CFB_FREE 0 /* Available */ +#define CFB_PRECIOUS (1<<0) /* Constrained for decoding of next frame */ +#define CFB_SHOWING (1<<1) /* Waiting to display or displaying */ + +/*********************************************************************** + * Number of PCM samples that have finished playing + * + * Strictly the number samples that have finished playing. Does not + * include samples in FIFO nor the sample playing in the DA buffer. + * + * From this value you get the amount of time that has passed + * since the start of play of the HVQM2 stream. + ***********************************************************************/ +static u64 samples_played; + +/*********************************************************************** + * The time samples_played was first updated ( osGetTime() ) + * + * Time measured beyond this point is supplemented with osGetTime(). + ***********************************************************************/ +static OSTime last_time; + +#define OS_CLOCK_RATE 62500000LL +#define OS_CPU_COUNTER (OS_CLOCK_RATE*3/4) +#define OS_NSEC_TO_CYCLES(n) (((u64)(n)*(OS_CPU_COUNTER/15625000LL))/(1000000000LL/15625000LL)) +#define OS_USEC_TO_CYCLES(n) (((u64)(n)*(OS_CPU_COUNTER/15625LL))/(1000000LL/15625LL)) +#define OS_CYCLES_TO_NSEC(c) (((u64)(c)*(1000000000LL/15625000LL))/(OS_CPU_COUNTER/15625000LL)) +#define OS_CYCLES_TO_USEC(c) (((u64)(c)*(1000000LL/15625LL))/(OS_CPU_COUNTER/15625LL)) + +u64 tkGetTime(void) { + u64 retval; + + if (!clock_alive) return 0; + + retval = OS_CYCLES_TO_USEC((u64)osGetTime() - last_time); + if (samples_per_sec) retval += samples_played * 1000000LL / samples_per_sec; + return retval; +} + +static void tkClockDisable(void) { + clock_alive = 0; +} + +static void tkClockStart(void) { + samples_played = 0; + last_time = osGetTime(); + clock_alive = 1; +} + +static void daCounterProc(void *argument) { + while (1) { + osRecvMesg( &aiMessageQ, NULL, OS_MESG_BLOCK ); + last_time = osGetTime(); + if (aiDAsamples > 0) --pcmBufferCount; + samples_played += aiDAsamples; + aiDAsamples = aiFIFOsamples; + aiFIFOsamples = 0; + } +} + +static void timekeeperProc(void *argument) { + TKCMD *cmd; + void *showing_cfb = NULL; /* Frame buffer being displayed */ + u32 *showing_cfb_statP = NULL; /* Pointer to state flag of the frame buffer being displayed */ + s32 video_started; /* Flag indicating start of video display */ + s32 audio_started; /* Flag indicating start of audio playback */ + s32 video_done; /* Flag indicating end of video decoding */ + s32 audio_done; /* Flag indicating end of audio decoding */ + void *pushed_cfb; /* Frame buffer reserved for swap */ + u32 *pushed_cfb_statP; /* Pointer to state flag of frame buffer reserved for swap */ + s32 next_pcmbufno; /* The number of the PCM buffer to use next */ + s32 pcm_mod_samples; /* The number of PCM samples left over */ + tkAudioProc audioproc; + OSTime present_vtime; + + osCreateMesgQueue( &viMessageQ, viMessages, VI_MSG_SIZE ); + osViSetEvent( &viMessageQ, 0, 1 ); + + /* Acquire AI event*/ + osCreateMesgQueue( &aiMessageQ, aiMessages, AI_MSG_SIZE ); + osSetEventMesg( OS_EVENT_AI, &aiMessageQ, (OSMesg *)1 ); + + /* + * Create and start audio DA counter thread + */ + osCreateThread(&daCounterThread, DA_COUNTER_THREAD_ID, daCounterProc, + NULL, daCounterStack + (STACKSIZE/sizeof(u64)), + (OSPri)DA_COUNTER_PRIORITY); + osStartThread(&daCounterThread); + + osRecvMesg(&tkCmdMesgQ, (OSMesg *)&cmd, OS_MESG_BLOCK); + + while (cmd == NULL) { + osSendMesg(&tkResMesgQ, NULL, OS_MESG_BLOCK); + osRecvMesg(&tkCmdMesgQ, (OSMesg *)&cmd, OS_MESG_BLOCK); + } + + tkClockDisable(); + audioproc = (cmd->rewind)(); + + samples_per_sec = cmd->samples_per_sec; + if (samples_per_sec) osAiSetFrequency(samples_per_sec); + + pcmBufferCount = 0; + + aiDAsamples = 0; + aiFIFOsamples = 0; + + audioRingRead = audioRingWrite = 0; + audioRingCount = 0; + + videoRingRead = videoRingWrite = 0; + videoRingCount = 0; + + video_started = 0; + audio_started = 0; + video_done = 0; + audio_done = 0; + pushed_cfb = NULL; + pushed_cfb_statP = NULL; + next_pcmbufno = 0; + pcm_mod_samples = 0; + + osSendMesg(&tkResMesgQ, NULL, OS_MESG_BLOCK); + + present_vtime = osGetTime(); + + while (!video_done || videoRingCount > 0 || pushed_cfb != NULL || + !audio_done || audioRingCount > 0 || aiFIFOsamples > 0) { + u64 present_time; + OSTime last_vtime; + + osRecvMesg(&viMessageQ, NULL, OS_MESG_BLOCK); + + last_vtime = present_vtime; + present_vtime = osGetTime(); + present_time = tkGetTime() + OS_CYCLES_TO_USEC(present_vtime - last_vtime); + + if (pushed_cfb != NULL) { + /* The display has switched from showing_cfb to pushed_cfb (it should have) */ + if (!video_started) { + /* Display of the first frame has begun (the audio will begin from here) */ + video_started = 1; + if (!audio_started && audio_done) { + audio_started = 1; + tkClockStart(); + } + } + + /* Release showing_cfb */ + if (showing_cfb_statP != NULL && showing_cfb != pushed_cfb) { + *showing_cfb_statP &= ~CFB_SHOWING; + } + + /* Update showing_cfb, pushed_cfb */ + showing_cfb = pushed_cfb; + showing_cfb_statP = pushed_cfb_statP; + pushed_cfb = NULL; + pushed_cfb_statP = NULL; + } + + if ( video_started || audioRingCount > 0 || audio_done ) { + while ( videoRingCount ) { + void *next_cfb; + if ( videoRing[videoRingRead].disptime > present_time ) break; + if ( pushed_cfb_statP != NULL ) *pushed_cfb_statP &= ~CFB_SHOWING; + pushed_cfb = videoRing[videoRingRead].vaddr; + pushed_cfb_statP = videoRing[videoRingRead].statP; + *pushed_cfb_statP |= CFB_SHOWING; + osViSwapBuffer( pushed_cfb ); + if ( ++videoRingRead == VIDEO_RING_SIZE ) videoRingRead = 0; + --videoRingCount; + } + } + + if ( video_started ) { + osSetThreadPri( NULL, (OSPri)(DA_COUNTER_PRIORITY + 1) ); + while ( audioRingCount > 0 && aiFIFOsamples == 0 ) { + void *buffer = audioRing[audioRingRead].buf; + u32 length = audioRing[audioRingRead].len; + //!if ( osAiGetStatus() & AI_STATUS_AI_FULL ) break; + if ( osAiSetNextBuffer( buffer, length ) == -1 ) break; + aiFIFOsamples = length >> PCM_BYTES_PER_SAMPLE_SHIFT; + if ( ! audio_started ) { + /* + * Audio playback has begun. + * Here the time count starts to synchronize audio and video + */ + audio_started = 1; + tkClockStart(); + } + if ( ++audioRingRead == AUDIO_RING_BUFFER_SIZE ) audioRingRead = 0; + --audioRingCount; + } + osSetThreadPri( NULL, (OSPri)TIMEKEEPER_PRIORITY ); + } + + if ( ! audio_done && audioRingCount < AUDIO_RING_BUFFER_SIZE && pcmBufferCount < NUM_PCMBUFs ) { + u32 samples; + u32 length; + void *buffer; + s16 *sp, *dp; + int i; + + samples = audioproc( &gAiBuffers[next_pcmbufno][pcm_mod_samples< 0 ) { + ++pcmBufferCount; + + sp = pcmModBuf; + dp = (s16 *)((u8 *)gAiBuffers[next_pcmbufno]); + i = pcm_mod_samples << PCM_CHANNELS_SHIFT; + while ( i-- ) *dp++ = *sp++; + samples += pcm_mod_samples; + samples -= (pcm_mod_samples = samples & (PCM_ALIGN-1)); + length = samples << PCM_BYTES_PER_SAMPLE_SHIFT; + buffer = gAiBuffers[next_pcmbufno]; + osWritebackDCache( buffer, length ); + audioRing[audioRingWrite].buf = buffer; + audioRing[audioRingWrite].len = length; + if ( ++audioRingWrite == AUDIO_RING_BUFFER_SIZE ) audioRingWrite = 0; + ++audioRingCount; + + sp = (s16 *)((u8 *)(gAiBuffers[next_pcmbufno]) + length); + dp = pcmModBuf; + i = pcm_mod_samples << PCM_CHANNELS_SHIFT; + while ( i-- ) *dp++ = *sp++; + + if ( ++next_pcmbufno >= NUM_PCMBUFs ) next_pcmbufno = 0; + } + else audio_done = 1; + } + + //! Push audio + + //! Prepare audio + + if (osRecvMesg(&tkCmdMesgQ, (OSMesg *)&cmd, OS_MESG_NOBLOCK) == 0) { + video_done = 1; + } + } +} + +void createTimekeeper(void) { + osCreateMesgQueue( &tkCmdMesgQ, &tkCmdMesgBuf, 1 ); + osCreateMesgQueue( &tkResMesgQ, &tkResMesgBuf, 1 ); + osCreateThread( &tkThread, TIMEKEEPER_THREAD_ID, timekeeperProc, + NULL, tkStack + (STACKSIZE/sizeof(u64)), + (OSPri)TIMEKEEPER_PRIORITY ); + osStartThread( &tkThread ); +} + +void tkStart( tkRewindProc rewind, u32 samples_per_sec ) { + TKCMD tkcmd; + tkcmd.samples_per_sec = samples_per_sec; + tkcmd.rewind = rewind; + osSendMesg( &tkCmdMesgQ, (OSMesg *)&tkcmd, OS_MESG_BLOCK ); + osRecvMesg( &tkResMesgQ, (OSMesg *)NULL, OS_MESG_BLOCK ); +} + +void tkStop( void ) { + osSendMesg( &tkCmdMesgQ, (OSMesg *)NULL, OS_MESG_BLOCK ); + osRecvMesg( &tkResMesgQ, (OSMesg *)NULL, OS_MESG_BLOCK ); +} + +void tkPushVideoframe( void *vaddr, u32 *statP, u64 disptime ) { + *statP |= CFB_SHOWING; + while ( videoRingCount >= VIDEO_RING_SIZE ) osYieldThread(); + videoRing[videoRingWrite].disptime = disptime; + videoRing[videoRingWrite].vaddr = vaddr; + videoRing[videoRingWrite].statP = statP; + if ( ++videoRingWrite == VIDEO_RING_SIZE ) videoRingWrite = 0; + ++videoRingCount; +} diff --git a/src/game/crash_screen.c b/src/game/crash_screen.c index 3f1a20dd..d161cd24 100644 --- a/src/game/crash_screen.c +++ b/src/game/crash_screen.c @@ -5,8 +5,6 @@ #include "sm64.h" -#if defined(TARGET_N64) && (defined(VERSION_EU) || defined(VERSION_SH)) - #include "printf.h" u8 gCrashScreenCharToGlyph[128] = { @@ -118,11 +116,7 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { if (size > 0) { ptr = buf; -#ifdef VERSION_SH - while (size > 0) { -#else while (*ptr) { -#endif glyph = gCrashScreenCharToGlyph[*ptr & 0x7f]; @@ -130,10 +124,6 @@ void crash_screen_print(s32 x, s32 y, const char *fmt, ...) { crash_screen_draw_glyph(x, y, glyph); } -#ifdef VERSION_SH - size--; -#endif - ptr++; x += 6; } @@ -191,16 +181,10 @@ void draw_crash_screen(OSThread *thread) { cause = 17; } -#ifdef VERSION_SH - osWritebackDCacheAll(); -#endif - crash_screen_draw_rect(25, 20, 270, 25); crash_screen_print(30, 25, "THREAD:%d (%s)", thread->id, gCauseDesc[cause]); crash_screen_print(30, 35, "PC:%08XH SR:%08XH VA:%08XH", tc->pc, tc->sr, tc->badvaddr); -#ifdef VERSION_EU osWritebackDCacheAll(); -#endif crash_screen_sleep(2000); crash_screen_draw_rect(25, 45, 270, 185); crash_screen_print(30, 50, "AT:%08XH V0:%08XH V1:%08XH", (u32) tc->at, (u32) tc->v0, @@ -223,9 +207,7 @@ void draw_crash_screen(OSThread *thread) { (u32) tc->sp); crash_screen_print(30, 140, "S8:%08XH RA:%08XH", (u32) tc->s8, (u32) tc->ra); crash_screen_print_fpcsr(tc->fpcsr); -#ifdef VERSION_EU osWritebackDCacheAll(); -#endif crash_screen_print_float_reg(30, 170, 0, &tc->fp0.f.f_even); crash_screen_print_float_reg(120, 170, 2, &tc->fp2.f.f_even); crash_screen_print_float_reg(210, 170, 4, &tc->fp4.f.f_even); @@ -242,9 +224,7 @@ void draw_crash_screen(OSThread *thread) { crash_screen_print_float_reg(120, 210, 26, &tc->fp26.f.f_even); crash_screen_print_float_reg(210, 210, 28, &tc->fp28.f.f_even); crash_screen_print_float_reg(30, 220, 30, &tc->fp30.f.f_even); -#ifdef VERSION_EU osWritebackDCacheAll(); -#endif osViBlack(FALSE); osViSwapBuffer(gCrashScreen.framebuffer); } @@ -279,37 +259,20 @@ void thread2_crash_screen(UNUSED void *arg) { } void crash_screen_set_framebuffer(u16 *framebuffer, u16 width, u16 height) { -#ifdef VERSION_EU gCrashScreen.framebuffer = framebuffer; -#else - gCrashScreen.framebuffer = (u16 *)((uintptr_t)framebuffer | 0xa0000000); -#endif gCrashScreen.width = width; gCrashScreen.height = height; } void crash_screen_init(void) { -#ifdef VERSION_EU gCrashScreen.framebuffer = (u16 *) (osMemSize | 0x80000000) - SCREEN_WIDTH * SCREEN_HEIGHT; -#else - gCrashScreen.framebuffer = (u16 *) (osMemSize | 0xA0000000) - SCREEN_WIDTH * SCREEN_HEIGHT; -#endif gCrashScreen.width = SCREEN_WIDTH; -#ifdef VERSION_EU gCrashScreen.height = SCREEN_HEIGHT; -#else - gCrashScreen.height = 0x10; -#endif osCreateMesgQueue(&gCrashScreen.mesgQueue, &gCrashScreen.mesg, 1); osCreateThread(&gCrashScreen.thread, 2, thread2_crash_screen, NULL, (u8 *) gCrashScreen.stack + sizeof(gCrashScreen.stack), -#ifdef VERSION_EU OS_PRIORITY_APPMAX -#else - OS_PRIORITY_RMON -#endif ); osStartThread(&gCrashScreen.thread); } -#endif diff --git a/src/game/game_init.c b/src/game/game_init.c index bb14c86a..c2cf82b2 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -59,6 +59,10 @@ struct DemoInput *gCurrDemoInput = NULL; // demo input sequence u16 gDemoInputListID = 0; struct DemoInput gRecordedDemoInput = { 0 }; // possibly removed in EU. TODO: Check + +#include "include/timekeeper.inc.c" +#include "include/hvqm.inc.c" + /** * Initializes the Reality Display Processor (RDP). * This function initializes settings such as texture filtering mode, @@ -606,6 +610,7 @@ void thread5_game_loop(UNUSED void *arg) { #if ENABLE_RUMBLE create_thread_6(); #endif + createHvqmThread(); save_file_load_all(); set_vblank_handler(2, &gGameVblankHandler, &gGameVblankQueue, (OSMesg) 1); @@ -647,11 +652,9 @@ void thread5_game_loop(UNUSED void *arg) { // amount of free space remaining. print_text_fmt_int(180, 20, "BUF %d", gGfxPoolEnd - (u8 *) gDisplayListHead); } - #if 0 if (gPlayer1Controller->buttonPressed & L_TRIG) { osStartThread(&hvqmThread); osRecvMesg(&gDmaMesgQueue, NULL, OS_MESG_BLOCK); } - #endif } } diff --git a/src/game/main.c b/src/game/main.c index 3a4c96ed..654b7bf1 100644 --- a/src/game/main.c +++ b/src/game/main.c @@ -328,11 +328,13 @@ void handle_dp_complete(void) { sCurrentDisplaySPTask->state = SPTASK_STATE_FINISHED_DP; sCurrentDisplaySPTask = NULL; } +extern void crash_screen_init(void); void thread3_main(UNUSED void *arg) { setup_mesg_queues(); alloc_pool(); load_engine_code_segment(); + crash_screen_init(); create_thread(&gSoundThread, 4, thread4_sound, NULL, gThread4Stack + 0x2000, 20); osStartThread(&gSoundThread); @@ -340,7 +342,6 @@ void thread3_main(UNUSED void *arg) { create_thread(&gGameLoopThread, 5, thread5_game_loop, NULL, gThread5Stack + 0x2000, 10); osStartThread(&gGameLoopThread); - //createHvqmThread(); while (TRUE) { OSMesg msg; @@ -458,10 +459,7 @@ void thread1_idle(UNUSED void *arg) { } void main_func(void) { - UNUSED u8 pad[64]; // needed to pad the stack - osInitialize(); - stub_main_1(); create_thread(&gIdleThread, 1, thread1_idle, NULL, gIdleThreadStack + 0x800, 100); osStartThread(&gIdleThread); } diff --git a/src/game/main.h b/src/game/main.h index b50aeb51..bdc88e07 100644 --- a/src/game/main.h +++ b/src/game/main.h @@ -26,7 +26,6 @@ extern OSThread gIdleThread; extern OSThread gMainThread; extern OSThread gGameLoopThread; extern OSThread gSoundThread; -extern OSThread hvqmThread; #if ENABLE_RUMBLE extern OSThread gRumblePakThread;