1
0
Fork 1
mirror of https://git.s-ol.nu/glsl-view synced 2025-07-29 01:05:08 +02:00
zig-glsl-view/lib/hap.c
s-ol cf3475eab4 implement HAP decoding straight to compressed GPU texture
Supports HAP and HAP Alpha (Hap1/Hap5) formats
2025-03-10 12:44:48 +01:00

1188 lines
41 KiB
C

/*
hap.c
Copyright (c) 2011-2013, Tom Butterworth and Vidvox LLC. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "hap.h"
#include <stdlib.h>
#include <stdint.h>
#include <string.h> // For memcpy for uncompressed frames
#include "snappy-c.h"
#define kHapUInt24Max 0x00FFFFFF
/*
Hap Constants
First four bits represent the compressor
Second four bits represent the format
*/
#define kHapCompressorNone 0xA
#define kHapCompressorSnappy 0xB
#define kHapCompressorComplex 0xC
#define kHapFormatRGBDXT1 0xB
#define kHapFormatRGBADXT5 0xE
#define kHapFormatYCoCgDXT5 0xF
#define kHapFormatARGTC1 0x1
#define kHapFormatRGBABPTC 0xC
#define kHapFormatRGBBPTCUF 0x2
#define kHapFormatRGBBPTCSF 0x3
/*
Packed byte values for Hap
Format Compressor Byte Code
----------------------------------------------------
RGB_DXT1 None 0xAB
RGB_DXT1 Snappy 0xBB
RGB_DXT1 Complex 0xCB
RGBA_DXT5 None 0xAE
RGBA_DXT5 Snappy 0xBE
RGBA_DXT5 Complex 0xCE
YCoCg_DXT5 None 0xAF
YCoCg_DXT5 Snappy 0xBF
YCoCg_DXT5 Complex 0xCF
A_RGTC1 None 0xA1
A_RGTC1 Snappy 0xB1
A_RGTC1 Complex 0xC1
RGBA_BPTC_UNORM None 0xAC
RGBA_BPTC_UNORM Snappy 0xBC
RGBA_BPTC_UNORM Complex 0xCC
RGB_BPTC_UNSIGNED_FLOAT None 0xA2
RGB_BPTC_UNSIGNED_FLOAT Snappy 0xB2
RGB_BPTC_UNSIGNED_FLOAT Complex 0xC2
RGB_BPTC_SIGNED_FLOAT None 0xA3
RGB_BPTC_SIGNED_FLOAT Snappy 0xB3
RGB_BPTC_SIGNED_FLOAT Complex 0xC3
*/
/*
Hap Frame Section Types
*/
#define kHapSectionMultipleImages 0x0D
#define kHapSectionDecodeInstructionsContainer 0x01
#define kHapSectionChunkSecondStageCompressorTable 0x02
#define kHapSectionChunkSizeTable 0x03
#define kHapSectionChunkOffsetTable 0x04
/*
To decode we use a struct to store details of each chunk
*/
typedef struct HapChunkDecodeInfo {
unsigned int result;
unsigned int compressor;
const char *compressed_chunk_data;
size_t compressed_chunk_size;
char *uncompressed_chunk_data;
size_t uncompressed_chunk_size;
} HapChunkDecodeInfo;
// TODO: rename the defines we use for codes used in stored frames
// to better differentiate them from the enums used for the API
// These read and write little-endian values on big or little-endian architectures
static unsigned int hap_read_3_byte_uint(const void *buffer)
{
return (*(uint8_t *)buffer) + ((*(((uint8_t *)buffer) + 1)) << 8) + ((*(((uint8_t *)buffer) + 2)) << 16);
}
static void hap_write_3_byte_uint(void *buffer, unsigned int value)
{
*(uint8_t *)buffer = value & 0xFF;
*(((uint8_t *)buffer) + 1) = (value >> 8) & 0xFF;
*(((uint8_t *)buffer) + 2) = (value >> 16) & 0xFF;
}
static unsigned int hap_read_4_byte_uint(const void *buffer)
{
return (*(uint8_t *)buffer) + ((*(((uint8_t *)buffer) + 1)) << 8) + ((*(((uint8_t *)buffer) + 2)) << 16) + ((*(((uint8_t *)buffer) + 3)) << 24);
}
static void hap_write_4_byte_uint(const void *buffer, unsigned int value)
{
*(uint8_t *)buffer = value & 0xFF;
*(((uint8_t *)buffer) + 1) = (value >> 8) & 0xFF;
*(((uint8_t *)buffer) + 2) = (value >> 16) & 0xFF;
*(((uint8_t *)buffer) + 3) = (value >> 24) & 0xFF;
}
#define hap_top_4_bits(x) (((x) & 0xF0) >> 4)
#define hap_bottom_4_bits(x) ((x) & 0x0F)
#define hap_4_bit_packed_byte(top_bits, bottom_bits) (((top_bits) << 4) | ((bottom_bits) & 0x0F))
static int hap_read_section_header(const void *buffer, uint32_t buffer_length, uint32_t *out_header_length, uint32_t *out_section_length, unsigned int *out_section_type)
{
/*
Verify buffer is big enough to contain a four-byte header
*/
if (buffer_length < 4U)
{
return HapResult_Bad_Frame;
}
/*
The first three bytes are the length of the section (not including the header) or zero
if the length is stored in the last four bytes of an eight-byte header
*/
*out_section_length = hap_read_3_byte_uint(buffer);
/*
If the first three bytes are zero, the size is in the following four bytes
*/
if (*out_section_length == 0U)
{
/*
Verify buffer is big enough to contain an eight-byte header
*/
if (buffer_length < 8U)
{
return HapResult_Bad_Frame;
}
*out_section_length = hap_read_4_byte_uint(((uint8_t *)buffer) + 4U);
*out_header_length = 8U;
}
else
{
*out_header_length = 4U;
}
/*
The fourth byte stores the section type
*/
*out_section_type = *(((uint8_t *)buffer) + 3U);
/*
Verify the section does not extend beyond the buffer
*/
if (*out_header_length + *out_section_length > buffer_length)
{
return HapResult_Bad_Frame;
}
return HapResult_No_Error;
}
static void hap_write_section_header(void *buffer, size_t header_length, uint32_t section_length, unsigned int section_type)
{
/*
The first three bytes are the length of the section (not including the header) or zero
if using an eight-byte header
*/
if (header_length == 4U)
{
hap_write_3_byte_uint(buffer, (unsigned int)section_length);
}
else
{
/*
For an eight-byte header, the length is in the last four bytes
*/
hap_write_3_byte_uint(buffer, 0U);
hap_write_4_byte_uint(((uint8_t *)buffer) + 4U, section_length);
}
/*
The fourth byte stores the section type
*/
*(((uint8_t *)buffer) + 3) = section_type;
}
// Returns an API texture format constant or 0 if not recognised
static unsigned int hap_texture_format_constant_for_format_identifier(unsigned int identifier)
{
switch (identifier)
{
case kHapFormatRGBDXT1:
return HapTextureFormat_RGB_DXT1;
case kHapFormatRGBADXT5:
return HapTextureFormat_RGBA_DXT5;
case kHapFormatYCoCgDXT5:
return HapTextureFormat_YCoCg_DXT5;
case kHapFormatARGTC1:
return HapTextureFormat_A_RGTC1;
case kHapFormatRGBABPTC:
return HapTextureFormat_RGBA_BPTC_UNORM;
case kHapFormatRGBBPTCUF:
return HapTextureFormat_RGB_BPTC_UNSIGNED_FLOAT;
case kHapFormatRGBBPTCSF:
return HapTextureFormat_RGB_BPTC_SIGNED_FLOAT;
default:
return 0;
}
}
// Returns a frame identifier or 0 if not recognised
static unsigned int hap_texture_format_identifier_for_format_constant(unsigned int constant)
{
switch (constant)
{
case HapTextureFormat_RGB_DXT1:
return kHapFormatRGBDXT1;
case HapTextureFormat_RGBA_DXT5:
return kHapFormatRGBADXT5;
case HapTextureFormat_YCoCg_DXT5:
return kHapFormatYCoCgDXT5;
case HapTextureFormat_A_RGTC1:
return kHapFormatARGTC1;
case HapTextureFormat_RGBA_BPTC_UNORM:
return kHapFormatRGBABPTC;
case HapTextureFormat_RGB_BPTC_UNSIGNED_FLOAT:
return kHapFormatRGBBPTCUF;
case HapTextureFormat_RGB_BPTC_SIGNED_FLOAT:
return kHapFormatRGBBPTCSF;
default:
return 0;
}
}
// Returns the length of a decode instructions container of chunk_count chunks
// not including the section header
static size_t hap_decode_instructions_length(unsigned int chunk_count)
{
/*
Calculate the size of our Decode Instructions Section
= Second-Stage Compressor Table + Chunk Size Table + headers for both sections
= chunk_count + (4 * chunk_count) + 4 + 4
*/
size_t length = (5 * chunk_count) + 8;
return length;
}
static unsigned int hap_limited_chunk_count_for_frame(size_t input_bytes, unsigned int texture_format, unsigned int chunk_count)
{
// This is a hard limit due to the 4-byte headers we use for the decode instruction container
// (0xFFFFFF == count + (4 x count) + 20)
if (chunk_count > 3355431)
{
chunk_count = 3355431;
}
// Divide frame equally on DXT block boundries (8 or 16 bytes)
unsigned long dxt_block_count;
switch (texture_format) {
case HapTextureFormat_RGB_DXT1:
case HapTextureFormat_A_RGTC1:
dxt_block_count = input_bytes / 8;
break;
default:
dxt_block_count = input_bytes / 16;
}
while (dxt_block_count % chunk_count != 0) {
chunk_count--;
}
return chunk_count;
}
static size_t hap_max_encoded_length(size_t input_bytes, unsigned int texture_format, unsigned int compressor, unsigned int chunk_count)
{
size_t decode_instructions_length, max_compressed_length;
chunk_count = hap_limited_chunk_count_for_frame(input_bytes, texture_format, chunk_count);
decode_instructions_length = hap_decode_instructions_length(chunk_count);
if (compressor == HapCompressorSnappy)
{
size_t chunk_size = input_bytes / chunk_count;
max_compressed_length = snappy_max_compressed_length(chunk_size) * chunk_count;
}
else
{
max_compressed_length = input_bytes;
}
// top section header + decode instructions section header + decode instructions + compressed data
return max_compressed_length + 8U + decode_instructions_length + 4U;
}
unsigned long HapMaxEncodedLength(unsigned int count,
unsigned long *inputBytes,
unsigned int *textureFormats,
unsigned int *chunkCounts)
{
// Start with the length of a multiple-image section header
unsigned long total_length = 8;
// Return 0 for bad arguments
if (count == 0 || count > 2
|| inputBytes == NULL
|| textureFormats == NULL
|| chunkCounts == NULL)
{
return 0;
}
for (int i = 0; i < count; i++)
{
if (chunkCounts[i] == 0)
{
return 0;
}
// Assume snappy, the worst case
total_length += hap_max_encoded_length(inputBytes[i], textureFormats[i], HapCompressorSnappy, chunkCounts[i]);
}
return total_length;
}
static unsigned int hap_encode_texture(const void *inputBuffer, unsigned long inputBufferBytes, unsigned int textureFormat,
unsigned int compressor, unsigned int chunkCount, void *outputBuffer,
unsigned long outputBufferBytes, unsigned long *outputBufferBytesUsed)
{
size_t top_section_header_length;
size_t top_section_length;
unsigned int storedCompressor;
unsigned int storedFormat;
/*
Check arguments
*/
if (inputBuffer == NULL
|| inputBufferBytes == 0
|| (textureFormat != HapTextureFormat_RGB_DXT1
&& textureFormat != HapTextureFormat_RGBA_DXT5
&& textureFormat != HapTextureFormat_YCoCg_DXT5
&& textureFormat != HapTextureFormat_A_RGTC1
&& textureFormat != HapTextureFormat_RGBA_BPTC_UNORM
&& textureFormat != HapTextureFormat_RGB_BPTC_UNSIGNED_FLOAT
&& textureFormat != HapTextureFormat_RGB_BPTC_SIGNED_FLOAT
)
|| (compressor != HapCompressorNone
&& compressor != HapCompressorSnappy
)
|| outputBuffer == NULL
|| outputBufferBytesUsed == NULL
)
{
return HapResult_Bad_Arguments;
}
else if (outputBufferBytes < hap_max_encoded_length(inputBufferBytes, textureFormat, compressor, chunkCount))
{
return HapResult_Buffer_Too_Small;
}
/*
To store frames of length greater than can be expressed in three bytes, we use an eight byte header (the last four bytes are the
frame size). We don't know the compressed size until we have performed compression, but we know the worst-case size
(the uncompressed size), so choose header-length based on that.
A simpler encoder could always use the eight-byte header variation.
*/
if (inputBufferBytes > kHapUInt24Max)
{
top_section_header_length = 8U;
}
else
{
top_section_header_length = 4U;
}
if (compressor == HapCompressorSnappy)
{
/*
We attempt to chunk as requested, and if resulting frame is larger than it is uncompressed then
store frame uncompressed
*/
size_t decode_instructions_length;
size_t chunk_size, compress_buffer_remaining;
uint8_t *second_stage_compressor_table;
void *chunk_size_table;
char *compressed_data;
unsigned int i;
chunkCount = hap_limited_chunk_count_for_frame(inputBufferBytes, textureFormat, chunkCount);
decode_instructions_length = hap_decode_instructions_length(chunkCount);
// Check we have space for the Decode Instructions Container
if ((inputBufferBytes + decode_instructions_length + 4) > kHapUInt24Max)
{
top_section_header_length = 8U;
}
second_stage_compressor_table = ((uint8_t *)outputBuffer) + top_section_header_length + 4 + 4;
chunk_size_table = ((uint8_t *)outputBuffer) + top_section_header_length + 4 + 4 + chunkCount + 4;
chunk_size = inputBufferBytes / chunkCount;
// write the Decode Instructions section header
hap_write_section_header(((uint8_t *)outputBuffer) + top_section_header_length, 4U, decode_instructions_length, kHapSectionDecodeInstructionsContainer);
// write the Second Stage Compressor Table section header
hap_write_section_header(((uint8_t *)outputBuffer) + top_section_header_length + 4U, 4U, chunkCount, kHapSectionChunkSecondStageCompressorTable);
// write the Chunk Size Table section header
hap_write_section_header(((uint8_t *)outputBuffer) + top_section_header_length + 4U + 4U + chunkCount, 4U, chunkCount * 4U, kHapSectionChunkSizeTable);
compressed_data = (char *)(((uint8_t *)outputBuffer) + top_section_header_length + 4 + decode_instructions_length);
compress_buffer_remaining = outputBufferBytes - top_section_header_length - 4 - decode_instructions_length;
top_section_length = 4 + decode_instructions_length;
for (i = 0; i < chunkCount; i++) {
size_t chunk_packed_length = compress_buffer_remaining;
const char *chunk_input_start = (const char *)(((uint8_t *)inputBuffer) + (chunk_size * i));
if (compressor == HapCompressorSnappy)
{
snappy_status result = snappy_compress(chunk_input_start, chunk_size, (char *)compressed_data, &chunk_packed_length);
if (result != SNAPPY_OK)
{
return HapResult_Internal_Error;
}
}
if (compressor == HapCompressorNone || chunk_packed_length >= chunk_size)
{
// store the chunk uncompressed
memcpy(compressed_data, chunk_input_start, chunk_size);
chunk_packed_length = chunk_size;
second_stage_compressor_table[i] = kHapCompressorNone;
}
else
{
// ie we used snappy and saved some space
second_stage_compressor_table[i] = kHapCompressorSnappy;
}
hap_write_4_byte_uint(((uint8_t *)chunk_size_table) + (i * 4), chunk_packed_length);
compressed_data += chunk_packed_length;
top_section_length += chunk_packed_length;
compress_buffer_remaining -= chunk_packed_length;
}
if (top_section_length < inputBufferBytes + top_section_header_length)
{
// use the complex storage because snappy compression saved space
storedCompressor = kHapCompressorComplex;
}
else
{
// Signal to store the frame uncompressed
compressor = HapCompressorNone;
}
}
if (compressor == HapCompressorNone)
{
memcpy(((uint8_t *)outputBuffer) + top_section_header_length, inputBuffer, inputBufferBytes);
top_section_length = inputBufferBytes;
storedCompressor = kHapCompressorNone;
}
storedFormat = hap_texture_format_identifier_for_format_constant(textureFormat);
hap_write_section_header(outputBuffer, top_section_header_length, top_section_length, hap_4_bit_packed_byte(storedCompressor, storedFormat));
*outputBufferBytesUsed = top_section_length + top_section_header_length;
return HapResult_No_Error;
}
unsigned int HapEncode(unsigned int count,
const void **inputBuffers, unsigned long *inputBuffersBytes,
unsigned int *textureFormats,
unsigned int *compressors,
unsigned int *chunkCounts,
void *outputBuffer, unsigned long outputBufferBytes,
unsigned long *outputBufferBytesUsed)
{
size_t top_section_header_length;
size_t top_section_length;
unsigned long section_length;
if (count == 0 || count > 2 // A frame must contain one or two textures
|| inputBuffers == NULL
|| inputBuffersBytes == NULL
|| textureFormats == NULL
|| compressors == NULL
|| chunkCounts == NULL
|| outputBuffer == NULL
|| outputBufferBytes == 0
|| outputBufferBytesUsed == NULL)
{
return HapResult_Bad_Arguments;
}
for (int i = 0; i < count; i++)
{
if (chunkCounts[i] == 0)
{
return HapResult_Bad_Arguments;
}
}
if (count == 1)
{
// Encode without the multi-image layout
return hap_encode_texture(inputBuffers[0],
inputBuffersBytes[0],
textureFormats[0],
compressors[0],
chunkCounts[0],
outputBuffer,
outputBufferBytes,
outputBufferBytesUsed);
}
else if ((textureFormats[0] != HapTextureFormat_YCoCg_DXT5 && textureFormats[1] != HapTextureFormat_YCoCg_DXT5)
&& (textureFormats[0] != HapTextureFormat_A_RGTC1 && textureFormats[1] != HapTextureFormat_A_RGTC1))
{
/*
Permitted combinations:
HapTextureFormat_YCoCg_DXT5 + HapTextureFormat_A_RGTC1
*/
return HapResult_Bad_Arguments;
}
else
{
// Calculate the worst-case size for the top section and choose a header-length based on that
top_section_length = 0;
for (int i = 0; i < count; i++)
{
top_section_length += inputBuffersBytes[i] + hap_decode_instructions_length(chunkCounts[i]) + 4;
}
if (top_section_length > kHapUInt24Max)
{
top_section_header_length = 8U;
}
else
{
top_section_header_length = 4U;
}
// Encode each texture
top_section_length = 0;
for (int i = 0; i < count; i++)
{
void *section = ((uint8_t *)outputBuffer) + top_section_header_length + top_section_length;
unsigned int result = hap_encode_texture(inputBuffers[i],
inputBuffersBytes[i],
textureFormats[i],
compressors[i],
chunkCounts[i],
section,
outputBufferBytes - (top_section_header_length + top_section_length),
&section_length);
if (result != HapResult_No_Error)
{
return result;
}
top_section_length += section_length;
}
hap_write_section_header(outputBuffer, top_section_header_length, top_section_length, kHapSectionMultipleImages);
*outputBufferBytesUsed = top_section_length + top_section_header_length;
return HapResult_No_Error;
}
}
static void hap_decode_chunk(HapChunkDecodeInfo chunks[], unsigned int index)
{
if (chunks)
{
if (chunks[index].compressor == kHapCompressorSnappy)
{
snappy_status snappy_result = snappy_uncompress(chunks[index].compressed_chunk_data,
chunks[index].compressed_chunk_size,
chunks[index].uncompressed_chunk_data,
&chunks[index].uncompressed_chunk_size);
switch (snappy_result)
{
case SNAPPY_INVALID_INPUT:
chunks[index].result = HapResult_Bad_Frame;
break;
case SNAPPY_OK:
chunks[index].result = HapResult_No_Error;
break;
default:
chunks[index].result = HapResult_Internal_Error;
break;
}
}
else if (chunks[index].compressor == kHapCompressorNone)
{
memcpy(chunks[index].uncompressed_chunk_data,
chunks[index].compressed_chunk_data,
chunks[index].compressed_chunk_size);
chunks[index].result = HapResult_No_Error;
}
else
{
chunks[index].result = HapResult_Bad_Frame;
}
}
}
static unsigned int hap_decode_header_complex_instructions(const void *texture_section, uint32_t texture_section_length, int * chunk_count,
const void **compressors, const void **chunk_sizes, const void **chunk_offsets, const char **frame_data){
int result = HapResult_No_Error;
const void *section_start;
uint32_t section_header_length;
uint32_t section_length;
unsigned int section_type;
size_t bytes_remaining = 0;
*compressors = NULL;
*chunk_sizes = NULL;
*chunk_offsets = NULL;
result = hap_read_section_header(texture_section, texture_section_length, &section_header_length, &section_length, &section_type);
if (result == HapResult_No_Error && section_type != kHapSectionDecodeInstructionsContainer)
{
result = HapResult_Bad_Frame;
}
if (result != HapResult_No_Error)
{
return result;
}
/*
Frame data follows immediately after the Decode Instructions Container
*/
*frame_data = ((const char *)texture_section) + section_header_length + section_length;
/*
Step through the sections inside the Decode Instructions Container
*/
section_start = ((uint8_t *)texture_section) + section_header_length;
bytes_remaining = section_length;
while (bytes_remaining > 0) {
unsigned int section_chunk_count = 0;
result = hap_read_section_header(section_start, bytes_remaining, &section_header_length, &section_length, &section_type);
if (result != HapResult_No_Error)
{
return result;
}
section_start = ((uint8_t *)section_start) + section_header_length;
switch (section_type) {
case kHapSectionChunkSecondStageCompressorTable:
*compressors = section_start;
section_chunk_count = section_length;
break;
case kHapSectionChunkSizeTable:
*chunk_sizes = section_start;
section_chunk_count = section_length / 4;
break;
case kHapSectionChunkOffsetTable:
*chunk_offsets = section_start;
section_chunk_count = section_length / 4;
break;
default:
// Ignore unrecognized sections
break;
}
/*
If we calculated a chunk count and already have one, make sure they match
*/
if (section_chunk_count != 0)
{
if ((*chunk_count) != 0 && section_chunk_count != (*chunk_count))
{
return HapResult_Bad_Frame;
}
*chunk_count = section_chunk_count;
}
section_start = ((uint8_t *)section_start) + section_length;
bytes_remaining -= section_header_length + section_length;
}
/*
The Chunk Second-Stage Compressor Table and Chunk Size Table are required
*/
if (*compressors == NULL || *chunk_sizes == NULL)
{
return HapResult_Bad_Frame;
}
return result;
}
unsigned int hap_decode_single_texture(const void *texture_section, uint32_t texture_section_length,
unsigned int texture_section_type,
HapDecodeCallback callback, void *info,
void *outputBuffer, unsigned long outputBufferBytes,
unsigned long *outputBufferBytesUsed,
unsigned int *outputBufferTextureFormat)
{
int result = HapResult_No_Error;
unsigned int textureFormat;
unsigned int compressor;
size_t bytesUsed = 0;
/*
One top-level section type describes texture-format and second-stage compression
Hap compressor/format constants can be unpacked by reading the top and bottom four bits.
*/
compressor = hap_top_4_bits(texture_section_type);
textureFormat = hap_bottom_4_bits(texture_section_type);
/*
Pass the texture format out
*/
*outputBufferTextureFormat = hap_texture_format_constant_for_format_identifier(textureFormat);
if (*outputBufferTextureFormat == 0)
{
return HapResult_Bad_Frame;
}
if (compressor == kHapCompressorComplex)
{
/*
The top-level section should contain a Decode Instructions Container followed by frame data
*/
int chunk_count = 0;
const void *compressors = NULL;
const void *chunk_sizes = NULL;
const void *chunk_offsets = NULL;
const char *frame_data = NULL;
result = hap_decode_header_complex_instructions(texture_section, texture_section_length, &chunk_count, &compressors, &chunk_sizes, &chunk_offsets, &frame_data);
if (result != HapResult_No_Error)
{
return result;
}
if (chunk_count > 0)
{
/*
Step through the chunks, storing information for their decompression
*/
HapChunkDecodeInfo *chunk_info = (HapChunkDecodeInfo *)malloc(sizeof(HapChunkDecodeInfo) * chunk_count);
size_t running_compressed_chunk_size = 0;
size_t running_uncompressed_chunk_size = 0;
int i;
if (chunk_info == NULL)
{
return HapResult_Internal_Error;
}
for (i = 0; i < chunk_count; i++) {
chunk_info[i].compressor = *(((uint8_t *)compressors) + i);
chunk_info[i].compressed_chunk_size = hap_read_4_byte_uint(((uint8_t *)chunk_sizes) + (i * 4));
if (chunk_offsets)
{
chunk_info[i].compressed_chunk_data = frame_data + hap_read_4_byte_uint(((uint8_t *)chunk_offsets) + (i * 4));
}
else
{
chunk_info[i].compressed_chunk_data = frame_data + running_compressed_chunk_size;
}
running_compressed_chunk_size += chunk_info[i].compressed_chunk_size;
if (chunk_info[i].compressor == kHapCompressorSnappy)
{
snappy_status snappy_result = snappy_uncompressed_length(chunk_info[i].compressed_chunk_data,
chunk_info[i].compressed_chunk_size,
&(chunk_info[i].uncompressed_chunk_size));
if (snappy_result != SNAPPY_OK)
{
switch (snappy_result)
{
case SNAPPY_INVALID_INPUT:
result = HapResult_Bad_Frame;
break;
default:
result = HapResult_Internal_Error;
break;
}
break;
}
}
else
{
chunk_info[i].uncompressed_chunk_size = chunk_info[i].compressed_chunk_size;
}
chunk_info[i].uncompressed_chunk_data = (char *)(((uint8_t *)outputBuffer) + running_uncompressed_chunk_size);
running_uncompressed_chunk_size += chunk_info[i].uncompressed_chunk_size;
}
if (result == HapResult_No_Error && running_uncompressed_chunk_size > outputBufferBytes)
{
result = HapResult_Buffer_Too_Small;
}
if (result == HapResult_No_Error)
{
/*
Perform decompression
*/
bytesUsed = running_uncompressed_chunk_size;
if (chunk_count == 1)
{
/*
We don't invoke the callback for one chunk, just decode it directly
*/
hap_decode_chunk(chunk_info, 0);
}
else
{
callback((HapDecodeWorkFunction)hap_decode_chunk, chunk_info, chunk_count, info);
}
/*
Check to see if we encountered any errors and report one of them
*/
for (i = 0; i < chunk_count; i++)
{
if (chunk_info[i].result != HapResult_No_Error)
{
result = chunk_info[i].result;
break;
}
}
}
free(chunk_info);
if (result != HapResult_No_Error)
{
return result;
}
}
}
else if (compressor == kHapCompressorSnappy)
{
/*
Only one section is present containing a single block of snappy-compressed texture data
*/
snappy_status snappy_result = snappy_uncompressed_length((const char *)texture_section, texture_section_length, &bytesUsed);
if (snappy_result != SNAPPY_OK)
{
return HapResult_Internal_Error;
}
if (bytesUsed > outputBufferBytes)
{
return HapResult_Buffer_Too_Small;
}
snappy_result = snappy_uncompress((const char *)texture_section, texture_section_length, (char *)outputBuffer, &bytesUsed);
if (snappy_result != SNAPPY_OK)
{
return HapResult_Internal_Error;
}
}
else if (compressor == kHapCompressorNone)
{
/*
Only one section is present containing a single block of uncompressed texture data
*/
bytesUsed = texture_section_length;
if (texture_section_length > outputBufferBytes)
{
return HapResult_Buffer_Too_Small;
}
memcpy(outputBuffer, texture_section, texture_section_length);
}
else
{
return HapResult_Bad_Frame;
}
/*
Fill out the remaining return value
*/
if (outputBufferBytesUsed != NULL)
{
*outputBufferBytesUsed = bytesUsed;
}
return HapResult_No_Error;
}
int hap_get_section_at_index(const void *input_buffer, uint32_t input_buffer_bytes,
unsigned int index,
const void **section, uint32_t *section_length, unsigned int *section_type)
{
int result;
uint32_t section_header_length;
result = hap_read_section_header(input_buffer, input_buffer_bytes, &section_header_length, section_length, section_type);
if (result != HapResult_No_Error)
{
return result;
}
if (*section_type == kHapSectionMultipleImages)
{
/*
Step through until we find the section at index
*/
size_t offset = 0;
size_t top_section_length = *section_length;
input_buffer = ((uint8_t *)input_buffer) + section_header_length;
section_header_length = 0;
*section_length = 0;
for (int i = 0; i <= index; i++) {
offset += section_header_length + *section_length;
if (offset >= top_section_length)
{
return HapResult_Bad_Arguments;
}
result = hap_read_section_header(((uint8_t *)input_buffer) + offset,
top_section_length - offset,
&section_header_length,
section_length,
section_type);
if (result != HapResult_No_Error)
{
return result;
}
}
offset += section_header_length;
*section = ((uint8_t *)input_buffer) + offset;
return HapResult_No_Error;
}
else if (index == 0)
{
/*
A single-texture frame with the texture as the top section.
*/
*section = ((uint8_t *)input_buffer) + section_header_length;
return HapResult_No_Error;
}
else
{
*section = NULL;
*section_length = 0;
*section_type = 0;
return HapResult_Bad_Arguments;
}
}
unsigned int HapDecode(const void *inputBuffer, unsigned long inputBufferBytes,
unsigned int index,
HapDecodeCallback callback, void *info,
void *outputBuffer, unsigned long outputBufferBytes,
unsigned long *outputBufferBytesUsed,
unsigned int *outputBufferTextureFormat)
{
int result = HapResult_No_Error;
const void *section;
uint32_t section_length;
unsigned int section_type;
/*
Check arguments
*/
if (inputBuffer == NULL
|| index > 1
|| callback == NULL
|| outputBuffer == NULL
|| outputBufferTextureFormat == NULL
)
{
return HapResult_Bad_Arguments;
}
/*
Locate the section at the given index, which will either be the top-level section in a single texture image, or one of the
sections inside a multi-image top-level section.
*/
result = hap_get_section_at_index(inputBuffer, inputBufferBytes, index, &section, &section_length, &section_type);
if (result == HapResult_No_Error)
{
/*
Decode the located texture
*/
result = hap_decode_single_texture(section,
section_length,
section_type,
callback, info,
outputBuffer,
outputBufferBytes,
outputBufferBytesUsed,
outputBufferTextureFormat);
}
return result;
}
unsigned int HapGetFrameTextureCount(const void *inputBuffer, unsigned long inputBufferBytes, unsigned int *outputTextureCount)
{
int result;
uint32_t section_header_length;
uint32_t section_length;
unsigned int section_type;
result = hap_read_section_header(inputBuffer, inputBufferBytes, &section_header_length, &section_length, &section_type);
if (result != HapResult_No_Error)
{
return result;
}
if (section_type == kHapSectionMultipleImages)
{
/*
Step through, counting sections
*/
uint32_t offset = section_header_length;
uint32_t top_section_length = section_length;
*outputTextureCount = 0;
while (offset < top_section_length) {
result = hap_read_section_header(((uint8_t *)inputBuffer) + offset,
inputBufferBytes - offset,
&section_header_length,
&section_length,
&section_type);
if (result != HapResult_No_Error)
{
return result;
}
offset += section_header_length + section_length;
*outputTextureCount += 1;
}
return HapResult_No_Error;
}
else
{
/*
A single-texture frame with the texture as the top section.
*/
*outputTextureCount = 1;
return HapResult_No_Error;
}
}
unsigned int HapGetFrameTextureFormat(const void *inputBuffer, unsigned long inputBufferBytes, unsigned int index, unsigned int *outputBufferTextureFormat)
{
unsigned int result = HapResult_No_Error;
const void *section;
uint32_t section_length;
unsigned int section_type;
/*
Check arguments
*/
if (inputBuffer == NULL
|| index > 1
|| outputBufferTextureFormat == NULL
)
{
return HapResult_Bad_Arguments;
}
/*
Locate the section at the given index, which will either be the top-level section in a single texture image, or one of the
sections inside a multi-image top-level section.
*/
result = hap_get_section_at_index(inputBuffer, inputBufferBytes, index, &section, &section_length, &section_type);
if (result == HapResult_No_Error)
{
/*
Pass the API enum value to match the constant out
*/
*outputBufferTextureFormat = hap_texture_format_constant_for_format_identifier(hap_bottom_4_bits(section_type));
/*
Check a valid format was present
*/
if (*outputBufferTextureFormat == 0)
{
result = HapResult_Bad_Frame;
}
}
return result;
}
unsigned int HapGetFrameTextureChunkCount(const void *inputBuffer, unsigned long inputBufferBytes, unsigned int index, int *chunk_count)
{
unsigned int result = HapResult_No_Error;
const void *section;
uint32_t section_length;
unsigned int section_type;
*chunk_count = 0;
/*
Check arguments
*/
if (inputBuffer == NULL
|| index > 1
)
{
return HapResult_Bad_Arguments;
}
/*
Locate the section at the given index, which will either be the top-level section in a single texture image, or one of the
sections inside a multi-image top-level section.
*/
result = hap_get_section_at_index(inputBuffer, inputBufferBytes, index, &section, &section_length, &section_type);
if (result == HapResult_No_Error)
{
unsigned int compressor;
/*
One top-level section type describes texture-format and second-stage compression
Hap compressor/format constants can be unpacked by reading the top and bottom four bits.
*/
compressor = hap_top_4_bits(section_type);
if (compressor == kHapCompressorComplex)
{
/*
The top-level section should contain a Decode Instructions Container followed by frame data
*/
const void *compressors = NULL;
const void *chunk_sizes = NULL;
const void *chunk_offsets = NULL;
const char *frame_data = NULL;
result = hap_decode_header_complex_instructions(section, section_length, chunk_count, &compressors, &chunk_sizes, &chunk_offsets, &frame_data);
if (result != HapResult_No_Error)
{
return result;
}
}
else if ((compressor == kHapCompressorSnappy)||(compressor == kHapCompressorNone))
{
*chunk_count = 1;
}
else
{
return HapResult_Bad_Frame;
}
}
return result;
}