ref: 2eb5205dcc7f276ed436bf42a9bcb9ce5318920a
parent: 23156d38de052b1480bed45468ec019e6b5bbea8
author: Clownacy <Clownacy@users.noreply.github.com>
date: Mon Sep 7 21:58:51 EDT 2020
Remove `cute_spritebatch.h`
--- a/external/cute_spritebatch.h
+++ /dev/null
@@ -1,1942 +1,0 @@
-/*
- ------------------------------------------------------------------------------
- Licensing information can be found at the end of the file.
- ------------------------------------------------------------------------------
-
- cute_spritebatch.h - v1.02
-
- To create implementation (the function definitions)
- #define SPRITEBATCH_IMPLEMENTATION
- in *one* C/CPP file (translation unit) that includes this file
-
- SUMMARY:
-
- This header implements a 2D sprite batcher by tracking different textures within
- a rolling atlas cache. Over time atlases are decayed and recreated when textures
- stop being used. This header is useful for batching sprites at run-time. This avoids
- the need to compile texture atlases as a pre-process step, letting the game load
- images up individually, dramatically simplifying art pipelines.
-
- MORE DETAILS:
-
- `spritebatch_push` is used to push sprite instances into a buffer. Rendering sprites
- works by calling `spritebatch_flush`. `spritebatch_flush` will use a user-supplied
- callback to report sprite batches. This callback is of type `submit_batch_fn`. The
- batches are reported as an array of `spritebatch_sprite_t` sprites, and can be
- further sorted by the user (for example to sort by depth). Sprites in a batch share
- the same texture handle (either from the same base image, or from the same internal
- atlas).
-
- cute_spritebatch does not know anything about how to generate texture handles, or
- destroy them. As such, the user must supply two callbacks for creating handles and
- destroying them. These can be simple wrappers around, for example, `glGenTextures`
- and `glDeleteTextures`.
-
- Finally, cute_spritebatch will periodically need access to pixels from images. These
- pixels are used to generate textures, or to build atlases (which in turn generate a
- texture). cute_spritebatch does not need to know much about your images, other than
- the pixel stride. The user supplies callback of type `get_pixels_fn`, which lets
- cute_spritebatch retreive the pixels associated with a particular image. The pixels
- can be stored in RAM and handed to cute_spritebatch whenever requested, or the pixels
- can be fetched directly from disk and handed to cute_spritebatch. It doesn't matter
- to cute_spritebatch. Since `get_pixels_fn` can be called from `spritebatch_flush` it
- is recommended to avoid file i/o within the `get_pixels_fn` callback, and instead try
- to already have pixels ready in RAM.
-
- The `spritebatch_defrag` function performs atlas creation and texture management. It
- should be called periodically. It can be called once per game tick (once per render),
- or optionally called at a different frequency (once ever N game ticks).
-
- PROS AND CONS:
-
- PROS
- - Texture atlases are completely hidden behind an api. The api in this header can
- easily be implemented with different backend sprite batchers. For example on
- some platforms bindless textures can be utilized in order to avoid texture
- atlases entirely! Code using this API can have the backend implementation swapped
- without requiring any user code to change.
- - Sprites are batched in an effective manner to dramatically reduce draw call counts.
- - Supporting hotswapping or live-reloading of images can be trivialized due to
- moving atlas creation out of the art-pipeline and into the run-time.
- - Since atlases are built at run-time and continually maintained, images are
- guaranteed to be drawn at the same time on-screen as their atlas neighbors. This is
- typically not the case for atlas preprocessors, as a *guess* must be made to try
- and organize images together in atlases that need to be drawn at roughly the same
- time.
-
- CONS
- - Performance hits in the `spritebatch_defrag` function, and a little as well in
- the `spritebatch_flush` function. Extra run-time memory usage for bookkeeping,
- which implies a RAM hit as well as more things to clog the CPU cache.
- - If each texture comes from a separate image on-disk, opening individual files on
- disk can be very slow. For example on Windows just performing permissions and
- related work to open a file is time-consuming.
- - For large numbers of separate images, some file abstraction is necessary to avoid
- a large performance hit on opening/closing many individual files. This problem is
- *not* solved by cute_spritebatch.h, and instead should be solved by some separate
- file abstraction system.
-
- EXAMPLE USAGE:
-
- spritebatch_config_t config;
- spritebatch_set_default_config(&config);
- config.batch_callback = my_report_batches_function;
- config.get_pixels_callback = my_get_pixels_function;
- config.generate_texture_callback = my_make_texture_handle_function;
- config.delete_texture_callback = my_destroy_texture_handle_function;
-
- spritebatch_t batcher;
- spritebatch_init(&batcher, &config);
-
- while (game_is_running)
- {
- for (int i = 0; i < sprite_count; ++i)
- spritebatch_push(
- &batcher,
- sprites[i].image_id,
- sprites[i].image_width_in_pixels,
- sprites[i].image_height_in_pixels,
- sprites[i].position_x,
- sprites[i].poxition_y,
- sprites[i].scale_x,
- sprites[i].scale_y,
- sprites[i].cos_rotation_angle,
- sprites[i].sin_rotation_angle
- );
-
- spritebatch_tick(&batcher);
- spritebatch_defrag(&batcher);
- spritebatch_flush(&batcher);
- }
-
- CUSTOMIZATION:
-
- The following macros can be defined before including this header with the
- SPRITEBATCH_IMPLEMENTATION symbol defined, in order to customize the internal
- behavior of cute_spritebatch.h. Search this header to find how each macro is
- defined and used. Note that MALLOC/FREE functions can optionally take a context
- parameter for custom allocation.
-
- SPRITEBATCH_MALLOC
- SPRITEBATCH_MEMCPY
- SPRITEBATCH_MEMSET
- SPRITEBATCH_ASSERT
- SPRITEBATCH_ATLAS_FLIP_Y_AXIS_FOR_UV
- SPRITEBATCH_ATLAS_EMPTY_COLOR
- SPRITEBATCH_LOG
-
- Revision history:
- 0.01 (11/20/2017) experimental release
- 1.00 (04/14/2018) initial release
- 1.01 (05/07/2018) modification for easier file embedding
- 1.02 (02/03/2019) moved def of spritebatch_t for easier embedding,
- inverted get pixels callback to let users have an easier time
- with memory management, added support for pixel padding along
- the edges of all textures (useful for certain shader effects)
- 1.02 (04/09/2020) Compilation fix for FreeBSD - Remove unused alloca header and define
-*/
-
-#ifndef SPRITEBATCH_H
-
-#ifndef SPRITEBATCH_U64
- #define SPRITEBATCH_U64 unsigned long long
-#endif // SPRITEBATCH_U64
-
-typedef struct spritebatch_t spritebatch_t;
-typedef struct spritebatch_config_t spritebatch_config_t;
-typedef struct spritebatch_sprite_t spritebatch_sprite_t;
-
-// Pushes a sprite onto an internal buffer. Does no other logic. `image_id` must be a unique
-// identifier for the image a sprite references. `image_w` and image_h` are the width and height
-// of the image referenced by `image_id`. `x` and `y` are the position of the sprite. `sx` and
-// `sy` are the scale factors on the x and y axis for the sprite. `c` and `s` are the cosine and
-// sine of the angle of the sprite, and represent a 2D rotation matrix.
-int spritebatch_push(spritebatch_t* sb, SPRITEBATCH_U64 image_id, int image_w, int image_h, float x, float y, float sx, float sy, float c, float s, int sort_bits);
-
-// Increments internal timestamps on all textures, for use in `spritebatch_defrag`.
-void spritebatch_tick(spritebatch_t* sb);
-
-// Sorts the internal sprites and flushes the buffer built by `spritebatch_push`. Will call
-// the `submit_batch_fn` function for each batch of sprites and return them as an array. Any `image_id`
-// within the `spritebatch_push` buffer that do not yet have a texture handle will request pixels
-// from the image via `get_pixels_fn` and request a texture handle via `generate_texture_handle_fn`.
-int spritebatch_flush(spritebatch_t* sb);
-
-// All textures created so far by `spritebatch_flush` will be considered as candidates for creating
-// new internal texture atlases. Internal texture atlases compress images together inside of one
-// texture to dramatically reduce draw calls. When an atlas is created, the most recently used `image_id`
-// instances are prioritized, to ensure atlases are filled with images all drawn at the same time.
-// As some textures cease to draw on screen, they "decay" over time. Once enough images in an atlas
-// decay, the atlas is removed, and any "live" images in the atlas are used to create new atlases.
-// Can be called every 1/N times `spritebatch_flush` is called.
-int spritebatch_defrag(spritebatch_t* sb);
-
-int spritebatch_init(spritebatch_t* sb, spritebatch_config_t* config, void* udata);
-void spritebatch_term(spritebatch_t* sb);
-
-// Sprite batches are submit via synchronous callback back to the user. This function is called
-// from inside `spritebatch_flush`. Each time `submit_batch_fn` is called an array of sprites
-// is handed to the user. The sprites are intended to be further sorted by the user as desired
-// (for example, additional sorting based on depth). `w` and `h` are the width/height, respectively,
-// of the texture the batch of sprites resides upon. w/h can be useful for knowing texture dim-
-// ensions, which is needed to know texel size or other measurements.
-typedef void (submit_batch_fn)(spritebatch_sprite_t* sprites, int count, int texture_w, int texture_h, void* udata);
-
-// cute_spritebatch.h needs to know how to get the pixels of an image, generate textures handles (for
-// example glGenTextures for OpenGL), and destroy texture handles. These functions are all called
-// from within the `spritebatch_defrag` function, and sometimes from `spritebatch_flush`.
-
-// Called when the pixels are needed from the user. `image_id` maps to a unique image, and is *not*
-// related to `texture_id` at all. The `texture_id` is the value returned by this function. `buffer`
-// must be filled in with `bytes_to_fill` number of bytes. The user is assumed to know the
-// width/height of the image, and can optionally verify that `bytes_to_fill` matches the user's
-// w * h * stride for this particular image.
-typedef void (get_pixels_fn)(SPRITEBATCH_U64 image_id, void* buffer, int bytes_to_fill, void* udata);
-
-// Called with a new texture handle is needed. This will happen whenever a new atlas is created,
-// and whenever new `image_id`s first appear to cute_spritebatch, and have yet to find their way
-// into an appropriate atlas.
-typedef SPRITEBATCH_U64 (generate_texture_handle_fn)(void* pixels, int w, int h, void* udata);
-
-// Called whenever a texture handle is ready to be free'd up. This happens whenever a particular image
-// or a particular atlas has not been used for a while, and is ready to be released. `texture_id` is the
-// value returned by a previous call to `generate_texture_handle_fn`.
-typedef void (destroy_texture_handle_fn)(SPRITEBATCH_U64 texture_id, void* udata);
-
-// Sets all function pointers originally defined in the `config` struct when calling `spritebatch_init`.
-// Useful if DLL's are reloaded, or swapped, etc.
-void spritebatch_reset_function_ptrs(spritebatch_t* sb, submit_batch_fn* batch_callback, get_pixels_fn* get_pixels_callback, generate_texture_handle_fn* generate_texture_callback, destroy_texture_handle_fn* delete_texture_callback);
-
-// Initializes a set of good default paramaters. The users must still set
-// the four callbacks inside of `config`.
-void spritebatch_set_default_config(spritebatch_config_t* config);
-
-struct spritebatch_config_t
-{
- int pixel_stride;
- int atlas_width_in_pixels;
- int atlas_height_in_pixels;
- int atlas_use_border_pixels;
- int ticks_to_decay_texture; // number of ticks it takes for a texture handle to be destroyed via `destroy_texture_handle_fn`
- int lonely_buffer_count_till_flush; // number of unique textures until an atlas is constructed
- float ratio_to_decay_atlas; // from 0 to 1, once ratio is less than `ratio_to_decay_atlas`, flush active textures in atlas to lonely buffer
- float ratio_to_merge_atlases; // from 0 to 0.5, attempts to merge atlases with some ratio of empty space
- submit_batch_fn* batch_callback;
- get_pixels_fn* get_pixels_callback;
- generate_texture_handle_fn* generate_texture_callback;
- destroy_texture_handle_fn* delete_texture_callback;
- void* allocator_context;
-};
-
-struct spritebatch_sprite_t
-{
- // User-defined value to represent a unique sprite.
- SPRITEBATCH_U64 image_id;
-
- // Assigned by calling `generate_texture_handle_fn`. Does not map one-to-one with `image_id`,
- // since a single sprite can be drawn, allocate a `texture_id`, and then not be drawn again
- // for a long time. This would then trigger the `destroy_texture_handle_fn` and release the
- // previously used `texture_id`. Then later, if the sprite is drawn again it will allocate a
- // new `texture_id` by calling `generate_texture_handle_fn`.
- SPRITEBATCH_U64 texture_id;
-
- // User-defined sorting key, see: http://realtimecollisiondetection.net/blog/?p=86
- // The first 32-bits store the user's sort bits. The bottom 32-bits are for internal
- // usage, and are not ever set by the user. Internally sprites are sorted first
- // based on `sort_bits`, and to break ties they are sorted on `texture_id`. Feel free
- // to change the sort predicate `spritebatch_internal_instance_pred` in the
- // implementation section.
- SPRITEBATCH_U64 sort_bits;
-
- float x, y; // x and y position
- float sx, sy; // scale on x and y axis
- float c, s; // cosine and sine (represents cos(angle) and sin(angle))
- float minx, miny; // u coordinate
- float maxx, maxy; // v coordinate
-};
-
-#define SPRITEBATCH_H
-#endif
-
-#if !defined(SPRITE_BATCH_INTERNAL_H)
-
-// hashtable.h implementation by Mattias Gustavsson
-// See: http://www.mattiasgustavsson.com/ and https://github.com/mattiasgustavsson/libs/blob/master/hashtable.h
-// begin hashtable.h
-
-/*
-------------------------------------------------------------------------------
- Licensing information can be found at the end of the file.
-------------------------------------------------------------------------------
-
-hashtable.h - v1.1 - Cache efficient hash table implementation for C/C++.
-
-Do this:
- #define HASHTABLE_IMPLEMENTATION
-before you include this file in *one* C/C++ file to create the implementation.
-*/
-
-#ifndef hashtable_h
-#define hashtable_h
-
-#ifndef HASHTABLE_U64
- #define HASHTABLE_U64 unsigned long long
-#endif
-
-typedef struct hashtable_t hashtable_t;
-
-void hashtable_init( hashtable_t* table, int item_size, int initial_capacity, void* memctx );
-void hashtable_term( hashtable_t* table );
-
-void* hashtable_insert( hashtable_t* table, HASHTABLE_U64 key, void const* item );
-void hashtable_remove( hashtable_t* table, HASHTABLE_U64 key );
-void hashtable_clear( hashtable_t* table );
-
-void* hashtable_find( hashtable_t const* table, HASHTABLE_U64 key );
-
-int hashtable_count( hashtable_t const* table );
-void* hashtable_items( hashtable_t const* table );
-HASHTABLE_U64 const* hashtable_keys( hashtable_t const* table );
-
-void hashtable_swap( hashtable_t* table, int index_a, int index_b );
-
-
-#endif /* hashtable_h */
-
-/*
-----------------------
- IMPLEMENTATION
-----------------------
-*/
-
-#ifndef hashtable_t_h
-#define hashtable_t_h
-
-#ifndef HASHTABLE_U32
- #define HASHTABLE_U32 unsigned int
-#endif
-
-struct hashtable_internal_slot_t
- {
- HASHTABLE_U32 key_hash;
- int item_index;
- int base_count;
- };
-
-struct hashtable_t
- {
- void* memctx;
- int count;
- int item_size;
-
- struct hashtable_internal_slot_t* slots;
- int slot_capacity;
-
- HASHTABLE_U64* items_key;
- int* items_slot;
- void* items_data;
- int item_capacity;
-
- void* swap_temp;
- };
-
-#endif /* hashtable_t_h */
-
-// end hashtable.h (more later)
-
-typedef struct
-{
- SPRITEBATCH_U64 image_id;
- SPRITEBATCH_U64 sort_bits;
- int w;
- int h;
- float x, y;
- float sx, sy;
- float c, s;
-} spritebatch_internal_sprite_t;
-
-typedef struct
-{
- int timestamp;
- int w, h;
- float minx, miny;
- float maxx, maxy;
- SPRITEBATCH_U64 image_id;
-} spritebatch_internal_texture_t;
-
-typedef struct spritebatch_internal_atlas_t
-{
- SPRITEBATCH_U64 texture_id;
- float volume_ratio;
- hashtable_t sprites_to_textures;
- struct spritebatch_internal_atlas_t* next;
- struct spritebatch_internal_atlas_t* prev;
-} spritebatch_internal_atlas_t;
-
-typedef struct
-{
- int timestamp;
- int w, h;
- SPRITEBATCH_U64 image_id;
- SPRITEBATCH_U64 texture_id;
-} spritebatch_internal_lonely_texture_t;
-
-
-
-struct spritebatch_t
-{
- int input_count;
- int input_capacity;
- spritebatch_internal_sprite_t* input_buffer;
-
- int sprite_count;
- int sprite_capacity;
- spritebatch_sprite_t* sprites;
-
- int key_buffer_count;
- int key_buffer_capacity;
- SPRITEBATCH_U64* key_buffer;
-
- int pixel_buffer_size; // number of pixels
- void* pixel_buffer;
-
- hashtable_t sprites_to_lonely_textures;
- hashtable_t sprites_to_atlases;
-
- spritebatch_internal_atlas_t* atlases;
-
- int pixel_stride;
- int atlas_width_in_pixels;
- int atlas_height_in_pixels;
- int atlas_use_border_pixels;
- int ticks_to_decay_texture;
- int lonely_buffer_count_till_flush;
- int lonely_buffer_count_till_decay;
- float ratio_to_decay_atlas;
- float ratio_to_merge_atlases;
- submit_batch_fn* batch_callback;
- get_pixels_fn* get_pixels_callback;
- generate_texture_handle_fn* generate_texture_callback;
- destroy_texture_handle_fn* delete_texture_callback;
- void* mem_ctx;
- void* udata;
-};
-
-#ifndef _CRT_SECURE_NO_WARNINGS
- #define _CRT_SECURE_NO_WARNINGS
-#endif
-
-#ifndef _CRT_NONSTDC_NO_DEPRECATE
- #define _CRT_NONSTDC_NO_DEPRECATE
-#endif
-
-#ifndef SPRITEBATCH_MALLOC
- #include <stdlib.h>
- #define SPRITEBATCH_MALLOC(size, ctx) malloc(size)
- #define SPRITEBATCH_FREE(ptr, ctx) free(ptr)
-#endif
-
-#ifndef SPRITEBATCH_MEMCPY
- #include <string.h>
- #define SPRITEBATCH_MEMCPY(dst, src, n) memcpy(dst, src, n)
-#endif
-
-#ifndef SPRITEBATCH_MEMSET
- #include <string.h>
- #define SPRITEBATCH_MEMSET(ptr, val, n) memset(ptr, val, n)
-#endif
-
-#ifndef SPRITEBATCH_ASSERT
- #include <assert.h>
- #define SPRITEBATCH_ASSERT(condition) assert(condition)
-#endif
-
-// flips output uv coordinate's y. Can be useful to "flip image on load"
-#ifndef SPRITEBATCH_ATLAS_FLIP_Y_AXIS_FOR_UV
- #define SPRITEBATCH_ATLAS_FLIP_Y_AXIS_FOR_UV 1
-#endif
-
-// flips output uv coordinate's y. Can be useful to "flip image on load"
-#ifndef SPRITEBATCH_LONELY_FLIP_Y_AXIS_FOR_UV
- #define SPRITEBATCH_LONELY_FLIP_Y_AXIS_FOR_UV 1
-#endif
-
-#ifndef SPRITEBATCH_ATLAS_EMPTY_COLOR
- #define SPRITEBATCH_ATLAS_EMPTY_COLOR 0x000000FF
-#endif
-
-#ifndef SPRITEBATCH_LOG
- #if 0
- #define SPRITEBATCH_LOG printf
- #else
- #define SPRITEBATCH_LOG(...)
- #endif
-#endif
-
-#ifndef HASHTABLE_MEMSET
- #define HASHTABLE_MEMSET(ptr, val, n) SPRITEBATCH_MEMSET(ptr, val, n)
-#endif
-
-#ifndef HASHTABLE_MEMCPY
- #define HASHTABLE_MEMCPY(dst, src, n) SPRITEBATCH_MEMCPY(dst, src, n)
-#endif
-
-#ifndef HASHTABLE_MALLOC
- #define HASHTABLE_MALLOC(ctx, size) SPRITEBATCH_MALLOC(size, ctx)
-#endif
-
-#ifndef HASHTABLE_FREE
- #define HASHTABLE_FREE(ctx, ptr) SPRITEBATCH_FREE(ptr, ctx)
-#endif
-
-#define SPRITE_BATCH_INTERNAL_H
-#endif
-
-#ifdef SPRITEBATCH_IMPLEMENTATION
-#ifndef SPRITEBATCH_IMPLEMENTATION_ONCE
-#define SPRITEBATCH_IMPLEMENTATION_ONCE
-
-#define HASHTABLE_IMPLEMENTATION
-
-#ifdef HASHTABLE_IMPLEMENTATION
-#ifndef HASHTABLE_IMPLEMENTATION_ONCE
-#define HASHTABLE_IMPLEMENTATION_ONCE
-
-// hashtable.h implementation by Mattias Gustavsson
-// See: http://www.mattiasgustavsson.com/ and https://github.com/mattiasgustavsson/libs/blob/master/hashtable.h
-// begin hashtable.h (continuing from first time)
-
-#ifndef HASHTABLE_SIZE_T
- #include <stddef.h>
- #define HASHTABLE_SIZE_T size_t
-#endif
-
-#ifndef HASHTABLE_ASSERT
- #include <assert.h>
- #define HASHTABLE_ASSERT( x ) assert( x )
-#endif
-
-#ifndef HASHTABLE_MEMSET
- #include <string.h>
- #define HASHTABLE_MEMSET( ptr, val, cnt ) ( memset( ptr, val, cnt ) )
-#endif
-
-#ifndef HASHTABLE_MEMCPY
- #include <string.h>
- #define HASHTABLE_MEMCPY( dst, src, cnt ) ( memcpy( dst, src, cnt ) )
-#endif
-
-#ifndef HASHTABLE_MALLOC
- #include <stdlib.h>
- #define HASHTABLE_MALLOC( ctx, size ) ( malloc( size ) )
- #define HASHTABLE_FREE( ctx, ptr ) ( free( ptr ) )
-#endif
-
-
-static HASHTABLE_U32 hashtable_internal_pow2ceil( HASHTABLE_U32 v )
- {
- --v;
- v |= v >> 1;
- v |= v >> 2;
- v |= v >> 4;
- v |= v >> 8;
- v |= v >> 16;
- ++v;
- v += ( v == 0 );
- return v;
- }
-
-
-void hashtable_init( hashtable_t* table, int item_size, int initial_capacity, void* memctx )
- {
- initial_capacity = (int)hashtable_internal_pow2ceil( initial_capacity >=0 ? (HASHTABLE_U32) initial_capacity : 32U );
- table->memctx = memctx;
- table->count = 0;
- table->item_size = item_size;
- table->slot_capacity = (int) hashtable_internal_pow2ceil( (HASHTABLE_U32) ( initial_capacity + initial_capacity / 2 ) );
- int slots_size = (int)( table->slot_capacity * sizeof( *table->slots ) );
- table->slots = (struct hashtable_internal_slot_t*) HASHTABLE_MALLOC( table->memctx, (HASHTABLE_SIZE_T) slots_size );
- HASHTABLE_ASSERT( table->slots );
- HASHTABLE_MEMSET( table->slots, 0, (HASHTABLE_SIZE_T) slots_size );
- table->item_capacity = (int) hashtable_internal_pow2ceil( (HASHTABLE_U32) initial_capacity );
- table->items_key = (HASHTABLE_U64*) HASHTABLE_MALLOC( table->memctx,
- table->item_capacity * ( sizeof( *table->items_key ) + sizeof( *table->items_slot ) + table->item_size ) + table->item_size );
- HASHTABLE_ASSERT( table->items_key );
- table->items_slot = (int*)( table->items_key + table->item_capacity );
- table->items_data = (void*)( table->items_slot + table->item_capacity );
- table->swap_temp = (void*)( ( (uintptr_t) table->items_data ) + table->item_size * table->item_capacity );
- }
-
-
-void hashtable_term( hashtable_t* table )
- {
- HASHTABLE_FREE( table->memctx, table->items_key );
- HASHTABLE_FREE( table->memctx, table->slots );
- }
-
-
-// from https://gist.github.com/badboy/6267743
-static HASHTABLE_U32 hashtable_internal_calculate_hash( HASHTABLE_U64 key )
- {
- key = ( ~key ) + ( key << 18 );
- key = key ^ ( key >> 31 );
- key = key * 21;
- key = key ^ ( key >> 11 );
- key = key + ( key << 6 );
- key = key ^ ( key >> 22 );
- HASHTABLE_ASSERT( key );
- return (HASHTABLE_U32) key;
- }
-
-
-static int hashtable_internal_find_slot( hashtable_t const* table, HASHTABLE_U64 key )
- {
- int const slot_mask = table->slot_capacity - 1;
- HASHTABLE_U32 const hash = hashtable_internal_calculate_hash( key );
-
- int const base_slot = (int)( hash & (HASHTABLE_U32)slot_mask );
- int base_count = table->slots[ base_slot ].base_count;
- int slot = base_slot;
-
- while( base_count > 0 )
- {
- HASHTABLE_U32 slot_hash = table->slots[ slot ].key_hash;
- if( slot_hash )
- {
- int slot_base = (int)( slot_hash & (HASHTABLE_U32)slot_mask );
- if( slot_base == base_slot )
- {
- HASHTABLE_ASSERT( base_count > 0 );
- --base_count;
- if( slot_hash == hash && table->items_key[ table->slots[ slot ].item_index ] == key )
- return slot;
- }
- }
- slot = ( slot + 1 ) & slot_mask;
- }
-
- return -1;
- }
-
-
-static void hashtable_internal_expand_slots( hashtable_t* table )
- {
- int const old_capacity = table->slot_capacity;
- struct hashtable_internal_slot_t* old_slots = table->slots;
-
- table->slot_capacity *= 2;
- int const slot_mask = table->slot_capacity - 1;
-
- int const size = (int)( table->slot_capacity * sizeof( *table->slots ) );
- table->slots = (struct hashtable_internal_slot_t*) HASHTABLE_MALLOC( table->memctx, (HASHTABLE_SIZE_T) size );
- HASHTABLE_ASSERT( table->slots );
- HASHTABLE_MEMSET( table->slots, 0, (HASHTABLE_SIZE_T) size );
-
- for( int i = 0; i < old_capacity; ++i )
- {
- HASHTABLE_U32 const hash = old_slots[ i ].key_hash;
- if( hash )
- {
- int const base_slot = (int)( hash & (HASHTABLE_U32)slot_mask );
- int slot = base_slot;
- while( table->slots[ slot ].key_hash )
- slot = ( slot + 1 ) & slot_mask;
- table->slots[ slot ].key_hash = hash;
- int item_index = old_slots[ i ].item_index;
- table->slots[ slot ].item_index = item_index;
- table->items_slot[ item_index ] = slot;
- ++table->slots[ base_slot ].base_count;
- }
- }
-
- HASHTABLE_FREE( table->memctx, old_slots );
- }
-
-
-static void hashtable_internal_expand_items( hashtable_t* table )
- {
- table->item_capacity *= 2;
- HASHTABLE_U64* const new_items_key = (HASHTABLE_U64*) HASHTABLE_MALLOC( table->memctx,
- table->item_capacity * ( sizeof( *table->items_key ) + sizeof( *table->items_slot ) + table->item_size ) + table->item_size);
- HASHTABLE_ASSERT( new_items_key );
-
- int* const new_items_slot = (int*)( new_items_key + table->item_capacity );
- void* const new_items_data = (void*)( new_items_slot + table->item_capacity );
- void* const new_swap_temp = (void*)( ( (uintptr_t) new_items_data ) + table->item_size * table->item_capacity );
-
- HASHTABLE_MEMCPY( new_items_key, table->items_key, table->count * sizeof( *table->items_key ) );
- HASHTABLE_MEMCPY( new_items_slot, table->items_slot, table->count * sizeof( *table->items_key ) );
- HASHTABLE_MEMCPY( new_items_data, table->items_data, (HASHTABLE_SIZE_T) table->count * table->item_size );
-
- HASHTABLE_FREE( table->memctx, table->items_key );
-
- table->items_key = new_items_key;
- table->items_slot = new_items_slot;
- table->items_data = new_items_data;
- table->swap_temp = new_swap_temp;
- }
-
-
-void* hashtable_insert( hashtable_t* table, HASHTABLE_U64 key, void const* item )
- {
- HASHTABLE_ASSERT( hashtable_internal_find_slot( table, key ) < 0 );
-
- if( table->count >= ( table->slot_capacity - table->slot_capacity / 3 ) )
- hashtable_internal_expand_slots( table );
-
- int const slot_mask = table->slot_capacity - 1;
- HASHTABLE_U32 const hash = hashtable_internal_calculate_hash( key );
-
- int const base_slot = (int)( hash & (HASHTABLE_U32)slot_mask );
- int base_count = table->slots[ base_slot ].base_count;
- int slot = base_slot;
- int first_free = slot;
- while( base_count )
- {
- HASHTABLE_U32 const slot_hash = table->slots[ slot ].key_hash;
- if( slot_hash == 0 && table->slots[ first_free ].key_hash != 0 ) first_free = slot;
- int slot_base = (int)( slot_hash & (HASHTABLE_U32)slot_mask );
- if( slot_base == base_slot )
- --base_count;
- slot = ( slot + 1 ) & slot_mask;
- }
-
- slot = first_free;
- while( table->slots[ slot ].key_hash )
- slot = ( slot + 1 ) & slot_mask;
-
- if( table->count >= table->item_capacity )
- hashtable_internal_expand_items( table );
-
- HASHTABLE_ASSERT( !table->slots[ slot ].key_hash && ( hash & (HASHTABLE_U32) slot_mask ) == (HASHTABLE_U32) base_slot );
- HASHTABLE_ASSERT( hash );
- table->slots[ slot ].key_hash = hash;
- table->slots[ slot ].item_index = table->count;
- ++table->slots[ base_slot ].base_count;
-
-
- void* dest_item = (void*)( ( (uintptr_t) table->items_data ) + table->count * table->item_size );
- memcpy( dest_item, item, (HASHTABLE_SIZE_T) table->item_size );
- table->items_key[ table->count ] = key;
- table->items_slot[ table->count ] = slot;
- ++table->count;
- return dest_item;
- }
-
-
-void hashtable_remove( hashtable_t* table, HASHTABLE_U64 key )
- {
- int const slot = hashtable_internal_find_slot( table, key );
- HASHTABLE_ASSERT( slot >= 0 );
-
- int const slot_mask = table->slot_capacity - 1;
- HASHTABLE_U32 const hash = table->slots[ slot ].key_hash;
- int const base_slot = (int)( hash & (HASHTABLE_U32) slot_mask );
- HASHTABLE_ASSERT( hash );
- --table->slots[ base_slot ].base_count;
- table->slots[ slot ].key_hash = 0;
-
- int index = table->slots[ slot ].item_index;
- int last_index = table->count - 1;
- if( index != last_index )
- {
- table->items_key[ index ] = table->items_key[ last_index ];
- table->items_slot[ index ] = table->items_slot[ last_index ];
- void* dst_item = (void*)( ( (uintptr_t) table->items_data ) + index * table->item_size );
- void* src_item = (void*)( ( (uintptr_t) table->items_data ) + last_index * table->item_size );
- HASHTABLE_MEMCPY( dst_item, src_item, (HASHTABLE_SIZE_T) table->item_size );
- table->slots[ table->items_slot[ last_index ] ].item_index = index;
- }
- --table->count;
- }
-
-
-void hashtable_clear( hashtable_t* table )
- {
- table->count = 0;
- HASHTABLE_MEMSET( table->slots, 0, table->slot_capacity * sizeof( *table->slots ) );
- }
-
-
-void* hashtable_find( hashtable_t const* table, HASHTABLE_U64 key )
- {
- int const slot = hashtable_internal_find_slot( table, key );
- if( slot < 0 ) return 0;
-
- int const index = table->slots[ slot ].item_index;
- void* const item = (void*)( ( (uintptr_t) table->items_data ) + index * table->item_size );
- return item;
- }
-
-
-int hashtable_count( hashtable_t const* table )
- {
- return table->count;
- }
-
-
-void* hashtable_items( hashtable_t const* table )
- {
- return table->items_data;
- }
-
-
-HASHTABLE_U64 const* hashtable_keys( hashtable_t const* table )
- {
- return table->items_key;
- }
-
-
-void hashtable_swap( hashtable_t* table, int index_a, int index_b )
- {
- if( index_a < 0 || index_a >= table->count || index_b < 0 || index_b >= table->count ) return;
-
- int slot_a = table->items_slot[ index_a ];
- int slot_b = table->items_slot[ index_b ];
-
- table->items_slot[ index_a ] = slot_b;
- table->items_slot[ index_b ] = slot_a;
-
- HASHTABLE_U64 temp_key = table->items_key[ index_a ];
- table->items_key[ index_a ] = table->items_key[ index_b ];
- table->items_key[ index_b ] = temp_key;
-
- void* item_a = (void*)( ( (uintptr_t) table->items_data ) + index_a * table->item_size );
- void* item_b = (void*)( ( (uintptr_t) table->items_data ) + index_b * table->item_size );
- HASHTABLE_MEMCPY( table->swap_temp, item_a, table->item_size );
- HASHTABLE_MEMCPY( item_a, item_b, table->item_size );
- HASHTABLE_MEMCPY( item_b, table->swap_temp, table->item_size );
-
- table->slots[ slot_a ].item_index = index_b;
- table->slots[ slot_b ].item_index = index_a;
- }
-
-
-#endif /* HASHTABLE_IMPLEMENTATION */
-#endif // HASHTABLE_IMPLEMENTATION_ONCE
-
-/*
-
-contributors:
- Randy Gaul (hashtable_clear, hashtable_swap )
-
-revision history:
- 1.1 added hashtable_clear, hashtable_swap
- 1.0 first released version
-
-*/
-
-/*
-------------------------------------------------------------------------------
-
-This software is available under 2 licenses - you may choose the one you like.
-
-------------------------------------------------------------------------------
-
-ALTERNATIVE A - MIT License
-
-Copyright (c) 2015 Mattias Gustavsson
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-
-------------------------------------------------------------------------------
-
-ALTERNATIVE B - Public Domain (www.unlicense.org)
-
-This is free and unencumbered software released into the public domain.
-
-Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-software, either in source code form or as a compiled binary, for any purpose,
-commercial or non-commercial, and by any means.
-
-In jurisdictions that recognize copyright laws, the author or authors of this
-software dedicate any and all copyright interest in the software to the public
-domain. We make this dedication for the benefit of the public at large and to
-the detriment of our heirs and successors. We intend this dedication to be an
-overt act of relinquishment in perpetuity of all present and future rights to
-this software under copyright law.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-------------------------------------------------------------------------------
-*/
-
-// end of hashtable.h
-
-
-#include <limits.h>
-
-int spritebatch_init(spritebatch_t* sb, spritebatch_config_t* config, void* udata)
-{
- // read config params
- if (!config | !sb) return 1;
- sb->pixel_stride = config->pixel_stride;
- sb->atlas_width_in_pixels = config->atlas_width_in_pixels;
- sb->atlas_height_in_pixels = config->atlas_height_in_pixels;
- sb->atlas_use_border_pixels = config->atlas_use_border_pixels;
- sb->ticks_to_decay_texture = config->ticks_to_decay_texture;
- sb->lonely_buffer_count_till_flush = config->lonely_buffer_count_till_flush;
- sb->lonely_buffer_count_till_decay = sb->lonely_buffer_count_till_flush / 2;
- if (sb->lonely_buffer_count_till_decay <= 0) sb->lonely_buffer_count_till_decay = 1;
- sb->ratio_to_decay_atlas = config->ratio_to_decay_atlas;
- sb->ratio_to_merge_atlases = config->ratio_to_merge_atlases;
- sb->batch_callback = config->batch_callback;
- sb->get_pixels_callback = config->get_pixels_callback;
- sb->generate_texture_callback = config->generate_texture_callback;
- sb->delete_texture_callback = config->delete_texture_callback;
- sb->mem_ctx = config->allocator_context;
- sb->udata = udata;
-
- if (sb->atlas_width_in_pixels < 1 || sb->atlas_height_in_pixels < 1) return 1;
- if (sb->ticks_to_decay_texture < 1) return 1;
- if (sb->ratio_to_decay_atlas < 0 || sb->ratio_to_decay_atlas > 1.0f) return 1;
- if (sb->ratio_to_merge_atlases < 0 || sb->ratio_to_merge_atlases > 0.5f) return 1;
- if (!sb->batch_callback) return 1;
- if (!sb->get_pixels_callback) return 1;
- if (!sb->generate_texture_callback) return 1;
- if (!sb->delete_texture_callback) return 1;
-
- // initialize input buffer
- sb->input_count = 0;
- sb->input_capacity = 1024;
- sb->input_buffer = (spritebatch_internal_sprite_t*)SPRITEBATCH_MALLOC(sizeof(spritebatch_internal_sprite_t) * sb->input_capacity, sb->mem_ctx);
- if (!sb->input_buffer) return 1;
-
- // initialize sprite buffer
- sb->sprite_count = 0;
- sb->sprite_capacity = 1024;
- sb->sprites = (spritebatch_sprite_t*)SPRITEBATCH_MALLOC(sizeof(spritebatch_sprite_t) * sb->sprite_capacity, sb->mem_ctx);
- if (!sb->sprites) return 1;
-
- // initialize key buffer (for marking hash table entries for deletion)
- sb->key_buffer_count = 0;
- sb->key_buffer_capacity = 1024;
- sb->key_buffer = (SPRITEBATCH_U64*)SPRITEBATCH_MALLOC(sizeof(SPRITEBATCH_U64) * sb->key_buffer_capacity, sb->mem_ctx);
-
- // initialize pixel buffer for grabbing pixel data from the user as needed
- sb->pixel_buffer_size = 1024;
- sb->pixel_buffer = SPRITEBATCH_MALLOC(sb->pixel_buffer_size * sb->pixel_stride, sb->mem_ctx);
-
- // setup tables
- hashtable_init(&sb->sprites_to_lonely_textures, sizeof(spritebatch_internal_lonely_texture_t), 1024, sb->mem_ctx);
- hashtable_init(&sb->sprites_to_atlases, sizeof(spritebatch_internal_atlas_t*), 16, sb->mem_ctx);
-
- sb->atlases = 0;
-
- return 0;
-}
-
-void spritebatch_term(spritebatch_t* sb)
-{
- SPRITEBATCH_FREE(sb->input_buffer, sb->mem_ctx);
- SPRITEBATCH_FREE(sb->sprites, sb->mem_ctx);
- SPRITEBATCH_FREE(sb->key_buffer, sb->mem_ctx);
- SPRITEBATCH_FREE(sb->pixel_buffer, ctx->mem_ctx);
- hashtable_term(&sb->sprites_to_lonely_textures);
- hashtable_term(&sb->sprites_to_atlases);
-
- if (sb->atlases)
- {
- spritebatch_internal_atlas_t* atlas = sb->atlases;
- spritebatch_internal_atlas_t* sentinel = sb->atlases;
- do
- {
- hashtable_term(&atlas->sprites_to_textures);
- spritebatch_internal_atlas_t* next = atlas->next;
- SPRITEBATCH_FREE(atlas, sb->mem_ctx);
- atlas = next;
- }
- while (atlas != sentinel);
- }
-
- SPRITEBATCH_MEMSET(sb, 0, sizeof(spritebatch_t));
-}
-
-void spritebatch_reset_function_ptrs(spritebatch_t* sb, submit_batch_fn* batch_callback, get_pixels_fn* get_pixels_callback, generate_texture_handle_fn* generate_texture_callback, destroy_texture_handle_fn* delete_texture_callback)
-{
- sb->batch_callback = batch_callback;
- sb->get_pixels_callback = get_pixels_callback;
- sb->generate_texture_callback = generate_texture_callback;
- sb->delete_texture_callback = delete_texture_callback;
-}
-
-void spritebatch_set_default_config(spritebatch_config_t* config)
-{
- config->pixel_stride = sizeof(char) * 4;
- config->atlas_width_in_pixels = 1024;
- config->atlas_height_in_pixels = 1024;
- config->atlas_use_border_pixels = 0;
- config->ticks_to_decay_texture = 60 * 30;
- config->lonely_buffer_count_till_flush = 64;
- config->ratio_to_decay_atlas = 0.5f;
- config->ratio_to_merge_atlases = 0.25f;
- config->batch_callback = 0;
- config->generate_texture_callback = 0;
- config->delete_texture_callback = 0;
- config->allocator_context = 0;
-}
-
-#define SPRITEBATCH_CHECK_BUFFER_GROW(ctx, count, capacity, data, type) \
- do { \
- if (ctx->count == ctx->capacity) \
- { \
- int new_capacity = ctx->capacity * 2; \
- void* new_data = SPRITEBATCH_MALLOC(sizeof(type) * new_capacity, ctx->mem_ctx); \
- if (!new_data) return 0; \
- SPRITEBATCH_MEMCPY(new_data, ctx->data, sizeof(type) * ctx->count); \
- SPRITEBATCH_FREE(ctx->data, ctx->mem_ctx); \
- ctx->data = (type*)new_data; \
- ctx->capacity = new_capacity; \
- } \
- } while (0)
-
-static SPRITEBATCH_U64 spritebatch_make_sort_key(int index, int sort_bits)
-{
- return (((SPRITEBATCH_U64)sort_bits) << 32) | ((SPRITEBATCH_U64)index);
-}
-
-int spritebatch_push(spritebatch_t* sb, SPRITEBATCH_U64 image_id, int w, int h, float x, float y, float sx, float sy, float c, float s, int sort_bits)
-{
- SPRITEBATCH_CHECK_BUFFER_GROW(sb, input_count, input_capacity, input_buffer, spritebatch_internal_sprite_t);
- spritebatch_internal_sprite_t sprite;
- sprite.image_id = image_id;
- sprite.sort_bits = spritebatch_make_sort_key(sb->input_count, sort_bits);
- sprite.w = w;
- sprite.h = h;
- sprite.x = x;
- sprite.y = y;
- sprite.sx = sx + (sb->atlas_use_border_pixels ? (sx / (float)w) * 2.0f : 0);
- sprite.sy = sy + (sb->atlas_use_border_pixels ? (sy / (float)h) * 2.0f : 0);
- sprite.c = c;
- sprite.s = s;
- sb->input_buffer[sb->input_count++] = sprite;
- return 1;
-}
-
-static int spritebatch_internal_instance_pred(spritebatch_sprite_t* a, spritebatch_sprite_t* b)
-{
- if (a->sort_bits < b->sort_bits) return 1;
- else if(a->sort_bits == b->sort_bits) return a->texture_id < b->texture_id;
- else return 0;
-}
-
-static void spritebatch_internal_qsort_sprites(spritebatch_sprite_t* items, int count)
-{
- if (count <= 1) return;
-
- spritebatch_sprite_t pivot = items[count - 1];
- int low = 0;
- for (int i = 0; i < count - 1; ++i)
- {
- if (spritebatch_internal_instance_pred(items + i, &pivot))
- {
- spritebatch_sprite_t tmp = items[i];
- items[i] = items[low];
- items[low] = tmp;
- low++;
- }
- }
-
- items[count - 1] = items[low];
- items[low] = pivot;
- spritebatch_internal_qsort_sprites(items, low);
- spritebatch_internal_qsort_sprites(items + low + 1, count - 1 - low);
-}
-
-static inline void spritebatch_internal_get_pixels(spritebatch_t* sb, SPRITEBATCH_U64 image_id, int w, int h)
-{
- int size = sb->atlas_use_border_pixels ? sb->pixel_stride * (w + 2) * (h + 2) : sb->pixel_stride * w * h;
- if (size > sb->pixel_buffer_size)
- {
- SPRITEBATCH_FREE(sb->pixel_buffer, ctx->mem_ctx);
- sb->pixel_buffer_size = size;
- sb->pixel_buffer = SPRITEBATCH_MALLOC(sb->pixel_buffer_size, ctx->mem_ctx);
- if (!sb->pixel_buffer) return;
- }
-
- memset(sb->pixel_buffer, 0, size);
- int size_from_user = sb->pixel_stride * w * h;
- sb->get_pixels_callback(image_id, sb->pixel_buffer, size_from_user, sb->udata);
-
- if (sb->atlas_use_border_pixels) {
- // Expand image from top-left corner, offset by (1, 1).
- int w0 = w;
- int h0 = h;
- w += 2;
- h += 2;
- char* buffer = (char*)sb->pixel_buffer;
- int dst_row_stride = w * sb->pixel_stride;
- int src_row_stride = w0 * sb->pixel_stride;
- int src_row_offset = sb->pixel_stride;
- for (int i = 0; i < h - 2; ++i)
- {
- char* src_row = buffer + (h0 - i - 1) * src_row_stride;
- char* dst_row = buffer + (h - i - 2) * dst_row_stride + src_row_offset;
- memmove(dst_row, src_row, src_row_stride);
- }
-
- // Clear the border pixels.
- int pixel_stride = sb->pixel_stride;
- memset(buffer, 0, dst_row_stride);
- for (int i = 1; i < h - 1; ++i)
- {
- memset(buffer + i * dst_row_stride, 0, pixel_stride);
- memset(buffer + i * dst_row_stride + src_row_stride + src_row_offset, 0, pixel_stride);
- }
- memset(buffer + (h - 1) * dst_row_stride, 0, dst_row_stride);
- }
-}
-
-static inline SPRITEBATCH_U64 spritebatch_internal_generate_texture_handle(spritebatch_t* sb, SPRITEBATCH_U64 image_id, int w, int h)
-{
- spritebatch_internal_get_pixels(sb, image_id, w, h);
- if (sb->atlas_use_border_pixels)
- {
- w += 2;
- h += 2;
- }
- return sb->generate_texture_callback(sb->pixel_buffer, w, h, sb->udata);
-}
-
-spritebatch_internal_lonely_texture_t* spritebatch_internal_lonelybuffer_push(spritebatch_t* sb, SPRITEBATCH_U64 image_id, int w, int h, int make_tex)
-{
- spritebatch_internal_lonely_texture_t texture;
- texture.timestamp = 0;
- texture.w = w;
- texture.h = h;
- texture.image_id = image_id;
- texture.texture_id = make_tex ? spritebatch_internal_generate_texture_handle(sb, image_id, w, h) : ~0;
- return (spritebatch_internal_lonely_texture_t*)hashtable_insert(&sb->sprites_to_lonely_textures, image_id, &texture);
-}
-
-int spritebatch_internal_lonely_sprite(spritebatch_t* sb, spritebatch_internal_sprite_t* s, spritebatch_sprite_t* sprite, int skip_missing_textures)
-{
- spritebatch_internal_lonely_texture_t* tex = (spritebatch_internal_lonely_texture_t*)hashtable_find(&sb->sprites_to_lonely_textures, s->image_id);
-
- if (skip_missing_textures)
- {
- if (!tex) spritebatch_internal_lonelybuffer_push(sb, s->image_id, s->w, s->h, 0);
- return 1;
- }
-
- else
- {
- if (!tex) tex = spritebatch_internal_lonelybuffer_push(sb, s->image_id, s->w, s->h, 1);
- else if (tex->texture_id == ~0) tex->texture_id = spritebatch_internal_generate_texture_handle(sb, s->image_id, s->w, s->h);
- tex->timestamp = 0;
- sprite->texture_id = tex->texture_id;
- sprite->minx = sprite->miny = 0;
- sprite->maxx = sprite->maxy = 1.0f;
-
- if (SPRITEBATCH_LONELY_FLIP_Y_AXIS_FOR_UV)
- {
- float tmp = sprite->miny;
- sprite->miny = sprite->maxy;
- sprite->maxy = tmp;
- }
-
- return 0;
- }
-}
-
-int spritebatch_internal_push_sprite(spritebatch_t* sb, spritebatch_internal_sprite_t* s, int skip_missing_textures)
-{
- int skipped_tex = 0;
- spritebatch_sprite_t sprite;
- sprite.image_id = s->image_id;
- sprite.sort_bits = s->sort_bits;
- sprite.x = s->x;
- sprite.y = s->y;
- sprite.sx = s->sx;
- sprite.sy = s->sy;
- sprite.c = s->c;
- sprite.s = s->s;
-
- void* atlas_ptr = hashtable_find(&sb->sprites_to_atlases, s->image_id);
- if (atlas_ptr)
- {
- spritebatch_internal_atlas_t* atlas = *(spritebatch_internal_atlas_t**)atlas_ptr;
- sprite.texture_id = atlas->texture_id;
-
- spritebatch_internal_texture_t* tex = (spritebatch_internal_texture_t*)hashtable_find(&atlas->sprites_to_textures, s->image_id);
- SPRITEBATCH_ASSERT(tex);
- tex->timestamp = 0;
- tex->w = s->w;
- tex->h = s->h;
- sprite.minx = tex->minx;
- sprite.miny = tex->miny;
- sprite.maxx = tex->maxx;
- sprite.maxy = tex->maxy;
- }
-
- else skipped_tex = spritebatch_internal_lonely_sprite(sb, s, &sprite, skip_missing_textures);
-
- if (!skipped_tex)
- {
- SPRITEBATCH_CHECK_BUFFER_GROW(sb, sprite_count, sprite_capacity, sprites, spritebatch_sprite_t);
- sb->sprites[sb->sprite_count++] = sprite;
- }
- return skipped_tex;
-}
-
-void spritebatch_internal_process_input(spritebatch_t* sb, int skip_missing_textures)
-{
- int skipped_index = 0;
- for (int i = 0; i < sb->input_count; ++i)
- {
- spritebatch_internal_sprite_t* s = sb->input_buffer + i;
- int skipped = spritebatch_internal_push_sprite(sb, s, skip_missing_textures);
- if (skip_missing_textures && skipped) sb->input_buffer[skipped_index++] = *s;
- }
-
- sb->input_count = skipped_index;
-}
-
-void spritebatch_tick(spritebatch_t* sb)
-{
- spritebatch_internal_atlas_t* atlas = sb->atlases;
- if (atlas)
- {
- spritebatch_internal_atlas_t* sentinel = atlas;
- do
- {
- int texture_count = hashtable_count(&atlas->sprites_to_textures);
- spritebatch_internal_texture_t* textures = (spritebatch_internal_texture_t*)hashtable_items(&atlas->sprites_to_textures);
- for (int i = 0; i < texture_count; ++i) textures[i].timestamp += 1;
- atlas = atlas->next;
- }
- while (atlas != sentinel);
- }
-
- int texture_count = hashtable_count(&sb->sprites_to_lonely_textures);
- spritebatch_internal_lonely_texture_t* lonely_textures = (spritebatch_internal_lonely_texture_t*)hashtable_items(&sb->sprites_to_lonely_textures);
- for (int i = 0; i < texture_count; ++i) lonely_textures[i].timestamp += 1;
-}
-
-int spritebatch_flush(spritebatch_t* sb)
-{
- // process input buffer, make any necessary lonely textures
- // convert user sprites to internal format
- // lookup uv coordinates
- spritebatch_internal_process_input(sb, 0);
-
- // patchup any missing lonely textures that may have come from atlases decaying and whatnot
- int texture_count = hashtable_count(&sb->sprites_to_lonely_textures);
- spritebatch_internal_lonely_texture_t* lonely_textures = (spritebatch_internal_lonely_texture_t*)hashtable_items(&sb->sprites_to_lonely_textures);
- for (int i = 0; i < texture_count; ++i)
- {
- spritebatch_internal_lonely_texture_t* lonely = lonely_textures + i;
- if (lonely->texture_id == ~0) lonely->texture_id = spritebatch_internal_generate_texture_handle(sb, lonely->image_id, lonely->w, lonely->h);
- }
-
- // sort internal sprite buffer and submit batches
- spritebatch_internal_qsort_sprites(sb->sprites, sb->sprite_count);
-
- int min = 0;
- int max = 0;
- int done = !sb->sprite_count;
- int count = 0;
- while (!done)
- {
- SPRITEBATCH_U64 id = sb->sprites[min].texture_id;
- SPRITEBATCH_U64 image_id = sb->sprites[min].image_id;
-
- while (1)
- {
- if (max == sb->sprite_count)
- {
- done = 1;
- break;
- }
-
- if (id != sb->sprites[max].texture_id)
- break;
-
- ++max;
- }
-
- int batch_count = max - min;
- if (batch_count)
- {
- void* atlas_ptr = hashtable_find(&sb->sprites_to_atlases, image_id);
- int w, h;
-
- if (atlas_ptr)
- {
- w = sb->atlas_width_in_pixels;
- h = sb->atlas_height_in_pixels;
- }
-
- else
- {
- spritebatch_internal_lonely_texture_t* tex = (spritebatch_internal_lonely_texture_t*)hashtable_find(&sb->sprites_to_lonely_textures, image_id);
- SPRITEBATCH_ASSERT(tex);
- w = tex->w;
- h = tex->h;
- if (sb->atlas_use_border_pixels)
- {
- w += 2;
- h += 2;
- }
- }
-
- sb->batch_callback(sb->sprites + min, batch_count, w, h, sb->udata);
- ++count;
- }
- min = max;
- }
-
- sb->sprite_count = 0;
-
- return count;
-}
-
-typedef struct
-{
- int x;
- int y;
-} spritebatch_v2_t;
-
-typedef struct
-{
- int img_index;
- spritebatch_v2_t size;
- spritebatch_v2_t min;
- spritebatch_v2_t max;
- int fit;
-} spritebatch_internal_integer_image_t;
-
-static spritebatch_v2_t spritebatch_v2(int x, int y)
-{
- spritebatch_v2_t v;
- v.x = x;
- v.y = y;
- return v;
-}
-
-static spritebatch_v2_t spritebatch_sub(spritebatch_v2_t a, spritebatch_v2_t b)
-{
- spritebatch_v2_t v;
- v.x = a.x - b.x;
- v.y = a.y - b.y;
- return v;
-}
-
-static spritebatch_v2_t spritebatch_add(spritebatch_v2_t a, spritebatch_v2_t b)
-{
- spritebatch_v2_t v;
- v.x = a.x + b.x;
- v.y = a.y + b.y;
- return v;
-}
-
-typedef struct
-{
- spritebatch_v2_t size;
- spritebatch_v2_t min;
- spritebatch_v2_t max;
-} spritebatch_internal_atlas_node_t;
-
-static spritebatch_internal_atlas_node_t* spritebatch_best_fit(int sp, int w, int h, spritebatch_internal_atlas_node_t* nodes)
-{
- int best_volume = INT_MAX;
- spritebatch_internal_atlas_node_t *best_node = 0;
- int img_volume = w * h;
-
- for ( int i = 0; i < sp; ++i )
- {
- spritebatch_internal_atlas_node_t *node = nodes + i;
- int can_contain = node->size.x >= w && node->size.y >= h;
- if ( can_contain )
- {
- int node_volume = node->size.x * node->size.y;
- if ( node_volume == img_volume ) return node;
- if ( node_volume < best_volume )
- {
- best_volume = node_volume;
- best_node = node;
- }
- }
- }
-
- return best_node;
-}
-
-static int spritebatch_internal_perimeter_pred(spritebatch_internal_integer_image_t* a, spritebatch_internal_integer_image_t* b)
-{
- int perimeterA = 2 * (a->size.x + a->size.y);
- int perimeterB = 2 * (b->size.x + b->size.y);
- return perimeterB < perimeterA;
-}
-
-static void spritebatch_internal_image_sort(spritebatch_internal_integer_image_t* items, int count)
-{
- if (count <= 1) return;
-
- spritebatch_internal_integer_image_t pivot = items[count - 1];
- int low = 0;
- for (int i = 0; i < count - 1; ++i)
- {
- if (spritebatch_internal_perimeter_pred(items + i, &pivot))
- {
- spritebatch_internal_integer_image_t tmp = items[i];
- items[i] = items[low];
- items[low] = tmp;
- low++;
- }
- }
-
- items[count - 1] = items[low];
- items[low] = pivot;
- spritebatch_internal_image_sort(items, low);
- spritebatch_internal_image_sort(items + low + 1, count - 1 - low);
-}
-
-typedef struct
-{
- int img_index; // index into the `imgs` array
- int w, h; // pixel w/h of original image
- float minx, miny; // u coordinate
- float maxx, maxy; // v coordinate
- int fit; // non-zero if image fit and was placed into the atlas
-} spritebatch_internal_atlas_image_t;
-
-#define SPRITEBATCH_CHECK( X, Y ) do { if ( !(X) ) { SPRITEBATCH_LOG(Y); goto sb_err; } } while ( 0 )
-
-void spritebatch_make_atlas(spritebatch_t* sb, spritebatch_internal_atlas_t* atlas_out, const spritebatch_internal_lonely_texture_t* imgs, int img_count)
-{
- float w0, h0, div, wTol, hTol;
- int atlas_image_size, atlas_stride, sp;
- void* atlas_pixels = 0;
- int atlas_node_capacity = img_count * 2;
- spritebatch_internal_integer_image_t* images = 0;
- spritebatch_internal_atlas_node_t* nodes = 0;
- int pixel_stride = sb->pixel_stride;
- int atlas_width = sb->atlas_width_in_pixels;
- int atlas_height = sb->atlas_height_in_pixels;
- float volume_used = 0;
-
- images = (spritebatch_internal_integer_image_t*)SPRITEBATCH_MALLOC(sizeof(spritebatch_internal_integer_image_t) * img_count, sb->mem_ctx);
- nodes = (spritebatch_internal_atlas_node_t*)SPRITEBATCH_MALLOC(sizeof(spritebatch_internal_atlas_node_t) * atlas_node_capacity, sb->mem_ctx);
- SPRITEBATCH_CHECK(images, "out of mem");
- SPRITEBATCH_CHECK(nodes, "out of mem");
-
- for (int i = 0; i < img_count; ++i)
- {
- const spritebatch_internal_lonely_texture_t* img = imgs + i;
- spritebatch_internal_integer_image_t* image = images + i;
- image->fit = 0;
- image->size = sb->atlas_use_border_pixels ? spritebatch_v2(img->w + 2, img->h + 2) : spritebatch_v2(img->w, img->h);
- image->img_index = i;
- }
-
- // Sort PNGs from largest to smallest
- spritebatch_internal_image_sort(images, img_count);
-
- // stack pointer, the stack is the nodes array which we will
- // allocate nodes from as necessary.
- sp = 1;
-
- nodes[0].min = spritebatch_v2(0, 0);
- nodes[0].max = spritebatch_v2(atlas_width, atlas_height);
- nodes[0].size = spritebatch_v2(atlas_width, atlas_height);
-
- // Nodes represent empty space in the atlas. Placing a texture into the
- // atlas involves splitting a node into two smaller pieces (or, if a
- // perfect fit is found, deleting the node).
- for (int i = 0; i < img_count; ++i)
- {
- spritebatch_internal_integer_image_t* image = images + i;
- int width = image->size.x;
- int height = image->size.y;
- spritebatch_internal_atlas_node_t *best_fit = spritebatch_best_fit(sp, width, height, nodes);
-
- image->min = best_fit->min;
- image->max = spritebatch_add(image->min, image->size);
-
- if (best_fit->size.x == width && best_fit->size.y == height)
- {
- spritebatch_internal_atlas_node_t* last_node = nodes + --sp;
- *best_fit = *last_node;
- image->fit = 1;
-
- continue;
- }
-
- image->fit = 1;
-
- if (sp == atlas_node_capacity)
- {
- int new_capacity = atlas_node_capacity * 2;
- spritebatch_internal_atlas_node_t* new_nodes = (spritebatch_internal_atlas_node_t*)SPRITEBATCH_MALLOC(sizeof(spritebatch_internal_atlas_node_t) * new_capacity, mem_ctx);
- SPRITEBATCH_CHECK(new_nodes, "out of mem");
- memcpy(new_nodes, nodes, sizeof(spritebatch_internal_atlas_node_t) * sp);
- SPRITEBATCH_FREE(nodes, mem_ctx);
- nodes = new_nodes;
- atlas_node_capacity = new_capacity;
- }
-
- spritebatch_internal_atlas_node_t* new_node = nodes + sp++;
- new_node->min = best_fit->min;
-
- // Split bestFit along x or y, whichever minimizes
- // fragmentation of empty space
- spritebatch_v2_t d = spritebatch_sub(best_fit->size, spritebatch_v2(width, height));
- if (d.x < d.y)
- {
- new_node->size.x = d.x;
- new_node->size.y = height;
- new_node->min.x += width;
-
- best_fit->size.y = d.y;
- best_fit->min.y += height;
- }
-
- else
- {
- new_node->size.x = width;
- new_node->size.y = d.y;
- new_node->min.y += height;
-
- best_fit->size.x = d.x;
- best_fit->min.x += width;
- }
-
- new_node->max = spritebatch_add(new_node->min, new_node->size);
- }
-
- // Write the final atlas image, use SPRITEBATCH_ATLAS_EMPTY_COLOR as base color
- atlas_stride = atlas_width * pixel_stride;
- atlas_image_size = atlas_width * atlas_height * pixel_stride;
- atlas_pixels = SPRITEBATCH_MALLOC(atlas_image_size, mem_ctx);
- SPRITEBATCH_CHECK(atlas_image_size, "out of mem");
- memset(atlas_pixels, SPRITEBATCH_ATLAS_EMPTY_COLOR, atlas_image_size);
-
- for (int i = 0; i < img_count; ++i)
- {
- spritebatch_internal_integer_image_t* image = images + i;
-
- if (image->fit)
- {
- const spritebatch_internal_lonely_texture_t* img = imgs + image->img_index;
- spritebatch_internal_get_pixels(sb, img->image_id, img->w, img->h);
- char* pixels = (char*)sb->pixel_buffer;
-
- spritebatch_v2_t min = image->min;
- spritebatch_v2_t max = image->max;
- int atlas_offset = min.x * pixel_stride;
- int tex_stride = image->size.x * pixel_stride;
-
- for (int row = min.y, y = 0; row < max.y; ++row, ++y)
- {
- void* row_ptr = (char*)atlas_pixels + (row * atlas_stride + atlas_offset);
- SPRITEBATCH_MEMCPY(row_ptr, pixels + y * tex_stride, tex_stride);
- }
- }
- }
-
- hashtable_init(&atlas_out->sprites_to_textures, sizeof(spritebatch_internal_texture_t), img_count, sb->mem_ctx);
- atlas_out->texture_id = sb->generate_texture_callback(atlas_pixels, atlas_width, atlas_height, sb->udata);
-
- // squeeze UVs inward by 128th of a pixel
- // this prevents atlas bleeding. tune as necessary for good results.
- w0 = 1.0f / (float)(atlas_width);
- h0 = 1.0f / (float)(atlas_height);
- div = 1.0f / 128.0f;
- wTol = w0 * div;
- hTol = h0 * div;
-
- for (int i = 0; i < img_count; ++i)
- {
- spritebatch_internal_integer_image_t* img = images + i;
-
- if (img->fit)
- {
- spritebatch_v2_t min = img->min;
- spritebatch_v2_t max = img->max;
- volume_used += img->size.x * img->size.y;
-
- float min_x = (float)min.x * w0 + wTol;
- float min_y = (float)min.y * h0 + hTol;
- float max_x = (float)max.x * w0 - wTol;
- float max_y = (float)max.y * h0 - hTol;
-
- // flip image on y axis
- if (SPRITEBATCH_ATLAS_FLIP_Y_AXIS_FOR_UV)
- {
- float tmp = min_y;
- min_y = max_y;
- max_y = tmp;
- }
-
- spritebatch_internal_texture_t texture;
- texture.w = img->size.x;
- texture.h = img->size.y;
- texture.timestamp = 0;
- texture.minx = min_x;
- texture.miny = min_y;
- texture.maxx = max_x;
- texture.maxy = max_y;
- SPRITEBATCH_ASSERT(!(img->size.x < 0));
- SPRITEBATCH_ASSERT(!(img->size.y < 0));
- SPRITEBATCH_ASSERT(!(min_x < 0));
- SPRITEBATCH_ASSERT(!(max_x < 0));
- SPRITEBATCH_ASSERT(!(min_y < 0));
- SPRITEBATCH_ASSERT(!(max_y < 0));
- texture.image_id = imgs[img->img_index].image_id;
- hashtable_insert(&atlas_out->sprites_to_textures, texture.image_id, &texture);
- }
- }
-
- // Need to adjust atlas_width and atlas_height in config params, as none of the images for this
- // atlas actually fit inside of the atlas! Either adjust the config, or stop sending giant images
- // to the sprite batcher.
- SPRITEBATCH_ASSERT(volume_used > 0);
-
- atlas_out->volume_ratio = volume_used / (atlas_width * atlas_height);
-
-sb_err:
- // no specific error handling needed here (yet)
-
- SPRITEBATCH_FREE(atlas_pixels, mem_ctx);
- SPRITEBATCH_FREE(nodes, mem_ctx);
- SPRITEBATCH_FREE(images, mem_ctx);
- return;
-}
-
-static int spritebatch_internal_lonely_pred(spritebatch_internal_lonely_texture_t* a, spritebatch_internal_lonely_texture_t* b)
-{
- return a->timestamp < b->timestamp;
-}
-
-static void spritebatch_internal_qsort_lonely(hashtable_t* lonely_table, spritebatch_internal_lonely_texture_t* items, int count)
-{
- if (count <= 1) return;
-
- spritebatch_internal_lonely_texture_t pivot = items[count - 1];
- int low = 0;
- for (int i = 0; i < count - 1; ++i)
- {
- if (spritebatch_internal_lonely_pred(items + i, &pivot))
- {
- hashtable_swap(lonely_table, i, low);
- low++;
- }
- }
-
- hashtable_swap(lonely_table, low, count - 1);
- spritebatch_internal_qsort_lonely(lonely_table, items, low);
- spritebatch_internal_qsort_lonely(lonely_table, items + low + 1, count - 1 - low);
-}
-
-int spritebatch_internal_buffer_key(spritebatch_t* sb, SPRITEBATCH_U64 key)
-{
- SPRITEBATCH_CHECK_BUFFER_GROW(sb, key_buffer_count, key_buffer_capacity, key_buffer, SPRITEBATCH_U64);
- sb->key_buffer[sb->key_buffer_count++] = key;
- return 0;
-}
-
-void spritebatch_internal_remove_table_entries(spritebatch_t* sb, hashtable_t* table)
-{
- for (int i = 0; i < sb->key_buffer_count; ++i) hashtable_remove(table, sb->key_buffer[i]);
- sb->key_buffer_count = 0;
-}
-
-void spritebatch_internal_flush_atlas(spritebatch_t* sb, spritebatch_internal_atlas_t* atlas, spritebatch_internal_atlas_t** sentinel, spritebatch_internal_atlas_t** next)
-{
- int ticks_to_decay_texture = sb->ticks_to_decay_texture;
- int texture_count = hashtable_count(&atlas->sprites_to_textures);
- spritebatch_internal_texture_t* textures = (spritebatch_internal_texture_t*)hashtable_items(&atlas->sprites_to_textures);
-
- for (int i = 0; i < texture_count; ++i)
- {
- spritebatch_internal_texture_t* atlas_texture = textures + i;
- if (atlas_texture->timestamp < ticks_to_decay_texture)
- {
- spritebatch_internal_lonely_texture_t* lonely_texture = spritebatch_internal_lonelybuffer_push(sb, atlas_texture->image_id, atlas_texture->w, atlas_texture->h, 0);
- lonely_texture->timestamp = atlas_texture->timestamp;
- }
- hashtable_remove(&sb->sprites_to_atlases, atlas_texture->image_id);
- }
-
- if (sb->atlases == atlas)
- {
- if (atlas->next == atlas) sb->atlases = 0;
- else sb->atlases = atlas->prev;
- }
-
- // handle loop end conditions if sentinel was removed from the chain
- if (sentinel && next)
- {
- if (*sentinel == atlas)
- {
- SPRITEBATCH_LOG("\t\tsentinel was also atlas: %p\n", *sentinel);
- if ((*next)->next != *sentinel)
- {
- SPRITEBATCH_LOG("\t\t*next = (*next)->next : %p = (*next)->%p\n", *next, (*next)->next);
- *next = (*next)->next;
- }
-
- SPRITEBATCH_LOG("\t\t*sentinel = *next : %p = %p\n", *sentinel, *next);
- *sentinel = *next;
-
- }
- }
-
- atlas->next->prev = atlas->prev;
- atlas->prev->next = atlas->next;
- hashtable_term(&atlas->sprites_to_textures);
- sb->delete_texture_callback(atlas->texture_id, sb->udata);
- SPRITEBATCH_FREE(atlas, sb->mem_ctx);
-}
-
-void spritebatch_internal_log_chain(spritebatch_internal_atlas_t* atlas)
-{
- if (atlas)
- {
- spritebatch_internal_atlas_t* sentinel = atlas;
- SPRITEBATCH_LOG("sentinel: %p\n", sentinel);
- do
- {
- spritebatch_internal_atlas_t* next = atlas->next;
- SPRITEBATCH_LOG("\tatlas %p\n", atlas);
- atlas = next;
- }
- while (atlas != sentinel);
- }
-}
-
-int spritebatch_defrag(spritebatch_t* sb)
-{
- // remove decayed atlases and flush them to the lonely buffer
- // only flush textures that are not decayed
- int ticks_to_decay_texture = sb->ticks_to_decay_texture;
- float ratio_to_decay_atlas = sb->ratio_to_decay_atlas;
- spritebatch_internal_atlas_t* atlas = sb->atlases;
- if (atlas)
- {
- spritebatch_internal_log_chain(atlas);
- spritebatch_internal_atlas_t* sentinel = atlas;
- do
- {
- spritebatch_internal_atlas_t* next = atlas->next;
- int texture_count = hashtable_count(&atlas->sprites_to_textures);
- spritebatch_internal_texture_t* textures = (spritebatch_internal_texture_t*)hashtable_items(&atlas->sprites_to_textures);
- int decayed_texture_count = 0;
- for (int i = 0; i < texture_count; ++i) if (textures[i].timestamp >= ticks_to_decay_texture) decayed_texture_count++;
-
- float ratio;
- if (!decayed_texture_count) ratio = 0;
- else ratio = (float)texture_count / (float)decayed_texture_count;
- if (ratio > ratio_to_decay_atlas)
- {
- SPRITEBATCH_LOG("flushed atlas %p\n", atlas);
- spritebatch_internal_flush_atlas(sb, atlas, &sentinel, &next);
- }
-
- atlas = next;
- }
- while (atlas != sentinel);
- }
-
- // merge mostly empty atlases
- float ratio_to_merge_atlases = sb->ratio_to_merge_atlases;
- atlas = sb->atlases;
- if (atlas)
- {
- int sp = 0;
- spritebatch_internal_atlas_t* merge_stack[2];
-
- spritebatch_internal_atlas_t* sentinel = atlas;
- do
- {
- spritebatch_internal_atlas_t* next = atlas->next;
-
- SPRITEBATCH_ASSERT(sp >= 0 && sp <= 2);
- if (sp == 2)
- {
- SPRITEBATCH_LOG("merged 2 atlases\n");
- spritebatch_internal_flush_atlas(sb, merge_stack[0], &sentinel, &next);
- spritebatch_internal_flush_atlas(sb, merge_stack[1], &sentinel, &next);
- sp = 0;
- }
-
- float ratio = atlas->volume_ratio;
- if (ratio < ratio_to_merge_atlases) merge_stack[sp++] = atlas;
-
- atlas = next;
- }
- while (atlas != sentinel);
-
- if (sp == 2)
- {
- SPRITEBATCH_LOG("merged 2 atlases (out of loop)\n");
- spritebatch_internal_flush_atlas(sb, merge_stack[0], 0, 0);
- spritebatch_internal_flush_atlas(sb, merge_stack[1], 0, 0);
- }
- }
-
- // remove decayed textures from the lonely buffer
- int lonely_buffer_count_till_decay = sb->lonely_buffer_count_till_decay;
- int lonely_count = hashtable_count(&sb->sprites_to_lonely_textures);
- spritebatch_internal_lonely_texture_t* lonely_textures = (spritebatch_internal_lonely_texture_t*)hashtable_items(&sb->sprites_to_lonely_textures);
- if (lonely_count >= lonely_buffer_count_till_decay)
- {
- spritebatch_internal_qsort_lonely(&sb->sprites_to_lonely_textures, lonely_textures, lonely_count);
- int index = 0;
- while (1)
- {
- if (index == lonely_count) break;
- if (lonely_textures[index].timestamp >= ticks_to_decay_texture) break;
- ++index;
- }
- for (int i = index; i < lonely_count; ++i)
- {
- SPRITEBATCH_U64 texture_id = lonely_textures[i].texture_id;
- if (texture_id != ~0) sb->delete_texture_callback(texture_id, sb->udata);
- spritebatch_internal_buffer_key(sb, lonely_textures[i].image_id);
- SPRITEBATCH_LOG("lonely texture decayed\n");
- }
- spritebatch_internal_remove_table_entries(sb, &sb->sprites_to_lonely_textures);
- lonely_count -= lonely_count - index;
- SPRITEBATCH_ASSERT(lonely_count == hashtable_count(&sb->sprites_to_lonely_textures));
- }
-
- // process input, but don't make textures just yet
- spritebatch_internal_process_input(sb, 1);
- lonely_count = hashtable_count(&sb->sprites_to_lonely_textures);
-
- // while greater than lonely_buffer_count_till_flush elements in lonely buffer
- // grab lonely_buffer_count_till_flush of them and make an atlas
- int lonely_buffer_count_till_flush = sb->lonely_buffer_count_till_flush;
- int stuck = 0;
- while (lonely_count > lonely_buffer_count_till_flush && !stuck)
- {
- atlas = (spritebatch_internal_atlas_t*)SPRITEBATCH_MALLOC(sizeof(spritebatch_internal_atlas_t), sb->mem_ctx);
- if (sb->atlases)
- {
- atlas->prev = sb->atlases;
- atlas->next = sb->atlases->next;
- sb->atlases->next->prev = atlas;
- sb->atlases->next = atlas;
- }
-
- else
- {
- atlas->next = atlas;
- atlas->prev = atlas;
- sb->atlases = atlas;
- }
-
- spritebatch_make_atlas(sb, atlas, lonely_textures, lonely_count);
- SPRITEBATCH_LOG("making atlas\n");
-
- int tex_count_in_atlas = hashtable_count(&atlas->sprites_to_textures);
- if (tex_count_in_atlas != lonely_count)
- {
- int hit_count = 0;
- for (int i = 0; i < lonely_count; ++i)
- {
- SPRITEBATCH_U64 key = lonely_textures[i].image_id;
- if (hashtable_find(&atlas->sprites_to_textures, key))
- {
- spritebatch_internal_buffer_key(sb, key);
- SPRITEBATCH_U64 texture_id = lonely_textures[i].texture_id;
- if (texture_id != ~0) sb->delete_texture_callback(texture_id, sb->udata);
- hashtable_insert(&sb->sprites_to_atlases, key, &atlas);
- SPRITEBATCH_LOG("removing lonely texture for atlas%s\n", texture_id != ~0 ? "" : " (tex was ~0)" );
- }
- else hit_count++;
- }
- spritebatch_internal_remove_table_entries(sb, &sb->sprites_to_lonely_textures);
-
- if (!hit_count)
- {
- // TODO
- // handle case where none fit in atlas
- SPRITEBATCH_ASSERT(0);
- }
- }
-
- else
- {
- for (int i = 0; i < lonely_count; ++i)
- {
- SPRITEBATCH_U64 key = lonely_textures[i].image_id;
- SPRITEBATCH_U64 texture_id = lonely_textures[i].texture_id;
- if (texture_id != ~0) sb->delete_texture_callback(texture_id, sb->udata);
- hashtable_insert(&sb->sprites_to_atlases, key, &atlas);
- SPRITEBATCH_LOG("(fast path) removing lonely texture for atlas%s\n", texture_id != ~0 ? "" : " (tex was ~0)" );
- }
- hashtable_clear(&sb->sprites_to_lonely_textures);
- lonely_count = 0;
- break;
- }
- }
-
- return 1;
-}
-
-#endif // SPRITEBATCH_IMPLEMENTATION_ONCE
-#endif // SPRITEBATCH_IMPLEMENTATION
-
-/*
- ------------------------------------------------------------------------------
- This software is available under 2 licenses - you may choose the one you like.
- ------------------------------------------------------------------------------
- ALTERNATIVE A - zlib license
- Copyright (c) 2017 Randy Gaul http://www.randygaul.net
- This software is provided 'as-is', without any express or implied warranty.
- In no event will the authors be held liable for any damages arising from
- the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not
- be misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- ------------------------------------------------------------------------------
- ALTERNATIVE B - Public Domain (www.unlicense.org)
- This is free and unencumbered software released into the public domain.
- Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
- software, either in source code form or as a compiled binary, for any purpose,
- commercial or non-commercial, and by any means.
- In jurisdictions that recognize copyright laws, the author or authors of this
- software dedicate any and all copyright interest in the software to the public
- domain. We make this dedication for the benefit of the public at large and to
- the detriment of our heirs and successors. We intend this dedication to be an
- overt act of relinquishment in perpetuity of all present and future rights to
- this software under copyright law.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- ------------------------------------------------------------------------------
-*/