@@ -227,26 +227,71 @@ static unsigned int hap_texture_format_identifier_for_format_constant(unsigned i
227227 }
228228}
229229
230- unsigned long HapMaxEncodedLength (unsigned long inputBytes )
230+ // Returns the length of a decode instructions container of chunk_count chunks
231+ // not including the section header
232+ static size_t hap_decode_instructions_length (unsigned int chunk_count )
231233{
232234 /*
233- Actually our max encoded length is inputBytes + 8U but snappy may produce longer output
234- and the only way we can find out is by trying with a suitably-sized buffer
235+ Calculate the size of our Decode Instructions Section
236+ = Second-Stage Compressor Table + Chunk Size Table + headers for both sections
237+ = chunk_count + (4 * chunk_count) + 4 + 4
235238 */
236- unsigned long compressedLength = snappy_max_compressed_length (inputBytes );
237- if (compressedLength < inputBytes ) compressedLength = inputBytes ;
238- return compressedLength + 8U ;
239+ size_t length = (5 * chunk_count ) + 8 ;
240+
241+ return length ;
242+ }
243+
244+ static unsigned int hap_limited_chunk_count_for_frame (size_t input_bytes , unsigned int texture_format , unsigned int chunk_count )
245+ {
246+ // This is a hard limit due to the 4-byte headers we use for the decode instruction container
247+ // (0xFFFFFF == count + (4 x count) + 20)
248+ if (chunk_count > 3355431 )
249+ {
250+ chunk_count = 3355431 ;
251+ }
252+ // Divide frame equally on DXT block boundries (8 or 16 bytes)
253+ unsigned long dxt_block_count = input_bytes / (texture_format == HapTextureFormat_RGB_DXT1 ? 8 : 16 );
254+ while (dxt_block_count % chunk_count != 0 ) {
255+ chunk_count -- ;
256+ }
257+
258+ return chunk_count ;
259+ }
260+
261+ static size_t hap_max_encoded_length (size_t input_bytes , unsigned int texture_format , unsigned int compressor , unsigned int chunk_count )
262+ {
263+ size_t decode_instructions_length , max_compressed_length ;
264+
265+ chunk_count = hap_limited_chunk_count_for_frame (input_bytes , texture_format , chunk_count );
266+
267+ decode_instructions_length = hap_decode_instructions_length (chunk_count );
268+
269+ if (compressor == HapCompressorSnappy )
270+ {
271+ size_t chunk_size = input_bytes / chunk_count ;
272+ max_compressed_length = snappy_max_compressed_length (chunk_size ) * chunk_count ;
273+ }
274+ else
275+ {
276+ max_compressed_length = input_bytes ;
277+ }
278+
279+ // top section header + decode instructions section header + decode instructions + compressed data
280+ return max_compressed_length + 8U + decode_instructions_length + 4U ;
281+ }
282+
283+ unsigned long HapMaxEncodedLength (unsigned long inputBytes , unsigned int textureFormat , unsigned int chunkCount )
284+ {
285+ // Assume snappy, the worst case
286+ return hap_max_encoded_length (inputBytes , textureFormat , HapCompressorSnappy , chunkCount );
239287}
240288
241289unsigned int HapEncode (const void * inputBuffer , unsigned long inputBufferBytes , unsigned int textureFormat ,
242- unsigned int compressor , void * outputBuffer , unsigned long outputBufferBytes ,
243- unsigned long * outputBufferBytesUsed )
290+ unsigned int compressor , unsigned int chunkCount , void * outputBuffer ,
291+ unsigned long outputBufferBytes , unsigned long * outputBufferBytesUsed )
244292{
245- size_t maxCompressedLength ;
246- size_t maxOutputBufferLength ;
247- size_t headerLength ;
248- void * compressedStart ;
249- size_t storedLength ;
293+ size_t top_section_header_length ;
294+ size_t top_section_length ;
250295 unsigned int storedCompressor ;
251296 unsigned int storedFormat ;
252297
@@ -262,16 +307,14 @@ unsigned int HapEncode(const void *inputBuffer, unsigned long inputBufferBytes,
262307 || (compressor != HapCompressorNone
263308 && compressor != HapCompressorSnappy
264309 )
310+ || outputBuffer == NULL
265311 )
266312 {
267313 return HapResult_Bad_Arguments ;
268314 }
269-
270- maxCompressedLength = compressor == HapCompressorSnappy ? snappy_max_compressed_length (inputBufferBytes ) : inputBufferBytes ;
271- if (maxCompressedLength < inputBufferBytes )
315+ else if (outputBufferBytes < hap_max_encoded_length (inputBufferBytes , textureFormat , compressor , chunkCount ))
272316 {
273- // Sanity check in case a future Snappy promises to always compress
274- maxCompressedLength = inputBufferBytes ;
317+ return HapResult_Buffer_Too_Small ;
275318 }
276319
277320 /*
@@ -283,56 +326,110 @@ unsigned int HapEncode(const void *inputBuffer, unsigned long inputBufferBytes,
283326 */
284327 if (inputBufferBytes > kHapUInt24Max )
285328 {
286- headerLength = 8U ;
329+ top_section_header_length = 8U ;
287330 }
288331 else
289332 {
290- headerLength = 4U ;
291- }
292-
293- maxOutputBufferLength = maxCompressedLength + headerLength ;
294- if (outputBufferBytes < maxOutputBufferLength
295- || outputBuffer == NULL )
296- {
297- return HapResult_Buffer_Too_Small ;
333+ top_section_header_length = 4U ;
298334 }
299- compressedStart = ((uint8_t * )outputBuffer ) + headerLength ;
300-
335+
301336 if (compressor == HapCompressorSnappy )
302337 {
303- snappy_status result ;
304- storedLength = outputBufferBytes ;
305- result = snappy_compress ((const char * )inputBuffer , inputBufferBytes , (char * )compressedStart , & storedLength );
306- if (result != SNAPPY_OK )
338+ /*
339+ We attempt to chunk as requested, and if resulting frame is larger than it is uncompressed then
340+ store frame uncompressed
341+ */
342+
343+ size_t decode_instructions_length ;
344+ size_t chunk_size , compress_buffer_remaining ;
345+ uint8_t * second_stage_compressor_table ;
346+ void * chunk_size_table ;
347+ char * compressed_data ;
348+ unsigned int i ;
349+
350+ chunkCount = hap_limited_chunk_count_for_frame (inputBufferBytes , textureFormat , chunkCount );
351+ decode_instructions_length = hap_decode_instructions_length (chunkCount );
352+
353+ // Check we have space for the Decode Instructions Container
354+ if ((inputBufferBytes + decode_instructions_length + 4 ) > kHapUInt24Max )
307355 {
308- return HapResult_Internal_Error ;
356+ top_section_header_length = 8U ;
357+ }
358+
359+ second_stage_compressor_table = ((uint8_t * )outputBuffer ) + top_section_header_length + 4 + 4 ;
360+ chunk_size_table = ((uint8_t * )outputBuffer ) + top_section_header_length + 4 + 4 + chunkCount + 4 ;
361+
362+ chunk_size = inputBufferBytes / chunkCount ;
363+
364+ // write the Decode Instructions section header
365+ hap_write_section_header (((uint8_t * )outputBuffer ) + top_section_header_length , 4U , decode_instructions_length , kHapSectionDecodeInstructionsContainer );
366+ // write the Second Stage Compressor Table section header
367+ hap_write_section_header (((uint8_t * )outputBuffer ) + top_section_header_length + 4U , 4U , chunkCount , kHapSectionChunkSecondStageCompressorTable );
368+ // write the Chunk Size Table section header
369+ hap_write_section_header (((uint8_t * )outputBuffer ) + top_section_header_length + 4U + 4U + chunkCount , 4U , chunkCount * 4U , kHapSectionChunkSizeTable );
370+
371+ compressed_data = (char * )(((uint8_t * )outputBuffer ) + top_section_header_length + 4 + decode_instructions_length );
372+
373+ compress_buffer_remaining = outputBufferBytes - top_section_header_length - 4 - decode_instructions_length ;
374+
375+ top_section_length = 4 + decode_instructions_length ;
376+
377+ for (i = 0 ; i < chunkCount ; i ++ ) {
378+ size_t chunk_packed_length = compress_buffer_remaining ;
379+ const char * chunk_input_start = (const char * )(((uint8_t * )inputBuffer ) + (chunk_size * i ));
380+ if (compressor == HapCompressorSnappy )
381+ {
382+ snappy_status result = snappy_compress (chunk_input_start , chunk_size , (char * )compressed_data , & chunk_packed_length );
383+ if (result != SNAPPY_OK )
384+ {
385+ return HapResult_Internal_Error ;
386+ }
387+ }
388+
389+ if (compressor == HapCompressorNone || chunk_packed_length >= chunk_size )
390+ {
391+ // store the chunk uncompressed
392+ memcpy (compressed_data , chunk_input_start , chunk_size );
393+ chunk_packed_length = chunk_size ;
394+ second_stage_compressor_table [i ] = kHapCompressorNone ;
395+ }
396+ else
397+ {
398+ // ie we used snappy and saved some space
399+ second_stage_compressor_table [i ] = kHapCompressorSnappy ;
400+ }
401+ hap_write_4_byte_uint (((uint8_t * )chunk_size_table ) + (i * 4 ), chunk_packed_length );
402+ compressed_data += chunk_packed_length ;
403+ top_section_length += chunk_packed_length ;
404+ compress_buffer_remaining -= chunk_packed_length ;
405+ }
406+
407+ if (top_section_length < inputBufferBytes + top_section_header_length )
408+ {
409+ // use the complex storage because snappy compression saved space
410+ storedCompressor = kHapCompressorComplex ;
411+ }
412+ else
413+ {
414+ // Signal to store the frame uncompressed
415+ compressor = HapCompressorNone ;
309416 }
310- storedCompressor = kHapCompressorSnappy ;
311- }
312- else
313- {
314- // HapCompressorNone
315- // Setting storedLength to 0 causes the frame to be used uncompressed
316- storedLength = 0 ;
317417 }
318-
319- /*
320- If our "compressed" frame is no smaller than our input frame then store the input uncompressed.
321- */
322- if (storedLength == 0 || storedLength >= inputBufferBytes )
418+
419+ if (compressor == HapCompressorNone )
323420 {
324- memcpy (compressedStart , inputBuffer , inputBufferBytes );
325- storedLength = inputBufferBytes ;
421+ memcpy ((( uint8_t * ) outputBuffer ) + top_section_header_length , inputBuffer , inputBufferBytes );
422+ top_section_length = inputBufferBytes ;
326423 storedCompressor = kHapCompressorNone ;
327424 }
328425
329426 storedFormat = hap_texture_format_identifier_for_format_constant (textureFormat );
330427
331- hap_write_section_header (outputBuffer , headerLength , storedLength , hap_4_bit_packed_byte (storedCompressor , storedFormat ));
428+ hap_write_section_header (outputBuffer , top_section_header_length , top_section_length , hap_4_bit_packed_byte (storedCompressor , storedFormat ));
332429
333430 if (outputBufferBytesUsed != NULL )
334431 {
335- * outputBufferBytesUsed = storedLength + headerLength ;
432+ * outputBufferBytesUsed = top_section_length + top_section_header_length ;
336433 }
337434
338435 return HapResult_No_Error ;
@@ -591,7 +688,17 @@ unsigned int HapDecode(const void *inputBuffer, unsigned long inputBufferBytes,
591688 */
592689 bytesUsed = running_uncompressed_chunk_size ;
593690
594- callback ((HapDecodeWorkFunction )hap_decode_chunk , chunk_info , chunk_count , info );
691+ if (chunk_count == 1 )
692+ {
693+ /*
694+ We don't invoke the callback for one chunk, just decode it directly
695+ */
696+ hap_decode_chunk (chunk_info , 0 );
697+ }
698+ else
699+ {
700+ callback ((HapDecodeWorkFunction )hap_decode_chunk , chunk_info , chunk_count , info );
701+ }
595702
596703 /*
597704 Check to see if we encountered any errors and report one of them
0 commit comments