From 74146596b9230ef1983423ffda3f2d9f96f09206 Mon Sep 17 00:00:00 2001 From: Colin Leroy-Mira Date: Thu, 7 Aug 2025 13:35:50 +0200 Subject: [PATCH 1/4] Optimisations for the feeble computer - Huffman decoding: - we never use mMinCode's high byte, get rid of it - use a dedicated mGetMore bool instead of a special-case mMaxCode 0xFFFF to consume one more byte - decodeNextMCU: branch a bit less selecting the Huff tables - General: - getExtendOffset: use arrays instead of functions and switches - skipVariableMarker: jump to the desired place much faster than left-shifting 8 times every byte we want to skip --- picojpeg.c | 157 +++++++++++++++++++++++++++++------------------------ 1 file changed, 85 insertions(+), 72 deletions(-) diff --git a/picojpeg.c b/picojpeg.c index 2fae2a9..b2559ad 100644 --- a/picojpeg.c +++ b/picojpeg.c @@ -155,9 +155,10 @@ static int16 gLastDC[3]; typedef struct HuffTableT { - uint16 mMinCode[16]; + uint8 mGetMore[16]; + uint8 mMinCode[16]; uint16 mMaxCode[16]; - uint8 mValPtr[16]; + uint8 mValPtr[16]; } HuffTable; // DC - 192 @@ -261,6 +262,24 @@ static PJPG_INLINE void stuffChar(uint8 i) gInBufLeft++; } //------------------------------------------------------------------------------ +static PJPG_INLINE void skipChars(uint16 n) +{ + // skip characters fast + while (n > gInBufLeft) + { + n -= gInBufLeft; + gInBufOfs += gInBufLeft; + gInBufLeft = 0; + getChar(); + n--; + if (!gInBufLeft) + return; + } + + while (n--) + getChar(); +} +//------------------------------------------------------------------------------ static PJPG_INLINE uint8 getOctet(uint8 FFCheck) { uint8 c = getChar(); @@ -283,7 +302,7 @@ static uint16 getBits(uint8 numBits, uint8 FFCheck) { uint8 origBits = numBits; uint16 ret = gBitBuf; - + if (numBits > 8) { numBits -= 8; @@ -345,57 +364,28 @@ static PJPG_INLINE uint8 getBit(void) return ret; } //------------------------------------------------------------------------------ -static uint16 getExtendTest(uint8 i) -{ - switch (i) - { - case 0: return 0; - case 1: return 0x0001; - case 2: return 0x0002; - case 3: return 0x0004; - case 4: return 0x0008; - case 5: return 0x0010; - case 6: return 0x0020; - case 7: return 0x0040; - case 8: return 0x0080; - case 9: return 0x0100; - case 10: return 0x0200; - case 11: return 0x0400; - case 12: return 0x0800; - case 13: return 0x1000; - case 14: return 0x2000; - case 15: return 0x4000; - default: return 0; - } -} +uint16 extendTests[] = { + 0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, + 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000 +}; //------------------------------------------------------------------------------ -static int16 getExtendOffset(uint8 i) -{ - switch (i) - { - case 0: return 0; - case 1: return ((-1)<<1) + 1; - case 2: return ((-1)<<2) + 1; - case 3: return ((-1)<<3) + 1; - case 4: return ((-1)<<4) + 1; - case 5: return ((-1)<<5) + 1; - case 6: return ((-1)<<6) + 1; - case 7: return ((-1)<<7) + 1; - case 8: return ((-1)<<8) + 1; - case 9: return ((-1)<<9) + 1; - case 10: return ((-1)<<10) + 1; - case 11: return ((-1)<<11) + 1; - case 12: return ((-1)<<12) + 1; - case 13: return ((-1)<<13) + 1; - case 14: return ((-1)<<14) + 1; - case 15: return ((-1)<<15) + 1; - default: return 0; - } +uint16 extendOffsets[] = { + ((-1)<<0) + 1, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, ((-1)<<9) + 1, + ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, ((-1)<<13) + 1, ((-1)<<14) + 1, + ((-1)<<15) + 1 }; //------------------------------------------------------------------------------ static PJPG_INLINE int16 huffExtend(uint16 x, uint8 s) { - return ((x < getExtendTest(s)) ? ((int16)x + getExtendOffset(s)) : (int16)x); + if (s < 16) + { + uint16 t = extendTests[s]; + if (t > x) + return (int16)x + extendOffsets[s]; + } + + return (int16)x; } //------------------------------------------------------------------------------ static PJPG_INLINE uint8 huffDecode(const HuffTable* pHuffTable, const uint8* pHuffVal) @@ -409,14 +399,14 @@ static PJPG_INLINE uint8 huffDecode(const HuffTable* pHuffTable, const uint8* pH // more reasonable approach. for ( ; ; ) { - uint16 maxCode; - if (i == 16) return 0; - maxCode = pHuffTable->mMaxCode[i]; - if ((code <= maxCode) && (maxCode != 0xFFFF)) - break; + if (!pHuffTable->mGetMore[i]) + { + if (code <= pHuffTable->mMaxCode[i]) + break; + } i++; code <<= 1; @@ -440,15 +430,11 @@ static void huffCreate(const uint8* pBits, HuffTable* pHuffTable) { uint8 num = pBits[i]; - if (!num) + if (num) { - pHuffTable->mMinCode[i] = 0x0000; - pHuffTable->mMaxCode[i] = 0xFFFF; - pHuffTable->mValPtr[i] = 0; - } - else - { - pHuffTable->mMinCode[i] = code; + // minCode's high byte is never used in huffDecode(), + // we might as well drop it right now. + pHuffTable->mMinCode[i] = (uint8)code; pHuffTable->mMaxCode[i] = code + num - 1; pHuffTable->mValPtr[i] = j; @@ -456,6 +442,8 @@ static void huffCreate(const uint8* pBits, HuffTable* pHuffTable) code = (uint16)(code + num); } + else + pHuffTable->mGetMore[i] = 1; code <<= 1; @@ -653,18 +641,32 @@ static uint8 readSOFMarker(void) static uint8 skipVariableMarker(void) { uint16 left = getBits1(16); + uint16 safeSkip; - if (left < 2) - return PJPG_BAD_VARIABLE_MARKER; + switch (left) + { + case 0: + case 1: + return PJPG_BAD_VARIABLE_MARKER; + case 2: + return 0; + case 3: + getBits1(8); + return 0; + default: + safeSkip = left - 4; + left = 2; + } - left -= 2; + // Avoid shifting gBitBuf for all but the last + // two bytes that we want to skip. + skipChars(safeSkip); - while (left) + while(left--) { - getBits1(8); - left--; + getBits1(8); } - + return 0; } //------------------------------------------------------------------------------ @@ -2140,7 +2142,12 @@ static uint8 decodeNextMCU(void) const int16* pQ = compQuant ? gQuant1 : gQuant0; uint16 r, dc; - uint8 s = huffDecode(compDCTab ? &gHuffTab1 : &gHuffTab0, compDCTab ? gHuffVal1 : gHuffVal0); + uint8 s; + + if (compDCTab) + s = huffDecode(&gHuffTab1, gHuffVal1); + else + s = huffDecode(&gHuffTab0, gHuffVal0); r = 0; numExtraBits = s & 0xF; @@ -2160,7 +2167,10 @@ static uint8 decodeNextMCU(void) // Decode, but throw out the AC coefficients in reduce mode. for (k = 1; k < 64; k++) { - s = huffDecode(compACTab ? &gHuffTab3 : &gHuffTab2, compACTab ? gHuffVal3 : gHuffVal2); + if (compACTab) + s = huffDecode(&gHuffTab3, gHuffVal3); + else + s = huffDecode(&gHuffTab2, gHuffVal2); numExtraBits = s & 0xF; if (numExtraBits) @@ -2202,7 +2212,10 @@ static uint8 decodeNextMCU(void) { uint16 extraBits; - s = huffDecode(compACTab ? &gHuffTab3 : &gHuffTab2, compACTab ? gHuffVal3 : gHuffVal2); + if (compACTab) + s = huffDecode(&gHuffTab3, gHuffVal3); + else + s = huffDecode(&gHuffTab2, gHuffVal2); extraBits = 0; numExtraBits = s & 0xF; From 9fc0bcc49308b3f4aefcdcd4b55d1a732c384974 Mon Sep 17 00:00:00 2001 From: Colin Leroy-Mira Date: Fri, 8 Aug 2025 17:37:46 +0200 Subject: [PATCH 2/4] Drop mMinCode entirely. --- picojpeg.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/picojpeg.c b/picojpeg.c index b2559ad..de7df30 100644 --- a/picojpeg.c +++ b/picojpeg.c @@ -156,7 +156,6 @@ static int16 gLastDC[3]; typedef struct HuffTableT { uint8 mGetMore[16]; - uint8 mMinCode[16]; uint16 mMaxCode[16]; uint8 mValPtr[16]; } HuffTable; @@ -413,8 +412,7 @@ static PJPG_INLINE uint8 huffDecode(const HuffTable* pHuffTable, const uint8* pH code |= getBit(); } - j = pHuffTable->mValPtr[i]; - j = (uint8)(j + (code - pHuffTable->mMinCode[i])); + j = (uint8)(pHuffTable->mValPtr[i] + code); return pHuffVal[j]; } @@ -432,11 +430,8 @@ static void huffCreate(const uint8* pBits, HuffTable* pHuffTable) if (num) { - // minCode's high byte is never used in huffDecode(), - // we might as well drop it right now. - pHuffTable->mMinCode[i] = (uint8)code; pHuffTable->mMaxCode[i] = code + num - 1; - pHuffTable->mValPtr[i] = j; + pHuffTable->mValPtr[i] = j - (uint8)code; j = (uint8)(j + num); From 3870644e7ec51927e5d6dc75a0b704e99690f53a Mon Sep 17 00:00:00 2001 From: Colin Leroy-Mira Date: Sun, 10 Aug 2025 10:22:16 +0200 Subject: [PATCH 3/4] Don't decrement mMaxCode, and check for < instead of <= This is easier wrt the carry on 6502 --- picojpeg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/picojpeg.c b/picojpeg.c index de7df30..03d4911 100644 --- a/picojpeg.c +++ b/picojpeg.c @@ -403,7 +403,7 @@ static PJPG_INLINE uint8 huffDecode(const HuffTable* pHuffTable, const uint8* pH if (!pHuffTable->mGetMore[i]) { - if (code <= pHuffTable->mMaxCode[i]) + if (code < pHuffTable->mMaxCode[i]) break; } @@ -430,7 +430,7 @@ static void huffCreate(const uint8* pBits, HuffTable* pHuffTable) if (num) { - pHuffTable->mMaxCode[i] = code + num - 1; + pHuffTable->mMaxCode[i] = code + num; pHuffTable->mValPtr[i] = j - (uint8)code; j = (uint8)(j + num); From 6c601ef2c83bf774bd5ed039cd0f2894d9150d4c Mon Sep 17 00:00:00 2001 From: Colin Leroy-Mira Date: Sun, 10 Aug 2025 23:47:01 +0200 Subject: [PATCH 4/4] Preinit ZAG coeffs[1-63] to 0 Avoids extra array accesses to ZAG[k++] when skipping, and extra loops --- picojpeg.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/picojpeg.c b/picojpeg.c index 03d4911..6daa63d 100644 --- a/picojpeg.c +++ b/picojpeg.c @@ -2155,6 +2155,11 @@ static uint8 decodeNextMCU(void) gCoeffBuf[0] = dc * pQ[0]; + /* Pre-zero the other coeffs */ + for (k = 1; k < 64; k++) { + gCoeffBuf[k] = 0; + } + compACTab = gCompACTab[componentID]; if (gReduce) @@ -2229,11 +2234,7 @@ static uint8 decodeNextMCU(void) if ((k + r) > 63) return PJPG_DECODE_ERROR; - while (r) - { - gCoeffBuf[ZAG[k++]] = 0; - r--; - } + k = (uint8)(k + r); } ac = huffExtend(extraBits, s); @@ -2247,18 +2248,12 @@ static uint8 decodeNextMCU(void) if ((k + 16) > 64) return PJPG_DECODE_ERROR; - for (r = 16; r > 0; r--) - gCoeffBuf[ZAG[k++]] = 0; - - k--; // - 1 because the loop counter is k + k += (16 - 1); // - 1 because the loop counter is k } else break; } } - - while (k < 64) - gCoeffBuf[ZAG[k++]] = 0; transformBlock(mcuBlock); }