#include "z64jpeg.h" #include "stdbool.h" #include "scheduler.h" #include "sys_ucode.h" #include "macros.h" #define MARKER_ESCAPE 0x00 #define MARKER_SOI 0xD8 #define MARKER_SOF 0xC0 #define MARKER_DHT 0xC4 #define MARKER_DQT 0xDB #define MARKER_DRI 0xDD #define MARKER_SOS 0xDA #define MARKER_APP0 0xE0 #define MARKER_APP1 0xE1 #define MARKER_APP2 0xE2 #define MARKER_COM 0xFE #define MARKER_EOI 0xD9 extern u64 njpgdspMainTextStart[]; extern u64 njpgdspMainDataStart[]; /** * Configures and schedules a JPEG decoder task and waits for it to finish. */ void Jpeg_ScheduleDecoderTask(JpegContext* jpegCtx) { static OSTask_t sJpegTask = { M_NJPEGTASK, // type 0, // flags NULL, // ucode_boot 0, // ucode_boot_size njpgdspMainTextStart, // ucode SP_UCODE_SIZE, // ucode_size njpgdspMainDataStart, // ucode_data SP_UCODE_DATA_SIZE, // ucode_data_size NULL, // dram_stack 0, // dram_stack_size NULL, // output_buff NULL, // output_buff_size NULL, // data_ptr sizeof(JpegTaskData), // data_size NULL, // yield_data_ptr sizeof(jpegCtx->workBuf->yieldData), // yield_data_size }; JpegWork* workBuf = jpegCtx->workBuf; s32 pad[2]; workBuf->taskData.address = &workBuf->data; workBuf->taskData.mode = jpegCtx->mode; workBuf->taskData.mbCount = 4; workBuf->taskData.qTableYPtr = &workBuf->qTableY; workBuf->taskData.qTableUPtr = &workBuf->qTableU; workBuf->taskData.qTableVPtr = &workBuf->qTableV; sJpegTask.flags = 0; sJpegTask.ucode_boot = SysUcode_GetUCodeBoot(); sJpegTask.ucode_boot_size = SysUcode_GetUCodeBootSize(); sJpegTask.yield_data_ptr = (u64*)&workBuf->yieldData; sJpegTask.data_ptr = (u64*)&workBuf->taskData; jpegCtx->scTask.next = NULL; jpegCtx->scTask.flags = OS_SC_NEEDS_RSP; jpegCtx->scTask.msgQ = &jpegCtx->mq; jpegCtx->scTask.msg = NULL; jpegCtx->scTask.framebuffer = NULL; jpegCtx->scTask.list.t = sJpegTask; osSendMesg(&gScheduler.cmdQueue, (OSMesg*)&jpegCtx->scTask, OS_MESG_BLOCK); Sched_SendNotifyMsg(&gScheduler); // osScKickEntryMsg osRecvMesg(&jpegCtx->mq, NULL, OS_MESG_BLOCK); } /** * Copies a 16x16 block of decoded image data to the Z-buffer. */ void Jpeg_CopyToZbuffer(u16* src, u16* zbuffer, s32 x, s32 y) { u16* dst = zbuffer + (((y * SCREEN_WIDTH) + x) * 16); s32 i; for (i = 0; i < 16; i++) { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; dst[4] = src[4]; dst[5] = src[5]; dst[6] = src[6]; dst[7] = src[7]; dst[8] = src[8]; dst[9] = src[9]; dst[10] = src[10]; dst[11] = src[11]; dst[12] = src[12]; dst[13] = src[13]; dst[14] = src[14]; dst[15] = src[15]; src += 16; dst += SCREEN_WIDTH; } } /** * Reads an u16 from a possibly unaligned address in memory. * * Replaces unaligned 16-bit reads with a pair of aligned reads, allowing for reading the possibly * unaligned values in JPEG header files. */ u16 Jpeg_GetUnalignedU16(u8* ptr) { if (((uintptr_t)ptr & 1) == 0) { // Read the value normally if it's aligned to a 16-bit address. return *(u16*)ptr; } else { // Read unaligned values using two separate aligned memory accesses when it's not. return *(u16*)(ptr - 1) << 8 | (*(u16*)(ptr + 1) >> 8); } } /** * Parses the markers in the JPEG file, storing information such as the pointer to the image data * in `jpegCtx` for later processing. */ void Jpeg_ParseMarkers(u8* ptr, JpegContext* jpegCtx) { u32 exit = false; jpegCtx->dqtCount = 0; jpegCtx->dhtCount = 0; while (true) { if (exit) { break; } // 0xFF indicates the start of a JPEG marker, so look for the next. if (*ptr++ == 0xFF) { switch (*ptr++) { case MARKER_ESCAPE: { // Compressed value 0xFF is stored as 0xFF00 to escape it, so ignore it. break; } case MARKER_SOI: { // Start of Image break; } case MARKER_APP0: { // Application marker for JFIF ptr += Jpeg_GetUnalignedU16(ptr); break; } case MARKER_APP1: { // Application marker for EXIF ptr += Jpeg_GetUnalignedU16(ptr); break; } case MARKER_APP2: { ptr += Jpeg_GetUnalignedU16(ptr); break; } case MARKER_DQT: { // Define Quantization Table, stored for later processing jpegCtx->dqtPtr[jpegCtx->dqtCount++] = ptr + 2; ptr += Jpeg_GetUnalignedU16(ptr); break; } case MARKER_DHT: { // Define Huffman Table, stored for later processing jpegCtx->dhtPtr[jpegCtx->dhtCount++] = ptr + 2; ptr += Jpeg_GetUnalignedU16(ptr); break; } case MARKER_DRI: { // Define Restart Interval ptr += Jpeg_GetUnalignedU16(ptr); break; } case MARKER_SOF: { // Start of Frame, stores important metadata of the image. // Only used for extracting the sampling factors (jpegCtx->mode). if (ptr[9] == 0x21) { // component Y : V0 == 1 jpegCtx->mode = 0; } else if (ptr[9] == 0x22) { // component Y : V0 == 2 jpegCtx->mode = 2; } ptr += Jpeg_GetUnalignedU16(ptr); break; } case MARKER_SOS: { // Start of Scan marker, indicates the start of the image data. ptr += Jpeg_GetUnalignedU16(ptr); jpegCtx->imageData = ptr; break; } case MARKER_EOI: { // End of Image exit = true; break; } default: { ptr += Jpeg_GetUnalignedU16(ptr); break; } } } } } s32 Jpeg_Decode(void* data, void* zbuffer, void* work, u32 workSize) { s32 y; s32 x; s32 j; s32 i; JpegContext jpegCtx; JpegHuffmanTable hTables[4]; JpegDecoder decoder; JpegDecoderState state; JpegWork* workBuff = work; if (workSize < sizeof(JpegWork)) { return -1; } osCreateMesgQueue(&jpegCtx.mq, &jpegCtx.msg, 1); Sched_FlushTaskQueue(); jpegCtx.workBuf = workBuff; Jpeg_ParseMarkers(data, &jpegCtx); switch (jpegCtx.dqtCount) { case 1: JpegUtils_ProcessQuantizationTable(jpegCtx.dqtPtr[0], &workBuff->qTableY, 3); break; case 2: JpegUtils_ProcessQuantizationTable(jpegCtx.dqtPtr[0], &workBuff->qTableY, 1); JpegUtils_ProcessQuantizationTable(jpegCtx.dqtPtr[1], &workBuff->qTableU, 1); JpegUtils_ProcessQuantizationTable(jpegCtx.dqtPtr[1], &workBuff->qTableV, 1); break; case 3: JpegUtils_ProcessQuantizationTable(jpegCtx.dqtPtr[0], &workBuff->qTableY, 1); JpegUtils_ProcessQuantizationTable(jpegCtx.dqtPtr[1], &workBuff->qTableU, 1); JpegUtils_ProcessQuantizationTable(jpegCtx.dqtPtr[2], &workBuff->qTableV, 1); break; default: return -1; } switch (jpegCtx.dhtCount) { case 1: if (JpegUtils_ProcessHuffmanTable(jpegCtx.dhtPtr[0], &hTables[0], workBuff->codesLengths, workBuff->codes, 4)) { return -1; } break; case 4: // This chained if-else has printfs inside it on debug if (JpegUtils_ProcessHuffmanTable(jpegCtx.dhtPtr[0], &hTables[0], workBuff->codesLengths, workBuff->codes, 1)) { } else if (JpegUtils_ProcessHuffmanTable(jpegCtx.dhtPtr[1], &hTables[1], workBuff->codesLengths, workBuff->codes, 1)) { } else if (JpegUtils_ProcessHuffmanTable(jpegCtx.dhtPtr[2], &hTables[2], workBuff->codesLengths, workBuff->codes, 1)) { } else if (!JpegUtils_ProcessHuffmanTable(jpegCtx.dhtPtr[3], &hTables[3], workBuff->codesLengths, workBuff->codes, 1)) { break; } return -1; default: return -1; } decoder.imageData = jpegCtx.imageData; decoder.mode = jpegCtx.mode; decoder.unk_05 = 2; decoder.hTablePtrs[0] = &hTables[0]; decoder.hTablePtrs[1] = &hTables[1]; decoder.hTablePtrs[2] = &hTables[2]; decoder.hTablePtrs[3] = &hTables[3]; decoder.unk_18 = 0; x = y = 0; for (i = 0; i < 300; i += 4) { if (!JpegDecoder_Decode(&decoder, (u16*)workBuff->data, 4, (i != 0), &state)) { Jpeg_ScheduleDecoderTask(&jpegCtx); for (j = 0; j < 4; j++) { Jpeg_CopyToZbuffer(workBuff->data[j], zbuffer, x, y); x++; if (x >= 20) { x = 0; y++; } } } } return 0; }