diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /thirdparty/stb/tests/caveview/cave_parse.c | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'thirdparty/stb/tests/caveview/cave_parse.c')
| -rw-r--r-- | thirdparty/stb/tests/caveview/cave_parse.c | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/thirdparty/stb/tests/caveview/cave_parse.c b/thirdparty/stb/tests/caveview/cave_parse.c new file mode 100644 index 0000000..e8ae02b --- /dev/null +++ b/thirdparty/stb/tests/caveview/cave_parse.c @@ -0,0 +1,632 @@ +#include <assert.h> +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> + +#define FAST_CHUNK // disabling this enables the old, slower path that deblocks into a regular form + +#include "cave_parse.h" + +#include "stb_image.h" +#include "stb.h" + +#define NUM_CHUNKS_PER_REGION 32 // only on one axis +#define NUM_CHUNKS_PER_REGION_LOG2 5 + +#define NUM_COLUMNS_PER_CHUNK 16 +#define NUM_COLUMNS_PER_CHUNK_LOG2 4 + +uint32 read_uint32_be(FILE *f) +{ + unsigned char data[4]; + fread(data, 1, 4, f); + return (data[0]<<24) + (data[1]<<16) + (data[2]<<8) + data[3]; +} + +typedef struct +{ + uint8 *data; + size_t len; + int x,z; // chunk index + int refcount; // for multi-threading +} compressed_chunk; + +typedef struct +{ + int x,z; + uint32 sector_data[NUM_CHUNKS_PER_REGION][NUM_CHUNKS_PER_REGION]; +} region; + +size_t cached_compressed=0; + +FILE *last_region; +int last_region_x; +int last_region_z; +int opened=0; + +static void open_file(int reg_x, int reg_z) +{ + if (!opened || last_region_x != reg_x || last_region_z != reg_z) { + char filename[256]; + if (last_region != NULL) + fclose(last_region); + sprintf(filename, "r.%d.%d.mca", reg_x, reg_z); + last_region = fopen(filename, "rb"); + last_region_x = reg_x; + last_region_z = reg_z; + opened = 1; + } +} + +static region *load_region(int reg_x, int reg_z) +{ + region *r; + int x,z; + + open_file(reg_x, reg_z); + + r = malloc(sizeof(*r)); + + if (last_region == NULL) { + memset(r, 0, sizeof(*r)); + } else { + fseek(last_region, 0, SEEK_SET); + for (z=0; z < NUM_CHUNKS_PER_REGION; ++z) + for (x=0; x < NUM_CHUNKS_PER_REGION; ++x) + r->sector_data[z][x] = read_uint32_be(last_region); + } + r->x = reg_x; + r->z = reg_z; + + return r; +} + +void free_region(region *r) +{ + free(r); +} + +#define MAX_MAP_REGIONS 64 // in one axis: 64 regions * 32 chunk/region * 16 columns/chunk = 16384 columns +region *regions[MAX_MAP_REGIONS][MAX_MAP_REGIONS]; + +static region *get_region(int reg_x, int reg_z) +{ + int slot_x = reg_x & (MAX_MAP_REGIONS-1); + int slot_z = reg_z & (MAX_MAP_REGIONS-1); + region *r; + + r = regions[slot_z][slot_x]; + + if (r) { + if (r->x == reg_x && r->z == reg_z) + return r; + free_region(r); + } + + r = load_region(reg_x, reg_z); + regions[slot_z][slot_x] = r; + + return r; +} + +// about one region, so size should be ok +#define NUM_CACHED_X 64 +#define NUM_CACHED_Z 64 + +// @TODO: is it really worth caching these? we probably can just +// pull them from the disk cache nearly as efficiently. +// Can test that by setting to 1x1? +compressed_chunk *cached_chunk[NUM_CACHED_Z][NUM_CACHED_X]; + +static void deref_compressed_chunk(compressed_chunk *cc) +{ + assert(cc->refcount > 0); + --cc->refcount; + if (cc->refcount == 0) { + if (cc->data) + free(cc->data); + free(cc); + } +} + +static compressed_chunk *get_compressed_chunk(int chunk_x, int chunk_z) +{ + int slot_x = chunk_x & (NUM_CACHED_X-1); + int slot_z = chunk_z & (NUM_CACHED_Z-1); + compressed_chunk *cc = cached_chunk[slot_z][slot_x]; + + if (cc && cc->x == chunk_x && cc->z == chunk_z) + return cc; + else { + int reg_x = chunk_x >> NUM_CHUNKS_PER_REGION_LOG2; + int reg_z = chunk_z >> NUM_CHUNKS_PER_REGION_LOG2; + region *r = get_region(reg_x, reg_z); + if (cc) { + deref_compressed_chunk(cc); + cached_chunk[slot_z][slot_x] = NULL; + } + cc = malloc(sizeof(*cc)); + cc->x = chunk_x; + cc->z = chunk_z; + { + int subchunk_x = chunk_x & (NUM_CHUNKS_PER_REGION-1); + int subchunk_z = chunk_z & (NUM_CHUNKS_PER_REGION-1); + uint32 code = r->sector_data[subchunk_z][subchunk_x]; + + if (code & 255) { + open_file(reg_x, reg_z); + fseek(last_region, (code>>8)*4096, SEEK_SET); + cc->len = (code&255)*4096; + cc->data = malloc(cc->len); + fread(cc->data, 1, cc->len, last_region); + } else { + cc->len = 0; + cc->data = 0; + } + } + cc->refcount = 1; + cached_chunk[slot_z][slot_x] = cc; + return cc; + } +} + + +// NBT parser -- can automatically parse stuff we don't +// have definitions for, but want to explicitly parse +// stuff we do have definitions for. +// +// option 1: auto-parse everything into data structures, +// then read those +// +// option 2: have a "parse next object" which +// doesn't resolve whether it expands its children +// yet, and then the user either says "expand" or +// "skip" after looking at the name. Anything with +// "children" without names can't go through this +// interface. +// +// Let's try option 2. + + +typedef struct +{ + unsigned char *buffer_start; + unsigned char *buffer_end; + unsigned char *cur; + int nesting; + char temp_buffer[256]; +} nbt; + +enum { TAG_End=0, TAG_Byte=1, TAG_Short=2, TAG_Int=3, TAG_Long=4, + TAG_Float=5, TAG_Double=6, TAG_Byte_Array=7, TAG_String=8, + TAG_List=9, TAG_Compound=10, TAG_Int_Array=11 }; + +static void nbt_get_string_data(unsigned char *data, char *buffer, size_t bufsize) +{ + int len = data[0]*256 + data[1]; + int i; + for (i=0; i < len && i+1 < (int) bufsize; ++i) + buffer[i] = (char) data[i+2]; + buffer[i] = 0; +} + +static char *nbt_peek(nbt *n) +{ + unsigned char type = *n->cur; + if (type == TAG_End) + return NULL; + nbt_get_string_data(n->cur+1, n->temp_buffer, sizeof(n->temp_buffer)); + return n->temp_buffer; +} + +static uint32 nbt_parse_uint32(unsigned char *buffer) +{ + return (buffer[0] << 24) + (buffer[1]<<16) + (buffer[2]<<8) + buffer[3]; +} + +static void nbt_skip(nbt *n); + +// skip an item that doesn't have an id or name prefix (usable in lists) +static void nbt_skip_raw(nbt *n, unsigned char type) +{ + switch (type) { + case TAG_Byte : n->cur += 1; break; + case TAG_Short : n->cur += 2; break; + case TAG_Int : n->cur += 4; break; + case TAG_Long : n->cur += 8; break; + case TAG_Float : n->cur += 4; break; + case TAG_Double: n->cur += 8; break; + case TAG_Byte_Array: n->cur += 4 + 1*nbt_parse_uint32(n->cur); break; + case TAG_Int_Array : n->cur += 4 + 4*nbt_parse_uint32(n->cur); break; + case TAG_String : n->cur += 2 + (n->cur[0]*256 + n->cur[1]); break; + case TAG_List : { + unsigned char list_type = *n->cur++; + unsigned int list_len = nbt_parse_uint32(n->cur); + unsigned int i; + n->cur += 4; // list_len + for (i=0; i < list_len; ++i) + nbt_skip_raw(n, list_type); + break; + } + case TAG_Compound : { + while (*n->cur != TAG_End) + nbt_skip(n); + nbt_skip(n); // skip the TAG_end + break; + } + } + assert(n->cur <= n->buffer_end); +} + +static void nbt_skip(nbt *n) +{ + unsigned char type = *n->cur++; + if (type == TAG_End) + return; + // skip name + n->cur += (n->cur[0]*256 + n->cur[1]) + 2; + nbt_skip_raw(n, type); +} + +// byteswap +static void nbt_swap(unsigned char *ptr, int len) +{ + int i; + for (i=0; i < (len>>1); ++i) { + unsigned char t = ptr[i]; + ptr[i] = ptr[len-1-i]; + ptr[len-1-i] = t; + } +} + +// pass in the expected type, fail if doesn't match +// returns a pointer to the data, byteswapped if appropriate +static void *nbt_get_fromlist(nbt *n, unsigned char type, int *len) +{ + unsigned char *ptr; + assert(type != TAG_Compound); + assert(type != TAG_List); // we could support getting lists of primitives as if they were arrays, but eh + if (len) *len = 1; + ptr = n->cur; + switch (type) { + case TAG_Byte : break; + + case TAG_Short : nbt_swap(ptr, 2); break; + case TAG_Int : nbt_swap(ptr, 4); break; + case TAG_Long : nbt_swap(ptr, 8); break; + case TAG_Float : nbt_swap(ptr, 4); break; + case TAG_Double: nbt_swap(ptr, 8); break; + + case TAG_Byte_Array: + *len = nbt_parse_uint32(ptr); + ptr += 4; + break; + case TAG_Int_Array: { + int i; + *len = nbt_parse_uint32(ptr); + ptr += 4; + for (i=0; i < *len; ++i) + nbt_swap(ptr + 4*i, 4); + break; + } + + default: assert(0); // unhandled case + } + nbt_skip_raw(n, type); + return ptr; +} + +static void *nbt_get(nbt *n, unsigned char type, int *len) +{ + assert(n->cur[0] == type); + n->cur += 3 + (n->cur[1]*256+n->cur[2]); + return nbt_get_fromlist(n, type, len); +} + +static void nbt_begin_compound(nbt *n) // start a compound +{ + assert(*n->cur == TAG_Compound); + // skip header + n->cur += 3 + (n->cur[1]*256 + n->cur[2]); + ++n->nesting; +} + +static void nbt_begin_compound_in_list(nbt *n) // start a compound +{ + ++n->nesting; +} + +static void nbt_end_compound(nbt *n) // end a compound +{ + assert(*n->cur == TAG_End); + assert(n->nesting != 0); + ++n->cur; + --n->nesting; +} + +// @TODO no interface to get lists from lists +static int nbt_begin_list(nbt *n, unsigned char type) +{ + uint32 len; + unsigned char *ptr; + + ptr = n->cur + 3 + (n->cur[1]*256 + n->cur[2]); + if (ptr[0] != type) + return -1; + n->cur = ptr; + len = nbt_parse_uint32(n->cur+1); + assert(n->cur[0] == type); + // @TODO keep a stack with the count to make sure they do it right + ++n->nesting; + n->cur += 5; + return (int) len; +} + +static void nbt_end_list(nbt *n) +{ + --n->nesting; +} + +// raw_block chunk is 16x256x16x4 = 2^(4+8+4+2) = 256KB +// +// if we want to process 64x64x256 at a time, that will be: +// 4*4*256KB => 4MB per area in raw_block +// +// (plus we maybe need to decode adjacent regions) + + +#ifdef FAST_CHUNK +typedef fast_chunk parse_chunk; +#else +typedef chunk parse_chunk; +#endif + +static parse_chunk *minecraft_chunk_parse(unsigned char *data, size_t len) +{ + char *s; + parse_chunk *c = NULL; + + nbt n_store, *n = &n_store; + n->buffer_start = data; + n->buffer_end = data + len; + n->cur = n->buffer_start; + n->nesting = 0; + + nbt_begin_compound(n); + while ((s = nbt_peek(n)) != NULL) { + if (!strcmp(s, "Level")) { + int *height; + c = malloc(sizeof(*c)); + #ifdef FAST_CHUNK + memset(c, 0, sizeof(*c)); + c->pointer_to_free = data; + #else + c->rb[15][15][255].block = 0; + #endif + c->max_y = 0; + + nbt_begin_compound(n); + while ((s = nbt_peek(n)) != NULL) { + if (!strcmp(s, "xPos")) + c->xpos = *(int *) nbt_get(n, TAG_Int, 0); + else if (!strcmp(s, "zPos")) + c->zpos = *(int *) nbt_get(n, TAG_Int, 0); + else if (!strcmp(s, "Sections")) { + int count = nbt_begin_list(n, TAG_Compound), i; + if (count == -1) { + // this not-a-list case happens in The End and I'm not sure + // what it means... possibly one of those silly encodings + // where it's not encoded as a list if there's only one? + // not worth figuring out + nbt_skip(n); + count = -1; + } + for (i=0; i < count; ++i) { + int yi, len; + uint8 *light = NULL, *blocks = NULL, *data = NULL, *skylight = NULL; + nbt_begin_compound_in_list(n); + while ((s = nbt_peek(n)) != NULL) { + if (!strcmp(s, "Y")) + yi = * (uint8 *) nbt_get(n, TAG_Byte, 0); + else if (!strcmp(s, "BlockLight")) { + light = nbt_get(n, TAG_Byte_Array, &len); + assert(len == 2048); + } else if (!strcmp(s, "Blocks")) { + blocks = nbt_get(n, TAG_Byte_Array, &len); + assert(len == 4096); + } else if (!strcmp(s, "Data")) { + data = nbt_get(n, TAG_Byte_Array, &len); + assert(len == 2048); + } else if (!strcmp(s, "SkyLight")) { + skylight = nbt_get(n, TAG_Byte_Array, &len); + assert(len == 2048); + } + } + nbt_end_compound(n); + + assert(yi < 16); + + #ifndef FAST_CHUNK + + // clear data below current max_y + { + int x,z; + while (c->max_y < yi*16) { + for (x=0; x < 16; ++x) + for (z=0; z < 16; ++z) + c->rb[z][x][c->max_y].block = 0; + ++c->max_y; + } + } + + // now assemble the data + { + int x,y,z, o2=0,o4=0; + for (y=0; y < 16; ++y) { + for (z=0; z < 16; ++z) { + for (x=0; x < 16; x += 2) { + raw_block *rb = &c->rb[15-z][x][y + yi*16]; // 15-z because switching to z-up will require flipping an axis + rb[0].block = blocks[o4]; + rb[0].light = light[o2] & 15; + rb[0].data = data[o2] & 15; + rb[0].skylight = skylight[o2] & 15; + + rb[256].block = blocks[o4+1]; + rb[256].light = light[o2] >> 4; + rb[256].data = data[o2] >> 4; + rb[256].skylight = skylight[o2] >> 4; + + o2 += 1; + o4 += 2; + } + } + } + c->max_y += 16; + } + #else + c->blockdata[yi] = blocks; + c->data [yi] = data; + c->light [yi] = light; + c->skylight [yi] = skylight; + #endif + } + //nbt_end_list(n); + } else if (!strcmp(s, "HeightMap")) { + height = nbt_get(n, TAG_Int_Array, &len); + assert(len == 256); + } else + nbt_skip(n); + } + nbt_end_compound(n); + + } else + nbt_skip(n); + } + nbt_end_compound(n); + assert(n->cur == n->buffer_end); + return c; +} + +#define MAX_DECODED_CHUNK_X 64 +#define MAX_DECODED_CHUNK_Z 64 + +typedef struct +{ + int cx,cz; + fast_chunk *fc; + int valid; +} decoded_buffer; + +static decoded_buffer decoded_buffers[MAX_DECODED_CHUNK_Z][MAX_DECODED_CHUNK_X]; +void lock_chunk_get_mutex(void); +void unlock_chunk_get_mutex(void); + +#ifdef FAST_CHUNK +fast_chunk *get_decoded_fastchunk_uncached(int chunk_x, int chunk_z) +{ + unsigned char *decoded; + compressed_chunk *cc; + int inlen; + int len; + fast_chunk *fc; + + lock_chunk_get_mutex(); + cc = get_compressed_chunk(chunk_x, chunk_z); + if (cc->len != 0) + ++cc->refcount; + unlock_chunk_get_mutex(); + + if (cc->len == 0) + return NULL; + + assert(cc != NULL); + + assert(cc->data[4] == 2); + + inlen = nbt_parse_uint32(cc->data); + decoded = stbi_zlib_decode_malloc_guesssize(cc->data+5, inlen, inlen*3, &len); + assert(decoded != NULL); + assert(len != 0); + + lock_chunk_get_mutex(); + deref_compressed_chunk(cc); + unlock_chunk_get_mutex(); + + #ifdef FAST_CHUNK + fc = minecraft_chunk_parse(decoded, len); + #else + fc = NULL; + #endif + if (fc == NULL) + free(decoded); + return fc; +} + + +decoded_buffer *get_decoded_buffer(int chunk_x, int chunk_z) +{ + decoded_buffer *db = &decoded_buffers[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)]; + if (db->valid) { + if (db->cx == chunk_x && db->cz == chunk_z) + return db; + if (db->fc) { + free(db->fc->pointer_to_free); + free(db->fc); + } + } + + db->cx = chunk_x; + db->cz = chunk_z; + db->valid = 1; + db->fc = 0; + + { + db->fc = get_decoded_fastchunk_uncached(chunk_x, chunk_z); + return db; + } +} + +fast_chunk *get_decoded_fastchunk(int chunk_x, int chunk_z) +{ + decoded_buffer *db = get_decoded_buffer(chunk_x, chunk_z); + return db->fc; +} +#endif + +#ifndef FAST_CHUNK +chunk *get_decoded_chunk_raw(int chunk_x, int chunk_z) +{ + unsigned char *decoded; + compressed_chunk *cc = get_compressed_chunk(chunk_x, chunk_z); + assert(cc != NULL); + if (cc->len == 0) + return NULL; + else { + chunk *ch; + int inlen = nbt_parse_uint32(cc->data); + int len; + assert(cc->data[4] == 2); + decoded = stbi_zlib_decode_malloc_guesssize(cc->data+5, inlen, inlen*3, &len); + assert(decoded != NULL); + #ifdef FAST_CHUNK + ch = NULL; + #else + ch = minecraft_chunk_parse(decoded, len); + #endif + free(decoded); + return ch; + } +} + +static chunk *decoded_chunks[MAX_DECODED_CHUNK_Z][MAX_DECODED_CHUNK_X]; +chunk *get_decoded_chunk(int chunk_x, int chunk_z) +{ + chunk *c = decoded_chunks[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)]; + if (c && c->xpos == chunk_x && c->zpos == chunk_z) + return c; + if (c) free(c); + c = get_decoded_chunk_raw(chunk_x, chunk_z); + decoded_chunks[chunk_z&(MAX_DECODED_CHUNK_Z-1)][chunk_x&(MAX_DECODED_CHUNK_X-1)] = c; + return c; +} +#endif |