#if defined(SOKOL_IMPL) && !defined(SOKOL_SPINE_IMPL) #define SOKOL_SPINE_IMPL #endif #ifndef SOKOL_SPINE_INCLUDED /* sokol_spine.h -- a sokol-gfx renderer for the spine-c runtime (see https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/spine-c) Project URL: https://github.com/floooh/sokol Do this: #define SOKOL_IMPL or #define SOKOL_SPINE_IMPL before you include this file in *one* C or C++ file to create the implementation. The following defines are used by the implementation to select the platform-specific embedded shader code (these are the same defines as used by sokol_gfx.h and sokol_app.h): SOKOL_GLCORE33 SOKOL_GLES3 SOKOL_D3D11 SOKOL_METAL ...optionally provide the following macros to override defaults: SOKOL_ASSERT(c) - your own assert macro (default: assert(c)) SOKOL_SPINE_API_DECL - public function declaration prefix (default: extern) SOKOL_API_DECL - same as SOKOL_SPINE_API_DECL SOKOL_API_IMPL - public function implementation prefix (default: -) SOKOL_UNREACHABLE() - a guard macro for unreachable code (default: assert(false)) If sokol_spine.h is compiled as a DLL, define the following before including the declaration or implementation: SOKOL_DLL On Windows, SOKOL_DLL will define SOKOL_SPINE_API_DECL as __declspec(dllexport) or __declspec(dllimport) as needed. Include the following headers before including sokol_spine.h: sokol_gfx.h Include the following headers before include the sokol_spine.h *IMPLEMENTATION*: spine/spine.h You'll also need to compile and link with the spine-c runtime: https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/spine-c/spine-c FEATURE OVERVIEW ================ sokol_spine.h is a sokol-gfx renderer and 'handle wrapper' for Spine (http://en.esotericsoftware.com/spine-in-depth) on top of the spine-c runtime: http://en.esotericsoftware.com/spine-c (source code: https://github.com/EsotericSoftware/spine-runtimes/tree/4.1/spine-c/spine-c). The sokol-gfx renderer allows to manage multiple contexts for rendering Spine scenes into different sokol-gfx render passes (similar to sokol-gl and sokol-debugtext), allows to split rendering into layers to mix Spine rendering with other rendering operations, and it automatically batches adjacent draw calls for Spine objects that use the same texture and in the same layer. Sokol-spine wraps 'raw' spine-c objects with tagged index handles. This eliminates the risk of memory corruption via dangling pointers. Any API calls involving invalid objects either result in a no-op, or in a proper error. The sokol-spine API exposes four 'base object types', and a number of 'subobject types' which are owned by base objects. Base object types are: - sspine_atlas: A wrapper around a spine-c spAtlas object, each spAtlas object owns at least one spAtlasPage object, and each spAtlasPage object owns exactly one sokol-gfx image object. - sspine_skeleton: A skeleton object requires an atlas object for creation, and is a wrapper around one spine-c spSkeletonData and one spAnimationStateData object. both contain the shared static data for individual spine instances - sspine_instance: Instance objects are created from skeleton objects. Instances are the objects that are actually getting rendered. Each instance tracks its own transformation and animation state, but otherwise just references shared data of the skeleton object it was created from. An sspine_instance object is a wrapper around one spine-c spSkeleton, spAnimationState and spSkeletonClipping object each. - sspine_skinset: Skin-set objects are collections of skins which define the look of an instance. Some Spine scenes consist of combinable skins (for instance a human character could offer different skins for different types of clothing, hats, scarfs, shirts, pants, and so on..., and a skin set would represent a specific outfit). Subobject types allow to inspect and manipulate Spine objects in more detail: - sspine_anim: Each skeleton object usually offers animations which can then be scheduled and mixed on an instance. - sspine_bone: Bone objects are the hierarchical transform nodes of a skeleton. The sokol-spine API allows both to inspect the shared static bone attributes of an sspine_skeleton object, as well as inspecting and manipulating the per-instance bone attributes on an sspine_instance object. - sspine_event: A running Spine animation may fire 'events' at certain positions in time (for instance a 'footstep' event whenever a foot hits the ground). Events can be used to play sound effects (or visual effects) at the right time. - sspine_iktarget: Allows to set the target position for a group of bones controlled by inverse kinematics. There's a couple of other subobject types which are mostly useful to inspect the interior structure of skeletons. Those will be explained in detail further down. MINIMAL API USAGE OVERVIEW ========================== During initialization: - call sspine_setup() after initializating sokol-gfx - create an atlas object from a Spine atlas file with sspine_make_atlas() - load and initialize the sokol-gfx image objects referenced by the atlas - create a skeleton object from a Spine skeleton file with sspine_make_skeleton() - create at least one instance object with sspine_make_instance() In the frame loop, outside of sokol-gfx render passes: - if needed, move instances around with sspine_set_position() - if needed, schedule new animations with sspine_set_animation() and sspine_add_animation() - each frame, advance the current instance animation state with sspine_update_instance() - each frame, render instances with sspine_draw_instance_in_layer(), this just records vertices, indices and draw commands into internal buffers, but does no actual sokol-gfx rendering In the frame loop, inside a sokol-gfx render pass: - call sspine_draw_layer() to draw all previously recorded instances in a specific layer On shutdown: - call sspine_shutdown(), ideally before shutting down sokol-gfx QUICKSTART STEP BY STEP ======================= For a simple demo program using sokol_app.h, sokol_gfx.h and sokol_fetch.h, see here: [TODO: add link to spine-simple-sapp wasm demo]. - sokol_spine.h must be included after sokol_gfx.h (this is true both for the declaration and implementation): #include "sokol_gfx.h" #include "sokol_spine.h" - ...and sokol_gfx.h must be initialized before sokol_spine.h: sg_setup(&(sg_desc){ ... }); sspine_setup(&(sspine_desc){ ... }); - You should always provide a logging callback to sokol-spine, otherwise no warning or errors will be logged. The easiest way is to use sokol_log.h for this: #include "sokol_log.h" sspine_setup(&(sspine_desc){ .logger = { .func = slog_func } }); - You can tweak the memory usage of sokol-spine by limiting or expanding the maximum number of vertices, draw commands and pool sizes: sspine_setup(&(sspine_desc){ .max_vertices = 1024, // default: (1<<16) = 65536 .max_commands = 128, // default: (1<<14) = 16384 .context_pool_size = 1, // default: 4 .atlas_pool_size = 1, // default: 64 .skeleton_pool_size = 1, // default: 64 .skinset_pool_size = 1, // default: 64 .instance_pool_size = 16, // default: 1024 .logger = { .func = slog_func, } }); Sokol-spine uses 32-bit vertex-indices for rendering (SG_INDEXTYPE_UINT32), so that the maximum number of Spine vertices in a frame isn't limited to (1<<16). - You can override memory allocation and logging with your own functions, this is explained in detail further down: sspine_setup(&(sspine_desc){ .allocator = { .alloc = my_alloc, .free = my_free, .user_data = ..., }, .logger = { .log_func = my_log_func, .user_data = ..., } }); - After initialization, the first thing you need is an sspine_atlas object. Sokol-spine doesn't concern itself with file IO, it expects all external data to be provided as pointer/size pairs: sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ .data = { .ptr = ..., // pointer to Spine atlas file data in memory .size = ..., // atlas file data size in bytes } }); assert(sspine_atlas_valid(atlas)); If you load the atlas data asynchronously, you can still run your per-frame rendering code without waiting for the atlas data to be loaded and the atlas to be created. This works because calling sokol-spine functions with 'invalid' object handles is a valid no-op. - Optionally you can override some or all of the atlas texture creation parameters: sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ .data = { ... }, .overrides = { .min_filter = SG_FILTER_NEAREST, .mag_filter = SG_FILTER_NEAREST, .wrap_u = SG_WRAP_MIRROR, .wrap_v = SG_WRAP_MIRROR, .premul_alpha_enabled = ..., .premul_alpha_disabled = ..., } }); - The atlas file itself doesn't contain any texture data, it only contains filenames of the required textures. Sokol-spine has already allocated a sokol-gfx sg_image handle for each required texture, but the actual texture loading and initialization must be performed by user code: // iterate over atlas textures and initialize sokol-gfx image objects // with existing handles const int num = sspine_num_images(atlas); for (int i = 0; i < num; i++) { const sspine_image img = sspine_image_by_index(atlas, i); const sspine_image_info img_info = sspine_get_image_info(img); assert(img_info.valid); assert(!img_info.filename.truncated); // the filename is now in img_info.filename.cstr, 'somehow' // load and decode the image data into memory, and then // initialize the sokol-gfx image from the existing sg_image handle // in img_info.sgimage: sg_init_image(img_info.sgimage, &(sg_image_desc){ .width = ..., .height = ..., .pixel_format = ..., .min_filter = img_info.min_filter, .mag_filter = img_info.mag_filter, .wrap_u = img_info.wrap_u, .wrap_v = img_info.wrap_v, .data.subimage[0][0] = { .ptr = ..., // pointer to decoded image pixel data .size = ..., // size of decoded image pixel data in bytes } }); } If you load the image data asynchronously, you can still simply start rendering before the image data is loaded. This works because sokol-gfx will silently drop any rendering operations that involve 'incomplete' objects. - Once an atlas object has been created (independently from loading any image data), an sspine_skeleton object is needed next. This requires a valid atlas object handle as input, and a pointer to the Spine skeleton file data loaded into memory. Spine skeleton files come in two flavours: binary or json, for binary data, a ptr/size pair must be provided: sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ .atlas = atlas, // atlas must be a valid sspine_atlas handle .binary_data = { .ptr = ..., // pointer to binary skeleton data in memory .size = ..., // size of binary skeleton data in bytes } }); assert(sspine_skeleton_valid(skeleton)); For JSON skeleton file data, the data must be provided as a zero-terminated C string: sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ .atlas = atlas, .json_data = ..., // JSON skeleton data as zero-terminated(!) C-string }); Like with all sokol-spine objects, if you load the skeleton data asynchronously and only then create a skeleton object, you can already start rendering before the data is loaded and the Spine objects have been created. Any operations involving 'incomplete' handles will be dropped. - You can pre-scale the Spine scene size, and you can provide a default cross-fade duration for animation mixing: sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ .atlas = atlas, .binary_data = { ... }, .prescale = 0.5f, // scale to half-size .anim_default_mix = 0.2f, // default anim mixing cross-fade duration 0.2 seconds }); - Once the skeleton object has been created, it's finally time to create one or many instance objects. If you want to independently render and animate the 'same' Spine object many times in a frame, you should only create one sspine_skeleton object, and then as many sspine_instance object as needed from the shared skeleton object: sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){ .skeleton = skeleton, // must be a valid skeleton handle }); assert(sspine_instance_valid(instance)); After creation, the sspine_instance will have a 'default skin' set as its appearance. - To set the position of an instance: sspine_set_position(inst, (sspine_vec2){ .x=..., .y=... }); Sokol-spine doesn't define a specific unit (like pixels or meters), instead the rendering coordinate system is defined later at 'render time'. - To schedule an initial looping animation by its name: // first lookup up the animation by name on the skeleton: sspine_anim anim = sspine_anim_by_name(skeleton, "walk"); assert(sspine_anim_valid(anim)); // then schedule the animation on the instance, on mixer track 0, as looping: sspine_set_animation(instance, anim, 0, true); Scheduling and mixing animations will be explained in more detail further down. - To advance and mix instance animations: sspine_update_instance(instance, delta_time_in_seconds); Usually you'd call this each frame for each active instance with the frame duration in seconds. - Now it's finally time to 'render' the instance at its current position and animation state: sspine_draw_instance_in_layer(instance, 0); Instances are generally rendered into numbered virtual 'render layers' (in this case, layer 0). Layers are useful for interleaving sokol-spine rendering with other rendering commands (like background and foreground tile maps, sprites or text). - It's important to note that no actual sokol-gfx rendering happens in sspine_draw_instance_in_layer(), instead only vertices, indices and draw commands are recorded into internal memory buffes. - The only sokol-spine function which *must* (and should) be called inside a sokol-gfx rendering pass is sspine_draw_layer(). This renders all draw commands that have been recorded previously in a specific layer via sspine_draw_instance_in_layer(). const sspine_layer_transform tform = { ... }; sg_begin_default_pass(...); sspine_draw_layer(0, tform); sg_end_pass(); sg_commit(); IMPORTANT: DO *NOT* MIX any calls to sspine_draw_instance_in_layer() with sspine_draw_layer(), as this will confuse the internal draw command recording. Ideally, move all sokol-gfx pass rendering (including all sspine_draw_layer() calls) towards the end of the frame, separate from any other sokol-spine calls. The sspine_layer_transform struct defines the layer's screen space coordinate system. For instance to map Spine coordinates to framebuffer pixels, with the origin in the screen center, you'd setup the layer transform like this: const float width = sapp_widthf(); const float height = sapp_heightf(); const sspine_layer_transform tform = { .size = { .x = width, .y = height }, .origin = { .x = width * 0.5f, .y = height * 0.5f }, }; With this pixel mapping, the Spine scene would *not* scale with window size, which often is not very useful. Instead it might make more sense to render to a fixed 'virtual' resolution, for instance 1024 * 768: const sspine_layer_transform tform = { .size = { .x = 1024.0f, .y = 768.0f }, .origin = { .x = 512.0f, .y = 384.0f }, }; How to configure a virtual resolution with a fixed aspect ratio is left as an exercise to the reader ;) - That's it for basic sokol-spine setup and rendering. Any existing objects will automatically be cleaned up when calling sspine_shutdown(), this should be called before shutting down sokol-gfx, but this is not required: sspine_shutdown(); sg_shutdown(); - You can explicitely destroy the base object types if you don't need them any longer. This will cause the underlying spine-c objects to be freed and the memory to be returned to the operating system: sspine_destroy_instance(instance); sspine_destroy_skinset(skinset); sspine_destroy_skeleton(skeleton); sspine_destroy_atlas(atlas); You can destroy these objects in any order without causing memory corruption issues. Instead any dependent object handles will simply become invalid (e.g. if you destroy an atlas object, all skeletons and instances created from this atlas will 'technically' still exist, but their handles will resolve to 'invalid' and all sokol-spine calls involving these handles will silently fail). For instance: // create an atlas, skeleton and instance sspine_atlas atlas = sspine_make_atlas(&(sspine_atlas_desc){ ... }); assert(sspine_atlas_valid(atlas)); sspine_skeleton skeleton = sspine_make_skeleton(&(sspine_skeleton_desc){ .atlas = atlas, ... }); assert(sspine_skeleton_valid(skeleton)); sspine_instance instance = sspine_make_instance(&(sspine_instance_desc){ .skeleton = skeleton, }); assert(sspine_instance_valid(instance)); // destroy the atlas object: sspine_destroy_atlas(atlas); // the skeleton and instance handle should now be invalid, but // otherwise, nothing bad will happen: if (!sspine_skeleton_valid(skeleton)) { ... } if (!sspine_instance_valid(instance)) { ... } RENDERER DETAILS ================ Any rendering related work happens in the functions sspine_draw_instance_in_layer() and sspine_draw_layer(). sspine_draw_instance_in_layer() will result in vertices, indices and internal draw commands which will be recorded into internal memory buffers (e.g. no sokol-gfx functions will be called here). If possible, batching will be performed by merging a new draw command with the previously recorded draw command. For two draw commands to be merged, the following conditions must be tru: - rendering needs to go into the same layer - the same atlas texture must be used - the blend mode must be compatible (the Spine blending modes 'normal' and 'additive' can be merged, but not 'multiply') - the same premultiplied alpha mode must be used To make the most out of batching: - use Spine objects which only have a single atlas texture and blend mode across all slots - group sspine_draw_instance_in_layer() calls by layer After all instances have been 'rendered' (or rather: recorded) into layers, the actually rendering happens inside a sokol-gfx pass by calling the function sspine_draw_layer() for each layer in 'z order' (e.g. the layer index doesn't matter for z-ordering, only the order how sspine_draw_layer() is called). Only the first call to sspine_draw_layer() in a frame will copy the recorded vertices and indices into sokol-gfx buffers. Each call to sspine_draw_layer() will iterate over all recorded (and hopefully well-batched) draw commands, skip any draw commands with a non-matching layer index, and draw only those with a matching layer by calling: - if the pipeline object has changed: - sg_apply_pipeline() - sg_apply_uniforms() for the vertex stage - if the atlas texture has changed: - sg_apply_bindings() - if the premultiplied-alpha mode has changed: - sg_apply_uniforms() for the fragment stage - and finally sg_draw() The main purpose of render layers is to mix Spine rendering with other render operations. In the not too distant future, the same render layer idea will also be implemented at least for sokol-gl and sokol-debugtext. FIXME: does this section need more details about layer transforms? RENDERING WITH CONTEXTS ======================= At first glance, render contexts may look like more heavy-weight render layers, but they serve a different purpose: they are useful if Spine rendering needs to happen in different sokol-gfx render passes with different pixel formats and MSAA sample counts. All Spine rendering happens within a context, even you don't call any of the context API functions, in this case, an internal 'default context' will be used. Each context has its own internal vertex-, index- and command buffer and all context state is completely independent from any other contexts. To create a new context object, call: sspine_context ctx = sspine_make_context(&(sspine_context_desc){ .max_vertices = ..., .max_commands = ..., .color_format = SG_PIXELFORMAT_..., .depth_format = SG_PIXELFORMAT_..., .sample_count = ..., .color_write_mask = SG_COLORMASK_..., }); The color_format, depth_format and sample_count items must be compatible with the sokol-gfx render pass you're going to render into. If you omit the color_format, depth_format and sample_count designators, the new context will be compatible with the sokol-gfx default pass (which is most likely not what you want, unless your offscreen render passes exactly match the default pass attributes). Once a context has been created, it can be made active with: sspine_set_context(ctx); To set the default context again: sspine_set_contxt(sspine_default_context()); ...and to get the currently active context: sspine_context cur_ctx = sspine_get_context(); The currently active context only matter for two functions: - sspine_draw_instance_in_layer() - sspine_draw_layer() Alternatively you can bypass the currently set context with these alternative functions: - sspine_context_draw_layer_in_instance(ctx, ...) - sspine_context_draw_layer(ctx, ...) These explicitely take a context argument, completely ignore and don't change the active context. You can query some information about the a context with the function: sspine_context_info info = ssgpine_get_context_info(ctx); This returns the current number of recorded vertices, indices and draw commands. RESOURCE STATES: ================ Similar to sokol-gfx, you can query the current 'resource state' of Spine objects: sspine_resource_state sspine_get_atlas_resource_state(sspine_atlas atlas); sspine_resource_state sspine_get_skeleton_resource_state(sspine_atlas atlas); sspine_resource_state sspine_get_instance_resource_state(sspine_atlas atlas); sspine_resource_state sspine_get_skinset_resource_state(sspine_atlas atlas); sspine_resource_state sspine_get_context_resource_state(sspine_atlas atlas); This returns one of - SSPINE_RESOURCE_VALID: the object is valid and ready to use - SSPINE_RESOURCE_FAILED: the object creation has failed - SSPINE_RESOURCE_INVALID: the object or one of its dependencies is invalid, it either no longer exists, or the the handle hasn't been initialized with a call to one of the object creation functions MISC HELPER FUNCTIONS: ====================== There's a couple of helper functions which don't fit into a big enough category of their own: You can ask a skeleton for the atlas it has been created from: sspine_atlas atlas = sspine_get_skeleton_atlas(skeleton); ...and likewise, ask an instance for the skeleton it has been created from: sspine_skeleton skeleton = sspine_get_instance_skeleton(instance); ...and finally you can convert a layer transform struct into a 4x4 projection matrix that's memory-layout compatible with sokol-gl: const sspine_layer_transform tform = { ... }; const sspine_mat4 proj = sspine_layer_transform_to_mat4(&tform); sgl_matrix_mode_projection(); sgl_load_matrix(proj.m); ANIMATIONS ========== Animations have their own handle type sspine_anim. A valid sspine_anim handle is either obtained by looking up an animation by name from a skeleton: sspine_anim anim = sspine_anim_by_name(skeleton, "walk"); ...or by index: sspine_anim anim = sspine_anim_by_index(skeleton, 0); The returned anim handle will be invalid if an animation of that name doesn't exist, or the provided index is out-of-range: if (!sspine_anim_is_valid(anim)) { // animation handle is not valid } An animation handle will also become invalid when the skeleton object it was created is destroyed, or otherwise becomes invalid. You can iterate over all animations in a skeleton: const int num_anims = sspine_num_anims(skeleton); for (int anim_index = 0; anim_index < num_anims; anim_index++) { sspine_anim anim = sspine_anim_by_index(skeleton, anim_index); ... } Since sspine_anim is a 'fat handle' (it houses a skeleton handle and an index), there's a helper function which checks if two anim handles are equal: if (sspine_anim_equal(anim0, anim1)) { ... } To query information about an animation: const sspine_anim_info info = sspine_get_anim_info(anim); if (info.valid) { printf("index: %d, duration: %f, name: %s", info.index, info.duration, info.name.cstr); } Scheduling and mixing animations is controlled through the following functions: void sspine_clear_animation_tracks(sspine_instance instance); void sspine_clear_animation_track(sspine_instance instance, int track_index); void sspine_set_animation(sspine_instance instance, sspine_anim anim, int track_index, bool loop); void sspine_add_animation(sspine_instance instance, sspine_anim anim, int track_index, bool loop, float delay); void sspine_set_empty_animation(sspine_instance instance, int track_index, float mix_duration); void sspine_add_empty_animation(sspine_instance instance, int track_index, float mix_duration, float delay); Please refer to the spine-c documentation to get an idea what these functions do: http://en.esotericsoftware.com/spine-c#Applying-animations EVENTS ====== For a general idea of Spine events, see here: http://esotericsoftware.com/spine-events After calling sspine_update_instance() to advance the currently configured animations, you can poll for triggered events like this: const int num_triggered_events = sspine_num_triggered_events(instance); for (int i = 0; i < num_triggered_events; i++) { const sspine_triggered_event_info info = sspine_get_triggered_event_info(instance, i); if (info.valid) { ... } } The returned sspine_triggered_event_info struct gives you the current runtime properties of the event (in case the event has keyed properties). For the actual list of event properties please see the actual sspine_triggered_event_info struct declaration. It's also possible to inspect the static event definition on a skeleton, this works the same as iterating through animations. You can lookup an event by name, get the number of events, lookup an event by its index, and get detailed information about an event: int sspine_num_events(sspine_skeleton skeleton); sspine_event sspine_event_by_name(sspine_skeleton skeleton, const char* name); sspine_event sspine_event_by_index(sspine_skeleton skeleton, int index); bool sspine_event_valid(sspine_event event); bool sspine_event_equal(sspine_event first, sspine_event second); sspine_event_info sspine_get_event_info(sspine_event event); (FIXME: shouldn't the event info struct contains an sspine_anim handle?) IK TARGETS ========== The IK target function group allows to iterate over the IK targets that have been defined on a skeleton, find an IK target by name, get detailed information about an IK target, and most importantly, set the world space position of an IK target which updates the position of all bones influenced by the IK target: int sspine_num_iktargets(sspine_skeleton skeleton); sspine_iktarget sspine_iktarget_by_name(sspine_skeleton skeleton, const char* name); sspine_iktarget sspine_iktarget_by_index(sspine_skeleton skeleton, int index); bool sspine_iktarget_valid(sspine_iktarget iktarget); bool sspine_iktarget_equal(sspine_iktarget first, sspine_iktarget second); sspine_iktarget_info sspine_get_iktarget_info(sspine_iktarget iktarget); void sspine_set_iktarget_world_pos(sspine_instance instance, sspine_iktarget iktarget, sspine_vec2 world_pos); BONES ===== Skeleton bones are wrapped with an sspine_bone handle which can be created from a skeleton handle, and either a bone name: sspine_bone bone = sspine_bone_by_name(skeleton, "root"); assert(sspine_bone_valid(bone)); ...or a bone index: sspine_bone bone = sspine_bone_by_index(skeleton, 0); assert(sspine_bone_valid(bone)); ...to iterate over all bones of a skeleton and query information about each bone: const int num_bones = sspine_num_bones(skeleton); for (int bone_index = 0; bone_index < num_bones; bone_index++) { sspine_bone bone = sspine_bone_by_index(skeleton, bone_index); const sspine_bone_info info = sspine_get_bone_info(skeleton, bone); if (info.valid) { ... } } The sspine_bone_info struct provides the shared, static bone state in the skeleton (like the name, a parent bone handle, bone length, pose transform and a color attribute), but doesn't contain any dynamic information of per-instance bones. To manipulate the per-instance bone attributes use the following setter functions: void sspine_set_bone_transform(sspine_instance instance, sspine_bone bone, const sspine_bone_transform* transform); void sspine_set_bone_position(sspine_instance instance, sspine_bone bone, sspine_vec2 position); void sspine_set_bone_rotation(sspine_instance instance, sspine_bone bone, float rotation); void sspine_set_bone_scale(sspine_instance instance, sspine_bone bone, sspine_vec2 scale); void sspine_set_bone_shear(sspine_instance instance, sspine_bone bone, sspine_vec2 shear); ...and to query the per-instance bone attributes, the following getters: sspine_bone_transform sspine_get_bone_transform(sspine_instance instance, sspine_bone bone); sspine_vec2 sspine_get_bone_position(sspine_instance instance, sspine_bone bone); float sspine_get_bone_rotation(sspine_instance instance, sspine_bone bone); sspine_vec2 sspine_get_bone_scale(sspine_instance instance, sspine_bone bone); sspine_vec2 sspine_get_bone_shear(sspine_instance instance, sspine_bone bone); These functions all work in the local bone coordinate system (relative to a bone's parent bone). To transform positions between bone-local and global space use the following helper functions: sspine_vec2 sspine_bone_local_to_world(sspine_instance instance, sspine_bone bone, sspine_vec2 local_pos); sspine_vec2 sspine_bone_world_to_local(sspine_instance instance, sspine_bone bone, sspine_vec2 world_pos); ...and as a convenience, there's a helper function which obtains the bone position in global space directly: sspine_vec2 sspine_get_bone_world_position(sspine_instance instance, sspine_bone bone); SKINS AND SKINSETS ================== Skins are named pieces of geometry which can be turned on and off, what makes Spine skins a bit confusing is that they are hierarchical. A skin can itself be a collection of other skins. Setting the 'root skin' will also make all 'child skins' visible. In sokol-spine collections of skins are managed through dedicated 'skin set' objects. Under the hood they create a 'root skin' where the skins of the skin set are attached to, but from the outside it just looks like a 'flat' collection of skins without the tricky hierarchical management. Like other 'subobjects', skin handles can be obtained by the skin name from a skeleton handle: sspine_skin skin = sspine_skin_by_name(skeleton, "jacket"); assert(sspine_skin_valid(skin)); ...or by a skin index: sspine_skin skin = sspine_skin_by_index(skeleton, 0); assert(sspine_skin_valid(skin)); ...you can iterate over all skins of a skeleton and query some information about the skin: const int num_skins = sspine_num_skins(skeleton); for (int skin_index = 0; skin_index < num_skins; skin_index++) { sspine_skin skin = sspine_skin_by_index(skin_index); sspine_skin_info info = sspine_get_skin_info(skin); if (info.valid) { ... } } Currently, the only useful query item is the skin name though. To make a skin visible on an instance, just call: sspine_set_skin(instance, skin); ...this will first deactivate the previous skin before setting a new skin. A more powerful way to configure the skin visibility is through 'skin sets'. Skin sets are simply flat collections of skins which should be made visible at once. A new skin set is created like this: sspine_skinset skinset = sspine_make_skinset(&(sspine_skinset_desc){ .skeleton = skeleton, .skins = { sspine_skin_by_name(skeleton, "blue-jacket"), sspine_skin_by_name(skeleton, "green-pants"), sspine_skin_by_name(skeleton, "blonde-hair"), ... } }); assert(sspine_skinset_valid(skinset)) ...then simply set the skinset on an instance to reconfigure the appearance of the instance: sspine_set_skinset(instance, skinset); The functions sspine_set_skinset() and sspine_set_skin() will cancel each other. Calling sspine_set_skinset() deactivates the effect of sspine_set_skin() and vice versa. ERROR REPORTING AND LOGGING =========================== To get any logging information at all you need to provide a logging callback in the setup call, the easiest way is to use sokol_log.h: #include "sokol_log.h" sspine_setup(&(sspine_desc){ .logger.func = slog_func }); To override logging with your own callback, first write a logging function like this: void my_log(const char* tag, // e.g. 'sspine' uint32_t log_level, // 0=panic, 1=error, 2=warn, 3=info uint32_t log_item_id, // SSPINE_LOGITEM_* const char* message_or_null, // a message string, may be nullptr in release mode uint32_t line_nr, // line number in sokol_spine.h const char* filename_or_null, // source filename, may be nullptr in release mode void* user_data) { ... } ...and then setup sokol-spine like this: sspine_setup(&(sspine_desc){ .logger = { .func = my_log, .user_data = my_user_data, } }); The provided logging function must be reentrant (e.g. be callable from different threads). If you don't want to provide your own custom logger it is highly recommended to use the standard logger in sokol_log.h instead, otherwise you won't see any warnings or errors. MEMORY ALLOCATION OVERRIDE ========================== You can override the memory allocation functions at initialization time like this: void* my_alloc(size_t size, void* user_data) { return malloc(size); } void my_free(void* ptr, void* user_data) { free(ptr); } ... sspine_setup(&(sspine_desc){ // ... .allocator = { .alloc = my_alloc, .free = my_free, .user_data = ...; } }); ... If no overrides are provided, malloc and free will be used. This only affects memory allocation calls done by sokol_gfx.h itself though, not any allocations in OS libraries. LICENSE ======= zlib/libpng license Copyright (c) 2022 Andre Weissflog 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. */ #define SOKOL_SPINE_INCLUDED (1) #include #include #include // size_t #if !defined(SOKOL_GFX_INCLUDED) #error "Please include sokol_gfx.h before sokol_spine.h" #endif #if defined(SOKOL_API_DECL) && !defined(SOKOL_SPINE_API_DECL) #define SOKOL_SPINE_API_DECL SOKOL_API_DECL #endif #ifndef SOKOL_SPINE_API_DECL #if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_SPINE_IMPL) #define SOKOL_SPINE_API_DECL __declspec(dllexport) #elif defined(_WIN32) && defined(SOKOL_DLL) #define SOKOL_SPINE_API_DECL __declspec(dllimport) #else #define SOKOL_SPINE_API_DECL extern #endif #endif #ifdef __cplusplus extern "C" { #endif enum { SSPINE_INVALID_ID = 0, SSPINE_MAX_SKINSET_SKINS = 32, SSPINE_MAX_STRING_SIZE = 61, // see sspine_string struct }; typedef struct sspine_context { uint32_t id; } sspine_context; typedef struct sspine_atlas { uint32_t id; } sspine_atlas; typedef struct sspine_skeleton { uint32_t id; } sspine_skeleton; typedef struct sspine_instance { uint32_t id; } sspine_instance; typedef struct sspine_skinset { uint32_t id; } sspine_skinset; typedef struct sspine_image { uint32_t atlas_id; int index; } sspine_image; typedef struct sspine_atlas_page { uint32_t atlas_id; int index; } sspine_atlas_page; typedef struct sspine_anim { uint32_t skeleton_id; int index; } sspine_anim; typedef struct sspine_bone { uint32_t skeleton_id; int index; } sspine_bone; typedef struct sspine_slot { uint32_t skeleton_id; int index; } sspine_slot; typedef struct sspine_event { uint32_t skeleton_id; int index; } sspine_event; typedef struct sspine_iktarget { uint32_t skeleton_id; int index; } sspine_iktarget; typedef struct sspine_skin { uint32_t skeleton_id; int index; } sspine_skin; typedef struct sspine_range { const void* ptr; size_t size; } sspine_range; typedef struct sspine_vec2 { float x, y; } sspine_vec2; typedef struct sspine_mat4 { float m[16]; } sspine_mat4; typedef sg_color sspine_color; typedef struct sspine_string { bool valid; bool truncated; uint8_t len; char cstr[SSPINE_MAX_STRING_SIZE]; } sspine_string; typedef enum sspine_resource_state { SSPINE_RESOURCESTATE_INITIAL, SSPINE_RESOURCESTATE_ALLOC, SSPINE_RESOURCESTATE_VALID, SSPINE_RESOURCESTATE_FAILED, SSPINE_RESOURCESTATE_INVALID, _SSPINE_RESOURCESTATE_FORCE_U32 = 0x7FFFFFFF } sspine_resource_state; // log item codes via x-macro magic #define _SSPINE_LOG_ITEMS \ _SSPINE_LOGITEM_XMACRO(OK, "Ok")\ _SSPINE_LOGITEM_XMACRO(MALLOC_FAILED, "memory allocation failed")\ _SSPINE_LOGITEM_XMACRO(CONTEXT_POOL_EXHAUSTED, "context pool exhausted (adjust via sspine_desc.context_pool_size)")\ _SSPINE_LOGITEM_XMACRO(ATLAS_POOL_EXHAUSTED, "atlas pool exhausted (adjust via sspine_desc.atlas_pool_size)")\ _SSPINE_LOGITEM_XMACRO(SKELETON_POOL_EXHAUSTED, "skeleton pool exhausted (adjust via sspine_desc.skeleton_pool_size)")\ _SSPINE_LOGITEM_XMACRO(SKINSET_POOL_EXHAUSTED, "skinset pool exhausted (adjust via sspine_desc.skinset_pool_size)")\ _SSPINE_LOGITEM_XMACRO(INSTANCE_POOL_EXHAUSTED, "instance pool exhausted (adjust via sspine_desc.instance_pool_size)")\ _SSPINE_LOGITEM_XMACRO(CANNOT_DESTROY_DEFAULT_CONTEXT, "cannot destroy default context")\ _SSPINE_LOGITEM_XMACRO(ATLAS_DESC_NO_DATA, "no data provided in sspine_atlas_desc.data")\ _SSPINE_LOGITEM_XMACRO(SPINE_ATLAS_CREATION_FAILED, "spAtlas_create() failed")\ _SSPINE_LOGITEM_XMACRO(SG_ALLOC_IMAGE_FAILED, "sg_alloc_image() failed")\ _SSPINE_LOGITEM_XMACRO(SKELETON_DESC_NO_DATA, "no data provided in sspine_skeleton_desc.json_data or .binary_data")\ _SSPINE_LOGITEM_XMACRO(SKELETON_DESC_NO_ATLAS, "no atlas object provided in sspine_skeleton_desc.atlas")\ _SSPINE_LOGITEM_XMACRO(SKELETON_ATLAS_NOT_VALID, "sspine_skeleton_desc.atlas is not in valid state")\ _SSPINE_LOGITEM_XMACRO(CREATE_SKELETON_DATA_FROM_JSON_FAILED, "spSkeletonJson_readSkeletonData() failed")\ _SSPINE_LOGITEM_XMACRO(CREATE_SKELETON_DATA_FROM_BINARY_FAILED, "spSkeletonBinary_readSkeletonData() failed")\ _SSPINE_LOGITEM_XMACRO(SKINSET_DESC_NO_SKELETON, "no skeleton object provided in sspine_skinset_desc.skeleton")\ _SSPINE_LOGITEM_XMACRO(SKINSET_SKELETON_NOT_VALID, "sspine_skinset_desc.skeleton is not in valid state")\ _SSPINE_LOGITEM_XMACRO(SKINSET_INVALID_SKIN_HANDLE, "invalid skin handle in sspine_skinset_desc.skins[]")\ _SSPINE_LOGITEM_XMACRO(INSTANCE_DESC_NO_SKELETON, "no skeleton object provided in sspine_instance_desc.skeleton")\ _SSPINE_LOGITEM_XMACRO(INSTANCE_SKELETON_NOT_VALID, "sspine_instance_desc.skeleton is not in valid state")\ _SSPINE_LOGITEM_XMACRO(INSTANCE_ATLAS_NOT_VALID, "skeleton's atlas object no longer valid via sspine_instance_desc.skeleton")\ _SSPINE_LOGITEM_XMACRO(SPINE_SKELETON_CREATION_FAILED, "spSkeleton_create() failed")\ _SSPINE_LOGITEM_XMACRO(SPINE_ANIMATIONSTATE_CREATION_FAILED, "spAnimationState_create() failed")\ _SSPINE_LOGITEM_XMACRO(SPINE_SKELETONCLIPPING_CREATION_FAILED, "spSkeletonClipping_create() failed")\ _SSPINE_LOGITEM_XMACRO(COMMAND_BUFFER_FULL, "command buffer full (adjust via sspine_desc.max_commands)")\ _SSPINE_LOGITEM_XMACRO(VERTEX_BUFFER_FULL, "vertex buffer (adjust via sspine_desc.max_vertices)")\ _SSPINE_LOGITEM_XMACRO(INDEX_BUFFER_FULL, "index buffer full (adjust via sspine_desc.max_vertices)")\ _SSPINE_LOGITEM_XMACRO(STRING_TRUNCATED, "a string has been truncated")\ _SSPINE_LOGITEM_XMACRO(ADD_COMMIT_LISTENER_FAILED, "sg_add_commit_listener() failed")\ #define _SSPINE_LOGITEM_XMACRO(item,msg) SSPINE_LOGITEM_##item, typedef enum sspine_log_item { _SSPINE_LOG_ITEMS } sspine_log_item; #undef _SSPINE_LOGITEM_XMACRO typedef struct sspine_layer_transform { sspine_vec2 size; sspine_vec2 origin; } sspine_layer_transform; typedef struct sspine_bone_transform { sspine_vec2 position; float rotation; // in degrees sspine_vec2 scale; sspine_vec2 shear; // in degrees } sspine_bone_transform; typedef struct sspine_context_desc { int max_vertices; int max_commands; sg_pixel_format color_format; sg_pixel_format depth_format; int sample_count; sg_color_mask color_write_mask; } sspine_context_desc; typedef struct sspine_context_info { int num_vertices; // current number of vertices int num_indices; // current number of indices int num_commands; // current number of commands } sspine_context_info; typedef struct sspine_image_info { bool valid; sg_image sgimage; sg_filter min_filter; sg_filter mag_filter; sg_wrap wrap_u; sg_wrap wrap_v; int width; int height; bool premul_alpha; sspine_string filename; } sspine_image_info; typedef struct sspine_atlas_overrides { sg_filter min_filter; sg_filter mag_filter; sg_wrap wrap_u; sg_wrap wrap_v; bool premul_alpha_enabled; bool premul_alpha_disabled; } sspine_atlas_overrides; typedef struct sspine_atlas_desc { sspine_range data; sspine_atlas_overrides override; } sspine_atlas_desc; typedef struct sspine_atlas_page_info { bool valid; sspine_atlas atlas; sspine_image_info image; sspine_atlas_overrides overrides; } sspine_atlas_page_info; typedef struct sspine_skeleton_desc { sspine_atlas atlas; float prescale; float anim_default_mix; const char* json_data; sspine_range binary_data; } sspine_skeleton_desc; typedef struct sspine_skinset_desc { sspine_skeleton skeleton; sspine_skin skins[SSPINE_MAX_SKINSET_SKINS]; } sspine_skinset_desc; typedef struct sspine_anim_info { bool valid; int index; float duration; sspine_string name; } sspine_anim_info; typedef struct sspine_bone_info { bool valid; int index; sspine_bone parent_bone; float length; sspine_bone_transform pose; sspine_color color; sspine_string name; } sspine_bone_info; typedef struct sspine_slot_info { bool valid; int index; sspine_bone bone; sspine_color color; sspine_string attachment_name; sspine_string name; } sspine_slot_info; typedef struct sspine_iktarget_info { bool valid; int index; sspine_bone target_bone; sspine_string name; } sspine_iktarget_info; typedef struct sspine_skin_info { bool valid; int index; sspine_string name; } sspine_skin_info; typedef struct sspine_event_info { bool valid; int index; int int_value; float float_value; float volume; float balance; sspine_string name; sspine_string string_value; sspine_string audio_path; } sspine_event_info; typedef struct sspine_triggered_event_info { bool valid; sspine_event event; float time; int int_value; float float_value; float volume; float balance; sspine_string string_value; } sspine_triggered_event_info; typedef struct sspine_instance_desc { sspine_skeleton skeleton; } sspine_instance_desc; typedef struct sspine_allocator { void* (*alloc)(size_t size, void* user_data); void (*free)(void* ptr, void* user_data); void* user_data; } sspine_allocator; typedef struct sspine_logger { void (*func)( const char* tag, // always "sspine" uint32_t log_level, // 0=panic, 1=error, 2=warning, 3=info uint32_t log_item_id, // SSPINE_LOGITEM_* const char* message_or_null, // a message string, may be nullptr in release mode uint32_t line_nr, // line number in sokol_spine.h const char* filename_or_null, // the source filename, may be nullptr in release mode void* user_data); void* user_data; } sspine_logger; typedef struct sspine_desc { int max_vertices; int max_commands; int context_pool_size; int atlas_pool_size; int skeleton_pool_size; int skinset_pool_size; int instance_pool_size; sg_pixel_format color_format; sg_pixel_format depth_format; int sample_count; sg_color_mask color_write_mask; sspine_allocator allocator; // optional allocation override functions (default: malloc/free) sspine_logger logger; // optional logging function (default: NO LOGGING!) } sspine_desc; // setup/shutdown SOKOL_SPINE_API_DECL void sspine_setup(const sspine_desc* desc); SOKOL_SPINE_API_DECL void sspine_shutdown(void); // context functions SOKOL_SPINE_API_DECL sspine_context sspine_make_context(const sspine_context_desc* desc); SOKOL_SPINE_API_DECL void sspine_destroy_context(sspine_context ctx); SOKOL_SPINE_API_DECL void sspine_set_context(sspine_context ctx); SOKOL_SPINE_API_DECL sspine_context sspine_get_context(void); SOKOL_SPINE_API_DECL sspine_context sspine_default_context(void); SOKOL_SPINE_API_DECL sspine_context_info sspine_get_context_info(sspine_context ctx); // create and destroy spine objects SOKOL_SPINE_API_DECL sspine_atlas sspine_make_atlas(const sspine_atlas_desc* desc); SOKOL_SPINE_API_DECL sspine_skeleton sspine_make_skeleton(const sspine_skeleton_desc* desc); SOKOL_SPINE_API_DECL sspine_skinset sspine_make_skinset(const sspine_skinset_desc* desc); SOKOL_SPINE_API_DECL sspine_instance sspine_make_instance(const sspine_instance_desc* desc); SOKOL_SPINE_API_DECL void sspine_destroy_atlas(sspine_atlas atlas); SOKOL_SPINE_API_DECL void sspine_destroy_skeleton(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL void sspine_destroy_skinset(sspine_skinset skinset); SOKOL_SPINE_API_DECL void sspine_destroy_instance(sspine_instance instance); // configure instance appearance via skinsets SOKOL_SPINE_API_DECL void sspine_set_skinset(sspine_instance instance, sspine_skinset skinset); // update instance animations before drawing SOKOL_SPINE_API_DECL void sspine_update_instance(sspine_instance instance, float delta_time); // iterate over triggered events after updating an instance SOKOL_SPINE_API_DECL int sspine_num_triggered_events(sspine_instance instance); SOKOL_SPINE_API_DECL sspine_triggered_event_info sspine_get_triggered_event_info(sspine_instance instance, int triggered_event_index); // draw instance into current or explicit context SOKOL_SPINE_API_DECL void sspine_draw_instance_in_layer(sspine_instance instance, int layer); SOKOL_SPINE_API_DECL void sspine_context_draw_instance_in_layer(sspine_context ctx, sspine_instance instance, int layer); // helper function to convert sspine_layer_transform into projection matrix SOKOL_SPINE_API_DECL sspine_mat4 sspine_layer_transform_to_mat4(const sspine_layer_transform* tform); // draw a layer in current context or explicit context (call once per context and frame in sokol-gfx pass) SOKOL_SPINE_API_DECL void sspine_draw_layer(int layer, const sspine_layer_transform* tform); SOKOL_SPINE_API_DECL void sspine_context_draw_layer(sspine_context ctx, int layer, const sspine_layer_transform* tform); // get current resource state (INITIAL, ALLOC, VALID, FAILED, INVALID) SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_context_resource_state(sspine_context context); SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_atlas_resource_state(sspine_atlas atlas); SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_skeleton_resource_state(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_skinset_resource_state(sspine_skinset skinset); SOKOL_SPINE_API_DECL sspine_resource_state sspine_get_instance_resource_state(sspine_instance instance); // shortcut for sspine_get_*_state() == SSPINE_RESOURCESTATE_VALID SOKOL_SPINE_API_DECL bool sspine_context_valid(sspine_context context); SOKOL_SPINE_API_DECL bool sspine_atlas_valid(sspine_atlas atlas); SOKOL_SPINE_API_DECL bool sspine_skeleton_valid(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL bool sspine_instance_valid(sspine_instance instance); SOKOL_SPINE_API_DECL bool sspine_skinset_valid(sspine_skinset skinset); // get dependency objects SOKOL_SPINE_API_DECL sspine_atlas sspine_get_skeleton_atlas(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL sspine_skeleton sspine_get_instance_skeleton(sspine_instance instance); // atlas images SOKOL_SPINE_API_DECL int sspine_num_images(sspine_atlas atlas); SOKOL_SPINE_API_DECL sspine_image sspine_image_by_index(sspine_atlas atlas, int index); SOKOL_SPINE_API_DECL bool sspine_image_valid(sspine_image image); SOKOL_SPINE_API_DECL bool sspine_image_equal(sspine_image first, sspine_image second); SOKOL_SPINE_API_DECL sspine_image_info sspine_get_image_info(sspine_image image); // atlas page functions SOKOL_SPINE_API_DECL int sspine_num_atlas_pages(sspine_atlas atlas); SOKOL_SPINE_API_DECL sspine_atlas_page sspine_atlas_page_by_index(sspine_atlas atlas, int index); SOKOL_SPINE_API_DECL bool sspine_atlas_page_valid(sspine_atlas_page page); SOKOL_SPINE_API_DECL bool sspine_atlas_page_equal(sspine_atlas_page first, sspine_atlas_page second); SOKOL_SPINE_API_DECL sspine_atlas_page_info sspine_get_atlas_page_info(sspine_atlas_page page); // instance transform functions SOKOL_SPINE_API_DECL void sspine_set_position(sspine_instance instance, sspine_vec2 position); SOKOL_SPINE_API_DECL void sspine_set_scale(sspine_instance instance, sspine_vec2 scale); SOKOL_SPINE_API_DECL void sspine_set_color(sspine_instance instance, sspine_color color); SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_position(sspine_instance instance); SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_scale(sspine_instance instance); SOKOL_SPINE_API_DECL sspine_color sspine_get_color(sspine_instance instance); // instance animation functions SOKOL_SPINE_API_DECL int sspine_num_anims(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL sspine_anim sspine_anim_by_name(sspine_skeleton skeleton, const char* name); SOKOL_SPINE_API_DECL sspine_anim sspine_anim_by_index(sspine_skeleton skeleton, int index); SOKOL_SPINE_API_DECL bool sspine_anim_valid(sspine_anim anim); SOKOL_SPINE_API_DECL bool sspine_anim_equal(sspine_anim first, sspine_anim second); SOKOL_SPINE_API_DECL sspine_anim_info sspine_get_anim_info(sspine_anim anim); SOKOL_SPINE_API_DECL void sspine_clear_animation_tracks(sspine_instance instance); SOKOL_SPINE_API_DECL void sspine_clear_animation_track(sspine_instance instance, int track_index); SOKOL_SPINE_API_DECL void sspine_set_animation(sspine_instance instance, sspine_anim anim, int track_index, bool loop); SOKOL_SPINE_API_DECL void sspine_add_animation(sspine_instance instance, sspine_anim anim, int track_index, bool loop, float delay); SOKOL_SPINE_API_DECL void sspine_set_empty_animation(sspine_instance instance, int track_index, float mix_duration); SOKOL_SPINE_API_DECL void sspine_add_empty_animation(sspine_instance instance, int track_index, float mix_duration, float delay); // bone functions SOKOL_SPINE_API_DECL int sspine_num_bones(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL sspine_bone sspine_bone_by_name(sspine_skeleton skeleton, const char* name); SOKOL_SPINE_API_DECL sspine_bone sspine_bone_by_index(sspine_skeleton skeleton, int index); SOKOL_SPINE_API_DECL bool sspine_bone_valid(sspine_bone bone); SOKOL_SPINE_API_DECL bool sspine_bone_equal(sspine_bone first, sspine_bone second); SOKOL_SPINE_API_DECL sspine_bone_info sspine_get_bone_info(sspine_bone bone); SOKOL_SPINE_API_DECL void sspine_set_bone_transform(sspine_instance instance, sspine_bone bone, const sspine_bone_transform* transform); SOKOL_SPINE_API_DECL void sspine_set_bone_position(sspine_instance instance, sspine_bone bone, sspine_vec2 position); SOKOL_SPINE_API_DECL void sspine_set_bone_rotation(sspine_instance instance, sspine_bone bone, float rotation); SOKOL_SPINE_API_DECL void sspine_set_bone_scale(sspine_instance instance, sspine_bone bone, sspine_vec2 scale); SOKOL_SPINE_API_DECL void sspine_set_bone_shear(sspine_instance instance, sspine_bone bone, sspine_vec2 shear); SOKOL_SPINE_API_DECL sspine_bone_transform sspine_get_bone_transform(sspine_instance instance, sspine_bone bone); SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_bone_position(sspine_instance instance, sspine_bone bone); SOKOL_SPINE_API_DECL float sspine_get_bone_rotation(sspine_instance instance, sspine_bone bone); SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_bone_scale(sspine_instance instance, sspine_bone bone); SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_bone_shear(sspine_instance instance, sspine_bone bone); SOKOL_SPINE_API_DECL sspine_vec2 sspine_get_bone_world_position(sspine_instance instance, sspine_bone bone); SOKOL_SPINE_API_DECL sspine_vec2 sspine_bone_local_to_world(sspine_instance instance, sspine_bone bone, sspine_vec2 local_pos); SOKOL_SPINE_API_DECL sspine_vec2 sspine_bone_world_to_local(sspine_instance instance, sspine_bone bone, sspine_vec2 world_pos); // slot functions SOKOL_SPINE_API_DECL int sspine_num_slots(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL sspine_slot sspine_slot_by_name(sspine_skeleton skeleton, const char* name); SOKOL_SPINE_API_DECL sspine_slot sspine_slot_by_index(sspine_skeleton skeleton, int index); SOKOL_SPINE_API_DECL bool sspine_slot_valid(sspine_slot slot); SOKOL_SPINE_API_DECL bool sspine_slot_equal(sspine_slot first, sspine_slot second); SOKOL_SPINE_API_DECL sspine_slot_info sspine_get_slot_info(sspine_slot slot); SOKOL_SPINE_API_DECL void sspine_set_slot_color(sspine_instance instance, sspine_slot slot, sspine_color color); SOKOL_SPINE_API_DECL sspine_color sspine_get_slot_color(sspine_instance instance, sspine_slot slot); // event functions SOKOL_SPINE_API_DECL int sspine_num_events(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL sspine_event sspine_event_by_name(sspine_skeleton skeleton, const char* name); SOKOL_SPINE_API_DECL sspine_event sspine_event_by_index(sspine_skeleton skeleton, int index); SOKOL_SPINE_API_DECL bool sspine_event_valid(sspine_event event); SOKOL_SPINE_API_DECL bool sspine_event_equal(sspine_event first, sspine_event second); SOKOL_SPINE_API_DECL sspine_event_info sspine_get_event_info(sspine_event event); // ik target functions SOKOL_SPINE_API_DECL int sspine_num_iktargets(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL sspine_iktarget sspine_iktarget_by_name(sspine_skeleton skeleton, const char* name); SOKOL_SPINE_API_DECL sspine_iktarget sspine_iktarget_by_index(sspine_skeleton skeleton, int index); SOKOL_SPINE_API_DECL bool sspine_iktarget_valid(sspine_iktarget iktarget); SOKOL_SPINE_API_DECL bool sspine_iktarget_equal(sspine_iktarget first, sspine_iktarget second); SOKOL_SPINE_API_DECL sspine_iktarget_info sspine_get_iktarget_info(sspine_iktarget iktarget); SOKOL_SPINE_API_DECL void sspine_set_iktarget_world_pos(sspine_instance instance, sspine_iktarget iktarget, sspine_vec2 world_pos); // skin functions SOKOL_SPINE_API_DECL int sspine_num_skins(sspine_skeleton skeleton); SOKOL_SPINE_API_DECL sspine_skin sspine_skin_by_name(sspine_skeleton skeleton, const char* name); SOKOL_SPINE_API_DECL sspine_skin sspine_skin_by_index(sspine_skeleton skeleton, int index); SOKOL_SPINE_API_DECL bool sspine_skin_valid(sspine_skin skin); SOKOL_SPINE_API_DECL bool sspine_skin_equal(sspine_skin first, sspine_skin second); SOKOL_SPINE_API_DECL sspine_skin_info sspine_get_skin_info(sspine_skin skin); SOKOL_SPINE_API_DECL void sspine_set_skin(sspine_instance instance, sspine_skin skin); #ifdef __cplusplus } // extern "C" #endif // ██ ███ ███ ██████ ██ ███████ ███ ███ ███████ ███ ██ ████████ █████ ████████ ██ ██████ ███ ██ // ██ ████ ████ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ // ██ ██ ████ ██ ██████ ██ █████ ██ ████ ██ █████ ██ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ // ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ // ██ ██ ██ ██ ███████ ███████ ██ ██ ███████ ██ ████ ██ ██ ██ ██ ██ ██████ ██ ████ // // >>implementation #ifdef SOKOL_SPINE_IMPL #define SOKOL_SPINE_IMPL_INCLUDED (1) #if !defined(SPINE_SPINE_H_) #error "Please include spine/spine.h before the sokol_spine.h implementation" #endif #ifndef SOKOL_API_IMPL #define SOKOL_API_IMPL #endif #ifndef SOKOL_DEBUG #ifndef NDEBUG #define SOKOL_DEBUG (1) #endif #endif #ifndef SOKOL_ASSERT #include #define SOKOL_ASSERT(c) assert(c) #endif #ifndef SOKOL_UNREACHABLE #define SOKOL_UNREACHABLE SOKOL_ASSERT(false) #endif #ifndef SOKOL_UNUSED #define SOKOL_UNUSED(x) (void)(x) #endif #include // malloc/free #include // memset, strcmp // ███████╗██╗ ██╗ █████╗ ██████╗ ███████╗██████╗ ███████╗ // ██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝ // ███████╗███████║███████║██║ ██║█████╗ ██████╔╝███████╗ // ╚════██║██╔══██║██╔══██║██║ ██║██╔══╝ ██╔══██╗╚════██║ // ███████║██║ ██║██║ ██║██████╔╝███████╗██║ ██║███████║ // ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝ // // >>shaders /* Embedded source compiled with: sokol-shdc -i sspine.glsl -o sspine.h -l glsl330:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgpu -b @vs vs uniform vs_params { mat4 mvp; }; in vec2 position; in vec2 texcoord0; in vec4 color0; out vec2 uv; out vec4 color; void main() { gl_Position = mvp * vec4(position, 0.0, 1.0); uv = texcoord0; color = color0; } @end @fs fs uniform sampler2D tex; uniform fs_params { float pma; }; in vec2 uv; in vec4 color; out vec4 frag_color; void main() { vec4 c0 = texture(tex, uv) * color; vec4 c1 = vec4(c0.rgb * c0.a, c0.a) * color; frag_color = mix(c0, c1, pma); } @end @program sspine vs fs */ #if defined(SOKOL_GLCORE33) static const char _sspine_vs_source_glsl330[352] = { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73,0x5f,0x70,0x61, 0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28, 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e, 0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0x0a, 0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x6c,0x61,0x79, 0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31, 0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65,0x78,0x63,0x6f,0x6f, 0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f, 0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, 0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20, 0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x67,0x6c,0x5f, 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61,0x74,0x34,0x28, 0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c,0x20,0x76,0x73, 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70, 0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f,0x70,0x61,0x72, 0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x70, 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e, 0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20,0x74,0x65,0x78, 0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63,0x6f,0x6c,0x6f, 0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, }; static const char _sspine_fs_source_glsl330[300] = { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x33,0x30,0x0a,0x0a,0x75,0x6e, 0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x73,0x5f,0x70,0x61, 0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x3b,0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d, 0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x3b,0x0a, 0x0a,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a,0x69,0x6e,0x20, 0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f, 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, 0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63, 0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e, 0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x5f,0x32, 0x35,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x2c, 0x20,0x75,0x76,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x20,0x20, 0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x5f,0x33,0x34,0x20,0x3d,0x20,0x5f,0x32, 0x35,0x2e,0x77,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f, 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x5f,0x32,0x35,0x2c,0x20,0x76, 0x65,0x63,0x34,0x28,0x5f,0x32,0x35,0x2e,0x78,0x79,0x7a,0x20,0x2a,0x20,0x5f,0x33, 0x34,0x2c,0x20,0x5f,0x33,0x34,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x2c, 0x20,0x76,0x65,0x63,0x34,0x28,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b, 0x30,0x5d,0x2e,0x78,0x29,0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, }; #elif defined(SOKOL_GLES3) static const char _sspine_vs_source_glsl300es[355] = { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, 0x0a,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x73, 0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x34,0x5d,0x3b,0x0a,0x6c,0x61,0x79,0x6f, 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29, 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, 0x6e,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, 0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20, 0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x65,0x78, 0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34, 0x20,0x63,0x6f,0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c, 0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20, 0x76,0x65,0x63,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x0a,0x76,0x6f, 0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20, 0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x6d,0x61, 0x74,0x34,0x28,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2c, 0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x2c,0x20,0x76, 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x32,0x5d,0x2c,0x20,0x76,0x73,0x5f, 0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x33,0x5d,0x29,0x20,0x2a,0x20,0x76,0x65,0x63, 0x34,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x30,0x2e,0x30,0x2c, 0x20,0x31,0x2e,0x30,0x29,0x3b,0x0a,0x20,0x20,0x20,0x20,0x75,0x76,0x20,0x3d,0x20, 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x63, 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x7d, 0x0a,0x0a,0x00, }; static const char _sspine_fs_source_glsl300es[391] = { 0x23,0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x33,0x30,0x30,0x20,0x65,0x73,0x0a, 0x70,0x72,0x65,0x63,0x69,0x73,0x69,0x6f,0x6e,0x20,0x6d,0x65,0x64,0x69,0x75,0x6d, 0x70,0x20,0x66,0x6c,0x6f,0x61,0x74,0x3b,0x0a,0x70,0x72,0x65,0x63,0x69,0x73,0x69, 0x6f,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x69,0x6e,0x74,0x3b,0x0a,0x0a,0x75, 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63, 0x34,0x20,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x31,0x5d,0x3b,0x0a, 0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x73,0x61, 0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x3b,0x0a,0x0a,0x69,0x6e, 0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x3b,0x0a, 0x69,0x6e,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x63,0x6f, 0x6c,0x6f,0x72,0x3b,0x0a,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x6f,0x75,0x74,0x20,0x68,0x69, 0x67,0x68,0x70,0x20,0x76,0x65,0x63,0x34,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f, 0x6c,0x6f,0x72,0x3b,0x0a,0x0a,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28, 0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x76,0x65, 0x63,0x34,0x20,0x5f,0x32,0x35,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, 0x28,0x74,0x65,0x78,0x2c,0x20,0x75,0x76,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f, 0x72,0x3b,0x0a,0x20,0x20,0x20,0x20,0x68,0x69,0x67,0x68,0x70,0x20,0x66,0x6c,0x6f, 0x61,0x74,0x20,0x5f,0x33,0x34,0x20,0x3d,0x20,0x5f,0x32,0x35,0x2e,0x77,0x3b,0x0a, 0x20,0x20,0x20,0x20,0x66,0x72,0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d, 0x20,0x6d,0x69,0x78,0x28,0x5f,0x32,0x35,0x2c,0x20,0x76,0x65,0x63,0x34,0x28,0x5f, 0x32,0x35,0x2e,0x78,0x79,0x7a,0x20,0x2a,0x20,0x5f,0x33,0x34,0x2c,0x20,0x5f,0x33, 0x34,0x29,0x20,0x2a,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x76,0x65,0x63,0x34, 0x28,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x5b,0x30,0x5d,0x2e,0x78,0x29, 0x29,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, }; #elif defined(SOKOL_D3D11) static const uint8_t _sspine_vs_bytecode_hlsl4[844] = { 0x44,0x58,0x42,0x43,0xb2,0xdb,0x57,0x6f,0x29,0xa5,0x26,0x49,0xa3,0x0e,0xb4,0x9e, 0x0f,0x0d,0x7f,0xcd,0x01,0x00,0x00,0x00,0x4c,0x03,0x00,0x00,0x05,0x00,0x00,0x00, 0x34,0x00,0x00,0x00,0xf4,0x00,0x00,0x00,0x58,0x01,0x00,0x00,0xc8,0x01,0x00,0x00, 0xd0,0x02,0x00,0x00,0x52,0x44,0x45,0x46,0xb8,0x00,0x00,0x00,0x01,0x00,0x00,0x00, 0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xfe,0xff, 0x10,0x81,0x00,0x00,0x90,0x00,0x00,0x00,0x3c,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d, 0x73,0x00,0xab,0xab,0x3c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x60,0x00,0x00,0x00, 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x80,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x5f,0x32,0x31,0x5f,0x6d,0x76,0x70,0x00,0x02,0x00,0x03,0x00, 0x04,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4d,0x69,0x63,0x72, 0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53, 0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31, 0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x5c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, 0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x50,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, 0x03,0x03,0x00,0x00,0x50,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43, 0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab,0x4f,0x53,0x47,0x4e,0x68,0x00,0x00,0x00, 0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0c,0x00,0x00, 0x50,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x59,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0f,0x00,0x00,0x00, 0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0x53,0x56,0x5f,0x50,0x6f,0x73,0x69, 0x74,0x69,0x6f,0x6e,0x00,0xab,0xab,0xab,0x53,0x48,0x44,0x52,0x00,0x01,0x00,0x00, 0x40,0x00,0x01,0x00,0x40,0x00,0x00,0x00,0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00, 0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0x32,0x10,0x10,0x00, 0x00,0x00,0x00,0x00,0x5f,0x00,0x00,0x03,0x32,0x10,0x10,0x00,0x01,0x00,0x00,0x00, 0x5f,0x00,0x00,0x03,0xf2,0x10,0x10,0x00,0x02,0x00,0x00,0x00,0x65,0x00,0x00,0x03, 0x32,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00, 0x01,0x00,0x00,0x00,0x67,0x00,0x00,0x04,0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x01,0x00,0x00,0x00,0x36,0x00,0x00,0x05, 0x32,0x20,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x01,0x00,0x00,0x00, 0x36,0x00,0x00,0x05,0xf2,0x20,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x1e,0x10,0x00, 0x02,0x00,0x00,0x00,0x38,0x00,0x00,0x08,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, 0x56,0x15,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00, 0x06,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, 0xf2,0x20,0x10,0x00,0x02,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00, 0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x3e,0x00,0x00,0x01, 0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x01,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; static const uint8_t _sspine_fs_bytecode_hlsl4[876] = { 0x44,0x58,0x42,0x43,0xd8,0xf3,0x35,0x5e,0xf6,0xd6,0x61,0x80,0x71,0x85,0x56,0x46, 0x08,0x09,0x74,0xcd,0x01,0x00,0x00,0x00,0x6c,0x03,0x00,0x00,0x05,0x00,0x00,0x00, 0x34,0x00,0x00,0x00,0x44,0x01,0x00,0x00,0x90,0x01,0x00,0x00,0xc4,0x01,0x00,0x00, 0xf0,0x02,0x00,0x00,0x52,0x44,0x45,0x46,0x08,0x01,0x00,0x00,0x01,0x00,0x00,0x00, 0x98,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x1c,0x00,0x00,0x00,0x00,0x04,0xff,0xff, 0x10,0x81,0x00,0x00,0xe0,0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x03,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x89,0x00,0x00,0x00,0x02,0x00,0x00,0x00, 0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x8d,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x5f,0x74,0x65,0x78,0x5f,0x73,0x61,0x6d, 0x70,0x6c,0x65,0x72,0x00,0x74,0x65,0x78,0x00,0x66,0x73,0x5f,0x70,0x61,0x72,0x61, 0x6d,0x73,0x00,0xab,0x8d,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xb0,0x00,0x00,0x00, 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc8,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0xd0,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x5f,0x35,0x30,0x5f,0x70,0x6d,0x61,0x00,0x00,0x00,0x03,0x00, 0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4d,0x69,0x63,0x72, 0x6f,0x73,0x6f,0x66,0x74,0x20,0x28,0x52,0x29,0x20,0x48,0x4c,0x53,0x4c,0x20,0x53, 0x68,0x61,0x64,0x65,0x72,0x20,0x43,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x72,0x20,0x31, 0x30,0x2e,0x31,0x00,0x49,0x53,0x47,0x4e,0x44,0x00,0x00,0x00,0x02,0x00,0x00,0x00, 0x08,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x38,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00, 0x0f,0x0f,0x00,0x00,0x54,0x45,0x58,0x43,0x4f,0x4f,0x52,0x44,0x00,0xab,0xab,0xab, 0x4f,0x53,0x47,0x4e,0x2c,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00, 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x0f,0x00,0x00,0x00,0x53,0x56,0x5f,0x54,0x61,0x72,0x67,0x65, 0x74,0x00,0xab,0xab,0x53,0x48,0x44,0x52,0x24,0x01,0x00,0x00,0x40,0x00,0x00,0x00, 0x49,0x00,0x00,0x00,0x59,0x00,0x00,0x04,0x46,0x8e,0x20,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x5a,0x00,0x00,0x03,0x00,0x60,0x10,0x00,0x00,0x00,0x00,0x00, 0x58,0x18,0x00,0x04,0x00,0x70,0x10,0x00,0x00,0x00,0x00,0x00,0x55,0x55,0x00,0x00, 0x62,0x10,0x00,0x03,0x32,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x62,0x10,0x00,0x03, 0xf2,0x10,0x10,0x00,0x01,0x00,0x00,0x00,0x65,0x00,0x00,0x03,0xf2,0x20,0x10,0x00, 0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x02,0x02,0x00,0x00,0x00,0x45,0x00,0x00,0x09, 0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x10,0x10,0x00,0x00,0x00,0x00,0x00, 0x46,0x7e,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x10,0x00,0x00,0x00,0x00,0x00, 0x38,0x00,0x00,0x07,0xf2,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00, 0x00,0x00,0x00,0x00,0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00,0x38,0x00,0x00,0x07, 0x72,0x00,0x10,0x00,0x01,0x00,0x00,0x00,0xf6,0x0f,0x10,0x00,0x00,0x00,0x00,0x00, 0x46,0x02,0x10,0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x05,0x82,0x00,0x10,0x00, 0x01,0x00,0x00,0x00,0x3a,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a, 0xf2,0x00,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x01,0x00,0x00,0x00, 0x46,0x1e,0x10,0x00,0x01,0x00,0x00,0x00,0x46,0x0e,0x10,0x80,0x41,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x32,0x00,0x00,0x0a,0xf2,0x20,0x10,0x00,0x00,0x00,0x00,0x00, 0x06,0x80,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x0e,0x10,0x00, 0x01,0x00,0x00,0x00,0x46,0x0e,0x10,0x00,0x00,0x00,0x00,0x00,0x3e,0x00,0x00,0x01, 0x53,0x54,0x41,0x54,0x74,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; #elif defined(SOKOL_METAL) static const uint8_t _sspine_vs_bytecode_metal_macos[3068] = { 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x01,0x00,0x00,0x00,0x00,0x00,0x00, 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x01,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x00,0x1c,0x82,0xf4,0xbb,0x79,0xa7, 0x30,0x7f,0x4a,0xbc,0xb6,0x93,0x11,0x29,0xf6,0x24,0x44,0x87,0xfa,0x98,0x7e,0xd3, 0x73,0xca,0x18,0xc6,0xe1,0x22,0x50,0x45,0x09,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x37,0x00,0x00,0x00,0x56,0x41,0x54, 0x54,0x22,0x00,0x03,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, 0x72,0x30,0x00,0x02,0x80,0x56,0x41,0x54,0x59,0x05,0x00,0x03,0x00,0x04,0x04,0x06, 0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b, 0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0xdc,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xb4,0x02,0x00,0x00,0x0b,0x82,0x20,0x00, 0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49, 0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62, 0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49, 0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72, 0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00, 0x51,0x18,0x00,0x00,0x81,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, 0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,0x1e,0xca,0x01,0x20,0xdc, 0xe1,0x1d,0xda,0xc0,0x1c,0xe4,0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21, 0x1d,0xdc,0x81,0x1e,0xca,0x41,0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0x01,0xa0, 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,0x77,0x78,0x87,0x36,0x30,0x07,0x79,0x08,0x87, 0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0,0x87,0x72,0x90,0x87,0x36, 0x28,0x07,0x76,0x48,0x87,0x76,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, 0x87,0x70,0x30,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87, 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1, 0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,0x87,0x79,0x68,0x03,0x73, 0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c, 0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, 0x40,0x1f,0xca,0x41,0x1e,0xde,0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21, 0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68, 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, 0x98,0x87,0x72,0x00,0x36,0x18,0x02,0x01,0x2c,0x40,0x05,0x00,0x49,0x18,0x00,0x00, 0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00,0x1f,0x00,0x00,0x00, 0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3, 0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4,0x4c,0x10,0x44,0x33,0x00, 0xc3,0x08,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x42,0x24,0x81,0x98,0x89,0x9a,0x07, 0x7a,0x90,0x87,0x7a,0x18,0x07,0x7a,0x70,0x83,0x76,0x28,0x07,0x7a,0x08,0x07,0x76, 0xd0,0x03,0x3d,0x68,0x87,0x70,0xa0,0x07,0x79,0x48,0x07,0x7c,0x40,0x01,0x39,0x48, 0x9a,0x22,0x4a,0x98,0xfc,0x4a,0xfa,0x1f,0x20,0x02,0x18,0x09,0x05,0x65,0x10,0xc1, 0x10,0x4a,0x31,0x42,0x10,0x87,0xd0,0x40,0xc0,0x1c,0x01,0x18,0xa4,0xc0,0x9a,0x23, 0x00,0x85,0x41,0x04,0x41,0x18,0x46,0x20,0x96,0x11,0x00,0x00,0x13,0xb2,0x70,0x48, 0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83, 0x76,0x08,0x87,0x71,0x78,0x87,0x79,0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38, 0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5,0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a, 0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0, 0x07,0x78,0xd0,0x06,0xe9,0x80,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e, 0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73, 0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40, 0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07, 0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a, 0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10, 0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07, 0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72, 0xa0,0x07,0x73,0x20,0x07,0x6d,0x60,0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0, 0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07, 0x76,0x40,0x07,0x6d,0x60,0x0f,0x79,0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a, 0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20, 0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07, 0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72, 0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0, 0x06,0xf6,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07, 0x71,0x20,0x07,0x6d,0x60,0x0f,0x71,0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70, 0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0, 0x07,0x71,0x60,0x07,0x7a,0x30,0x07,0x72,0x30,0x84,0x49,0x00,0x00,0x08,0x00,0x00, 0x00,0x00,0x00,0xc8,0x02,0x01,0x00,0x00,0x0a,0x00,0x00,0x00,0x32,0x1e,0x98,0x10, 0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50, 0x04,0x05,0x18,0x50,0x08,0x05,0x51,0x06,0x05,0x42,0x6d,0x04,0x80,0xd8,0x58,0x02, 0x33,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xea,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10, 0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28, 0x00,0xa3,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c, 0x81,0x22,0x2c,0x05,0xe3,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c, 0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xa5, 0x46,0x46,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06, 0x26,0xc6,0xa5,0x46,0x46,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x8c,0x45, 0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x82,0x45,0xe0,0x16, 0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26, 0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x12,0x72,0x61, 0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f, 0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x61,0x19, 0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5, 0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5, 0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5d,0x99, 0x1c,0x5d,0x19,0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97,0xba,0xb9,0x32,0x39, 0x14,0xb6,0xb7,0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f, 0x74,0x79,0x70,0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64, 0xc8,0x84,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5,0x95,0x51,0xa8,0xb3, 0x1b,0xc2,0x28,0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71,0xa9,0x9b,0x2b,0x93, 0x43,0x61,0x7b,0x1b,0x73,0x8b,0x49,0xa1,0x61,0xc6,0xf6,0x16,0x46,0x47,0xc3,0x62, 0xec,0x8d,0xed,0x4d,0x6e,0x08,0xa3,0x3c,0x8a,0xa5,0x44,0xca,0xa5,0x4c,0x0a,0x46, 0x26,0x2c,0x4d,0xce,0x05,0xee,0x6d,0x2e,0x8d,0x2e,0xed,0xcd,0x8d,0xcb,0x19,0xdb, 0x17,0xd4,0xdb,0x5c,0x1a,0x5d,0xda,0x9b,0xdb,0x10,0x45,0xd1,0x94,0x48,0xb9,0x94, 0x49,0xd9,0x86,0x18,0x4a,0xa5,0x64,0x0a,0x47,0x28,0x2c,0x4d,0xce,0xc5,0xae,0x4c, 0x8e,0xae,0x0c,0xef,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x52,0x58,0x9a,0x9c,0x0b,0xdb, 0xdb,0x58,0x18,0x5d,0xda,0x9b,0xdb,0x57,0x9a,0x1b,0x59,0x19,0x1e,0xbd,0xb3,0x32, 0xb7,0x32,0xb9,0x30,0xba,0x32,0x32,0x94,0xaf,0xaf,0xb0,0x34,0xb9,0x2f,0x38,0xb6, 0xb0,0xb1,0x32,0xb4,0x37,0x36,0xb2,0x32,0xb9,0xaf,0xaf,0x14,0x22,0x70,0x6f,0x73, 0x69,0x74,0x69,0x6f,0x6e,0x43,0xa8,0x45,0x50,0x3c,0xe5,0x5b,0x84,0x25,0x50,0xc0, 0x40,0x89,0x14,0x49,0x99,0x94,0x30,0x60,0x42,0x57,0x86,0x37,0xf6,0xf6,0x26,0x47, 0x06,0x33,0x84,0x5a,0x02,0xc5,0x53,0xbe,0x25,0x58,0x02,0x05,0x0c,0x94,0x48,0x91, 0x94,0x49,0x19,0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x65,0x50,0x3c, 0xe5,0x5b,0x86,0x25,0x50,0xc0,0x40,0x89,0x94,0x4b,0x99,0x94,0x32,0xa0,0x12,0x96, 0x26,0xe7,0x22,0x56,0x67,0x66,0x56,0x26,0xc7,0x27,0x2c,0x4d,0xce,0x45,0xac,0xce, 0xcc,0xac,0x4c,0xee,0x6b,0x2e,0x4d,0xaf,0x8c,0x48,0x58,0x9a,0x9c,0x8b,0x5c,0x59, 0x18,0x19,0xa9,0xb0,0x34,0x39,0x97,0x39,0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba,0x3c, 0xb8,0xb2,0xaf,0x34,0x37,0xb3,0x37,0x22,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34, 0x1c,0xda,0xec,0xe0,0x86,0x28,0x8b,0xb0,0x10,0x8b,0xa0,0xac,0x81,0xc2,0x06,0x8c, 0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xe6,0xd2,0xf4, 0xca,0x78,0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5,0xc1,0x95,0x7d,0x85, 0xb1,0xa5,0x9d,0xb9,0x7d,0xcd,0xa5,0xe9,0x95,0x31,0xb1,0x9b,0xfb,0x82,0x0b,0x93, 0x0b,0x6b,0x9b,0xe3,0xf0,0x25,0x13,0x33,0x84,0x0c,0x96,0x43,0x39,0x03,0x05,0x0d, 0x16,0x42,0xf9,0x16,0x61,0x09,0x94,0x34,0x50,0xd4,0x40,0x69,0x03,0xc5,0x0d,0x16, 0x42,0x79,0x83,0x05,0x51,0x22,0x05,0x0e,0x94,0x49,0x89,0x83,0x21,0x88,0x22,0x06, 0x0a,0x19,0x28,0x66,0xa0,0xc8,0xc1,0x10,0x23,0x01,0x94,0x4e,0x99,0x03,0x3e,0x6f, 0x6d,0x6e,0x69,0x70,0x6f,0x74,0x65,0x6e,0x74,0x20,0x63,0x68,0x61,0x72,0x7c,0xa6, 0xd2,0xda,0xe0,0xd8,0xca,0x40,0x86,0x56,0x56,0x40,0xa8,0x84,0x82,0x82,0x86,0x08, 0x8a,0x1d,0x0c,0x31,0x94,0x3a,0x50,0xee,0xa0,0x49,0x86,0x18,0x0a,0x1e,0x28,0x78, 0xd0,0x24,0x23,0x22,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x77,0x20,0x87, 0x7a,0x60,0x87,0x72,0x70,0x03,0x73,0x60,0x87,0x70,0x38,0x87,0x79,0x98,0x22,0x04, 0xc3,0x08,0x85,0x1d,0xd8,0xc1,0x1e,0xda,0xc1,0x0d,0xd2,0x81,0x1c,0xca,0xc1,0x1d, 0xe8,0x61,0x4a,0x50,0x8c,0x58,0xc2,0x21,0x1d,0xe4,0xc1,0x0d,0xec,0xa1,0x1c,0xe4, 0x61,0x1e,0xd2,0xe1,0x1d,0xdc,0x61,0x4a,0x60,0x8c,0xa0,0xc2,0x21,0x1d,0xe4,0xc1, 0x0d,0xd8,0x21,0x1c,0xdc,0xe1,0x1c,0xea,0x21,0x1c,0xce,0xa1,0x1c,0x7e,0xc1,0x1e, 0xca,0x41,0x1e,0xe6,0x21,0x1d,0xde,0xc1,0x1d,0xa6,0x04,0xc8,0x88,0x29,0x1c,0xd2, 0x41,0x1e,0xdc,0x60,0x1c,0xde,0xa1,0x1d,0xe0,0x21,0x1d,0xd8,0xa1,0x1c,0x7e,0xe1, 0x1d,0xe0,0x81,0x1e,0xd2,0xe1,0x1d,0xdc,0x61,0x1e,0xa6,0x0c,0x0a,0xe3,0x8c,0x50, 0xc2,0x21,0x1d,0xe4,0xc1,0x0d,0xec,0xa1,0x1c,0xe4,0x81,0x1e,0xca,0x01,0x1f,0xa6, 0x04,0x74,0x00,0x00,0x79,0x18,0x00,0x00,0x7b,0x00,0x00,0x00,0x33,0x08,0x80,0x1c, 0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07, 0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e, 0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43, 0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c, 0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76, 0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e, 0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8, 0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4, 0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68, 0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07, 0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71, 0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5, 0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4, 0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90, 0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43, 0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b, 0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78, 0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2, 0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20, 0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0, 0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83, 0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07, 0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61, 0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d, 0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39, 0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79, 0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3, 0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4, 0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8, 0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83, 0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00, 0x06,0x50,0x30,0x00,0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x1e,0x00,0x00,0x00, 0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0xf4,0xc6,0x22,0x82, 0x20,0x08,0x46,0x00,0xa8,0x95,0x40,0x19,0xd0,0x98,0x01,0xa0,0x30,0x03,0x00,0x00, 0xe3,0x15,0x07,0x33,0x4d,0x0c,0x05,0x65,0x90,0x81,0x19,0x0e,0x13,0x02,0xf9,0x8c, 0x57,0x2c,0xd0,0x75,0x21,0x14,0x94,0x41,0x06,0xe8,0x60,0x4c,0x08,0xe4,0x63,0x41, 0x01,0x9f,0xf1,0x0a,0xa8,0xe2,0x38,0x86,0x82,0x62,0x43,0x00,0x9f,0xd9,0x06,0xa7, 0x02,0x66,0x1b,0x82,0x2a,0x98,0x6d,0x08,0x06,0x21,0x83,0x80,0x18,0x00,0x00,0x00, 0x04,0x00,0x00,0x00,0x5b,0x86,0x20,0xc8,0x83,0x2d,0x43,0x11,0xe4,0xc1,0x96,0x41, 0x09,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; static const uint8_t _sspine_fs_bytecode_metal_macos[3257] = { 0x4d,0x54,0x4c,0x42,0x01,0x80,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xb9,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xe0,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0xeb,0x62,0x74,0xda,0xdc,0xfb,0x51, 0x12,0x89,0xbd,0xc2,0xa1,0xdc,0x89,0x67,0x5f,0x96,0x17,0x50,0xd6,0x8b,0xbc,0x5d, 0xa9,0xe9,0x5d,0xef,0xeb,0x59,0xe4,0x51,0x64,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, 0x00,0x01,0x00,0x01,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, 0x00,0x14,0x00,0x00,0x00,0xc8,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, 0xde,0x21,0x0c,0x00,0x00,0xef,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, 0x00,0x8e,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0xc2,0x1d,0xde, 0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1,0x0d,0xe0,0xe1,0x1d,0xd2,0xc1, 0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d,0xd2,0xa1,0x1d,0x00,0x7a,0x90, 0x87,0x7a,0x28,0x07,0x60,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87, 0x72,0x68,0x03,0x78,0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72, 0x60,0x87,0x74,0x68,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, 0x07,0x73,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77,0x30,0x87,0x79,0x68,0x03, 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1, 0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,0x77,0x98,0x87,0x36,0x30,0x07,0x78, 0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20, 0xdc,0xe1,0x1d,0xda,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, 0xa1,0x1c,0xe4,0xe1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0xa0, 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, 0x28,0x07,0x60,0x83,0x21,0x0c,0xc0,0x02,0x54,0x1b,0x8c,0x81,0x00,0x16,0xa0,0xda, 0x60,0x10,0x05,0xb0,0x00,0xd5,0x06,0xa3,0xf8,0xff,0xff,0xff,0xff,0x01,0x90,0x00, 0x6a,0x03,0x62,0xfc,0xff,0xff,0xff,0xff,0x00,0x30,0x80,0x04,0x54,0x1b,0x8c,0x23, 0x00,0x16,0xa0,0xda,0x60,0x20,0x02,0xb0,0x00,0x15,0x00,0x00,0x00,0x49,0x18,0x00, 0x00,0x03,0x00,0x00,0x00,0x13,0x88,0x40,0x18,0x88,0x09,0x41,0x31,0x61,0x30,0x0e, 0x04,0x89,0x20,0x00,0x00,0x27,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85, 0x04,0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a, 0x8c,0x0b,0x84,0xa4,0x4c,0x10,0x6c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x30,0x8c, 0x20,0x00,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x48,0xff,0x03,0x44,0x00,0x23,0xa1, 0x00,0x0c,0x22,0x10,0xc2,0x51,0xd2,0x14,0x51,0xc2,0xe4,0xff,0x13,0x71,0x4d,0x54, 0x44,0xfc,0xf6,0xf0,0x4f,0x63,0x04,0xc0,0x20,0x82,0x11,0x5c,0x24,0x4d,0x11,0x25, 0x4c,0xfe,0x2f,0x01,0xcc,0xb3,0x10,0xd1,0x3f,0x8d,0x11,0x00,0x83,0x08,0x88,0x50, 0x0c,0x31,0x42,0x39,0x89,0x54,0x21,0x42,0x08,0x81,0xd8,0x1c,0x41,0x30,0x47,0x00, 0x06,0xc3,0x08,0xc2,0x53,0x90,0x70,0xd2,0x70,0xd0,0x01,0x8a,0x03,0x01,0x29,0xf0, 0x86,0x11,0x86,0x67,0x18,0x61,0x00,0x86,0x11,0x88,0x67,0x8e,0x00,0x14,0x06,0x11, 0x00,0x61,0x04,0x00,0x00,0x13,0xb2,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x76,0x08,0x87,0x71,0x78,0x87,0x79, 0xc0,0x87,0x38,0x80,0x03,0x37,0x88,0x83,0x38,0x70,0x03,0x38,0xd8,0x70,0x1b,0xe5, 0xd0,0x06,0xf0,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, 0x07,0x6d,0x90,0x0e,0x71,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x80,0x07, 0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x71,0x60,0x07,0x7a,0x10,0x07,0x76, 0xa0,0x07,0x71,0x60,0x07,0x6d,0x90,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0, 0x07,0x73,0x20,0x07,0x6d,0x90,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07, 0x76,0x40,0x07,0x6d,0x60,0x0e,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73, 0x20,0x07,0x6d,0x60,0x0e,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40, 0x07,0x6d,0x60,0x0f,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07, 0x6d,0x60,0x0f,0x72,0x40,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d, 0x60,0x0f,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x6d,0x60, 0x0f,0x74,0x80,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f, 0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x6d,0x60,0x0f,0x79, 0x60,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60, 0x0f,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07, 0x78,0xd0,0x06,0xf6,0x10,0x07,0x79,0x20,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a, 0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50, 0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x50,0x07,0x71,0x20,0x07, 0x7a,0x50,0x07,0x71,0x20,0x07,0x7a,0x50,0x07,0x71,0x20,0x07,0x6d,0x60,0x0f,0x71, 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00, 0x07,0x72,0x40,0x07,0x6d,0xe0,0x0e,0x78,0xa0,0x07,0x71,0x60,0x07,0x7a,0x30,0x07, 0x72,0x30,0x84,0x59,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x18,0xc2,0x34,0x40, 0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x0c,0x61,0x24,0x20,0x00,0x06,0x00,0x00,0x00, 0x00,0x00,0xb2,0x40,0x00,0x09,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c, 0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x7a,0x23,0x00,0x25,0x50,0x08,0x45,0x50, 0x10,0x65,0x40,0x78,0x04,0x80,0xe8,0x58,0x02,0x33,0x00,0x00,0x00,0x79,0x18,0x00, 0x00,0xfa,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32, 0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x63,0x4c,0x00,0xa5,0x50,0xb9,0x1b,0x43,0x0b, 0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x3c,0xc4,0x24,0x3c,0x05,0xe3,0x20,0x08, 0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed, 0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xa5,0x46,0x46,0x06,0x04,0xa5,0xad,0x8c, 0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xa5,0x46,0x46,0x26,0x65, 0x88,0x30,0x11,0x43,0x8c,0x87,0x78,0x8e,0x67,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36, 0x04,0x99,0x8e,0x87,0x78,0x88,0x67,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06, 0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17, 0x26,0xc6,0x56,0x36,0x44,0x98,0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70, 0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e, 0x61,0x62,0x6c,0x65,0x43,0x84,0x69,0x61,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5, 0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9, 0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x91,0xa5,0xcd,0x85,0x89,0xb1,0x95,0x0d,0x11, 0xa6,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5c,0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98, 0xdc,0x59,0x19,0x1d,0xa3,0xb0,0x34,0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8, 0xb2,0x2f,0xb7,0xb0,0xb6,0x32,0x1a,0x66,0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2, 0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xdc,0xc2,0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d, 0x8d,0xbd,0xb1,0xbd,0xc9,0x0d,0x61,0xa6,0xe7,0x19,0x26,0x68,0x8a,0x26,0x69,0x9a, 0x86,0x08,0x13,0x45,0x29,0x2c,0x4d,0xce,0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d, 0xee,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98, 0x1b,0x4c,0x0a,0x95,0xb0,0x34,0x39,0x97,0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61, 0x69,0x72,0x2e,0x70,0x65,0x72,0x73,0x70,0x65,0x63,0x74,0x69,0x76,0x65,0x34,0xcc, 0xd8,0xde,0xc2,0xe8,0x64,0x28,0xd4,0xd9,0x0d,0x91,0x9e,0x61,0xb2,0xa6,0x6b,0xc2, 0xa6,0x6c,0x82,0x26,0x6d,0x92,0xa6,0x8d,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb, 0x98,0x5b,0x4c,0x0a,0x8b,0xb1,0x37,0xb6,0x37,0xb9,0x21,0xd2,0x43,0x4c,0xd6,0xd4, 0x4d,0xd8,0x94,0x4d,0xd0,0x14,0x4d,0xd2,0xe4,0x51,0x09,0x4b,0x93,0x73,0x11,0xab, 0x33,0x33,0x2b,0x93,0xe3,0x13,0x96,0x26,0xe7,0x22,0x56,0x67,0x66,0x56,0x26,0xf7, 0x35,0x97,0xa6,0x57,0x46,0x29,0x2c,0x4d,0xce,0x85,0xed,0x6d,0x2c,0x8c,0x2e,0xed, 0xcd,0xed,0x2b,0xcd,0x8d,0xac,0x0c,0x8f,0x48,0x58,0x9a,0x9c,0x8b,0x5c,0x59,0x18, 0x19,0xa9,0xb0,0x34,0x39,0x97,0x39,0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba,0x3c,0xb8, 0xb2,0xaf,0x34,0x37,0xb3,0x37,0x16,0x66,0x6c,0x6f,0x61,0x74,0x1c,0xe0,0xda,0xc2, 0x86,0x28,0xcf,0xf0,0x14,0xcf,0x30,0x95,0xc1,0x64,0x06,0x8c,0xc2,0xd2,0xe4,0x5c, 0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xe6,0xd2,0xf4,0xca,0x78,0x85,0xa5, 0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5,0xc1,0x95,0x7d,0x85,0xb1,0xa5,0x9d,0xb9, 0x7d,0xcd,0xa5,0xe9,0x95,0x31,0x31,0x9b,0xfb,0x82,0x0b,0x93,0x0b,0x6b,0x9b,0xe3, 0xf0,0x55,0x03,0x33,0x84,0x0c,0x1e,0x63,0x02,0x83,0x29,0x0c,0x9e,0x62,0x12,0x83, 0x67,0x78,0x88,0x69,0x0c,0x26,0x32,0x98,0xce,0x60,0x42,0x83,0xa7,0x98,0xd2,0xe0, 0x29,0x26,0x68,0x52,0x83,0x49,0x9a,0xd6,0x80,0x4b,0x58,0x9a,0x9c,0x0b,0x5d,0x19, 0x1e,0x5d,0x9d,0x5c,0x19,0x95,0xb0,0x34,0x39,0x97,0xb9,0xb0,0x36,0x38,0xb6,0x32, 0x62,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74, 0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x3e,0x1c,0xe8,0xca,0xf0,0x86,0x50,0x0f, 0x32,0xb5,0xc1,0x24,0x06,0xcf,0xf0,0x10,0x93,0x1b,0x4c,0xd0,0xf4,0x06,0x93,0x34, 0xc1,0x01,0x97,0xb0,0x34,0x39,0x97,0xb9,0xb0,0x36,0x38,0xb6,0x32,0x39,0x1e,0x73, 0x61,0x6d,0x70,0x6c,0x65,0x72,0x44,0xe8,0xca,0xf0,0xa6,0xda,0xe0,0xd8,0xe4,0x86, 0x48,0x4f,0x31,0xc9,0xc1,0x24,0x06,0xcf,0xf0,0x10,0x13,0x34,0xcd,0xc1,0x24,0x4d, 0x74,0x30,0x44,0x99,0xb8,0xe9,0x9b,0xd8,0x60,0x8a,0x83,0xa9,0x0e,0x86,0x18,0x0b, 0x30,0x55,0x93,0x1d,0xd0,0xf9,0xd2,0xa2,0x9a,0xca,0x31,0x9b,0xfb,0x82,0x0b,0x93, 0x0b,0x6b,0x9b,0xe3,0xf3,0xd6,0xe6,0x96,0x06,0xf7,0x46,0x57,0xe6,0x46,0x07,0x32, 0x86,0x16,0x26,0xc7,0x67,0x2a,0xad,0x0d,0x8e,0xad,0x0c,0x64,0x68,0x65,0x05,0x84, 0x4a,0x28,0x28,0x68,0x88,0x30,0xe9,0xc1,0x10,0x63,0xca,0x83,0x69,0x0f,0xb0,0x64, 0x88,0x31,0x95,0xc1,0xc4,0x07,0x58,0x32,0xc4,0x98,0xf0,0x60,0xea,0x03,0x2c,0x19, 0x62,0x4c,0x7e,0x30,0xf5,0x01,0x96,0x8c,0x88,0xd8,0x81,0x1d,0xec,0xa1,0x1d,0xdc, 0xa0,0x1d,0xde,0x81,0x1c,0xea,0x81,0x1d,0xca,0xc1,0x0d,0xcc,0x81,0x1d,0xc2,0xe1, 0x1c,0xe6,0x61,0x8a,0x10,0x0c,0x23,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x48, 0x07,0x72,0x28,0x07,0x77,0xa0,0x87,0x29,0x41,0x31,0x62,0x09,0x87,0x74,0x90,0x07, 0x37,0xb0,0x87,0x72,0x90,0x87,0x79,0x48,0x87,0x77,0x70,0x87,0x29,0x81,0x31,0x82, 0x0a,0x87,0x74,0x90,0x07,0x37,0x60,0x87,0x70,0x70,0x87,0x73,0xa8,0x87,0x70,0x38, 0x87,0x72,0xf8,0x05,0x7b,0x28,0x07,0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12, 0x20,0x23,0xa6,0x70,0x48,0x07,0x79,0x70,0x83,0x71,0x78,0x87,0x76,0x80,0x87,0x74, 0x60,0x87,0x72,0xf8,0x85,0x77,0x80,0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x79,0x98, 0x32,0x28,0x8c,0x33,0x82,0x09,0x87,0x74,0x90,0x07,0x37,0x30,0x07,0x79,0x08,0x87, 0x73,0x68,0x87,0x72,0x70,0x07,0x7a,0x98,0x12,0xdc,0x01,0x00,0x00,0x79,0x18,0x00, 0x00,0x7b,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d, 0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c, 0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d, 0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d, 0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79, 0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc, 0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50, 0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30, 0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03, 0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07, 0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76, 0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98, 0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8, 0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21, 0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43, 0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f, 0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70, 0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0, 0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40, 0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41, 0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e, 0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07, 0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f, 0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d, 0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38, 0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88, 0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08, 0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50, 0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01, 0x1e,0x66,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03, 0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c, 0x00,0x71,0x20,0x00,0x00,0x0b,0x00,0x00,0x00,0x26,0xb0,0x01,0x48,0xe4,0x4b,0x00, 0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45,0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d, 0x00,0x05,0x03,0x20,0x0d,0x6d,0x01,0x0d,0x80,0x44,0x3e,0x83,0x5c,0x7e,0x85,0x17, 0xb7,0x0d,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x25,0x00,0x00,0x00,0x13,0x04,0x41, 0x2c,0x10,0x00,0x00,0x00,0x0c,0x00,0x00,0x00,0x74,0x47,0x00,0xc6,0x22,0x80,0x40, 0x38,0xe6,0x20,0x06,0xc2,0xa8,0xc8,0xd5,0xc0,0x08,0x00,0xbd,0x19,0x00,0x82,0x23, 0x00,0x54,0xc7,0x1a,0x80,0x40,0x18,0x6b,0x18,0x86,0x81,0xec,0x0c,0x00,0x89,0x19, 0x00,0x0a,0x33,0x00,0x04,0x46,0x00,0x00,0x00,0x23,0x06,0xca,0x10,0x6c,0x8f,0x23, 0x29,0x47,0x12,0x58,0x20,0xc9,0x67,0x90,0x21,0x20,0x90,0x41,0x06,0xa1,0x40,0x4c, 0x08,0xe4,0x33,0xc8,0x10,0x24,0xd0,0x20,0x43,0x50,0x48,0x16,0x60,0xf2,0x19,0x6f, 0xc0,0x38,0x31,0xa0,0x60,0xcc,0x31,0x30,0x01,0x19,0x0c,0x32,0x04,0x0d,0x36,0x62, 0x60,0x08,0x01,0x1a,0x2c,0x45,0x30,0xdb,0x00,0x05,0x40,0x06,0x01,0x31,0x00,0x00, 0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0x24,0xf8,0x03,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; static const uint8_t _sspine_vs_bytecode_metal_ios[3068] = { 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xfc,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x3b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x01,0x00,0x00,0x00,0x00,0x00,0x00, 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x01,0x00,0x00,0x00,0x00,0x00,0x00, 0xf0,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, 0x01,0x00,0x00,0x48,0x41,0x53,0x48,0x20,0x00,0x0e,0xe2,0x38,0x0e,0x18,0xad,0x5b, 0x54,0x49,0xaf,0x95,0xc9,0xab,0xf7,0xda,0x83,0x18,0xe7,0x3c,0xff,0xd0,0x8d,0x85, 0x65,0x82,0x6c,0xdb,0x0b,0x02,0x06,0x0c,0x04,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, 0x00,0x01,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0x37,0x00,0x00,0x00,0x56,0x41,0x54, 0x54,0x22,0x00,0x03,0x00,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x00,0x00,0x80, 0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x00,0x01,0x80,0x63,0x6f,0x6c,0x6f, 0x72,0x30,0x00,0x02,0x80,0x56,0x41,0x54,0x59,0x05,0x00,0x03,0x00,0x04,0x04,0x06, 0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b, 0x00,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0xd8,0x0a,0x00,0x00,0xff,0xff,0xff,0xff, 0x42,0x43,0xc0,0xde,0x21,0x0c,0x00,0x00,0xb3,0x02,0x00,0x00,0x0b,0x82,0x20,0x00, 0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49, 0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62, 0x80,0x14,0x45,0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49, 0x0a,0x32,0x44,0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72, 0x24,0x07,0xc8,0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00, 0x51,0x18,0x00,0x00,0x82,0x00,0x00,0x00,0x1b,0xc8,0x25,0xf8,0xff,0xff,0xff,0xff, 0x01,0x90,0x80,0x8a,0x18,0x87,0x77,0x90,0x07,0x79,0x28,0x87,0x71,0xa0,0x07,0x76, 0xc8,0x87,0x36,0x90,0x87,0x77,0xa8,0x07,0x77,0x20,0x87,0x72,0x20,0x87,0x36,0x20, 0x87,0x74,0xb0,0x87,0x74,0x20,0x87,0x72,0x68,0x83,0x79,0x88,0x07,0x79,0xa0,0x87, 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0xc0,0x1c,0xc2,0x81, 0x1d,0xe6,0xa1,0x1c,0x00,0x82,0x1c,0xd2,0x61,0x1e,0xc2,0x41,0x1c,0xd8,0xa1,0x1c, 0xda,0x80,0x1e,0xc2,0x21,0x1d,0xd8,0xa1,0x0d,0xc6,0x21,0x1c,0xd8,0x81,0x1d,0xe6, 0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07,0x80,0x60,0x87,0x72,0x98,0x87,0x79, 0x68,0x03,0x78,0x90,0x87,0x72,0x18,0x87,0x74,0x98,0x87,0x72,0x68,0x03,0x73,0x80, 0x87,0x76,0x08,0x07,0x72,0x00,0xcc,0x21,0x1c,0xd8,0x61,0x1e,0xca,0x01,0x20,0xdc, 0xe1,0x1d,0xda,0xc0,0x1c,0xe4,0x21,0x1c,0xda,0xa1,0x1c,0xda,0x00,0x1e,0xde,0x21, 0x1d,0xdc,0x81,0x1e,0xca,0x41,0x1e,0xda,0xa0,0x1c,0xd8,0x21,0x1d,0xda,0x01,0xa0, 0x07,0x79,0xa8,0x87,0x72,0x00,0x06,0x77,0x78,0x87,0x36,0x30,0x07,0x79,0x08,0x87, 0x76,0x28,0x87,0x36,0x80,0x87,0x77,0x48,0x07,0x77,0xa0,0x87,0x72,0x90,0x87,0x36, 0x28,0x07,0x76,0x48,0x87,0x76,0x68,0x03,0x77,0x78,0x07,0x77,0x68,0x03,0x76,0x28, 0x87,0x70,0x30,0x07,0x80,0x70,0x87,0x77,0x68,0x83,0x74,0x70,0x07,0x73,0x98,0x87, 0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1, 0x1e,0xca,0x01,0x20,0xdc,0xe1,0x1d,0xda,0x40,0x1d,0xea,0xa1,0x1d,0xe0,0xa1,0x0d, 0xe8,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x1e,0x00,0x73,0x08,0x07,0x76,0x98,0x87, 0x72,0x00,0x08,0x77,0x78,0x87,0x36,0x70,0x87,0x70,0x70,0x87,0x79,0x68,0x03,0x73, 0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c, 0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xe6,0x21,0x1d,0xce,0xc1,0x1d,0xca,0x81,0x1c,0xda, 0x40,0x1f,0xca,0x41,0x1e,0xde,0x61,0x1e,0xda,0xc0,0x1c,0xe0,0xa1,0x0d,0xda,0x21, 0x1c,0xe8,0x01,0x1d,0x00,0x7a,0x90,0x87,0x7a,0x28,0x07,0x80,0x70,0x87,0x77,0x68, 0x03,0x7a,0x90,0x87,0x70,0x80,0x07,0x78,0x48,0x07,0x77,0x38,0x87,0x36,0x68,0x87, 0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1,0x1c,0x00,0x62,0x1e,0xe8,0x21, 0x1c,0xc6,0x61,0x1d,0xda,0x00,0x1e,0xe4,0xe1,0x1d,0xe8,0xa1,0x1c,0xc6,0x81,0x1e, 0xde,0x41,0x1e,0xda,0x40,0x1c,0xea,0xc1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x0d,0xe6, 0x21,0x1d,0xf4,0xa1,0x1c,0x00,0x3c,0x00,0x88,0x7a,0x70,0x87,0x79,0x08,0x07,0x73, 0x28,0x87,0x36,0x30,0x07,0x78,0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e, 0xe4,0xa1,0x1e,0xca,0x01,0x20,0xea,0x61,0x1e,0xca,0xa1,0x0d,0xe6,0xe1,0x1d,0xcc, 0x81,0x1e,0xda,0xc0,0x1c,0xd8,0xe1,0x1d,0xc2,0x81,0x1e,0x00,0x73,0x08,0x07,0x76, 0x98,0x87,0x72,0x00,0x36,0x20,0x02,0x01,0x24,0xc0,0x02,0x54,0x00,0x00,0x00,0x00, 0x49,0x18,0x00,0x00,0x01,0x00,0x00,0x00,0x13,0x84,0x40,0x00,0x89,0x20,0x00,0x00, 0x1f,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xa4,0x84, 0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a,0x8c,0x0b,0x84,0xa4,0x4c, 0x10,0x44,0x33,0x00,0xc3,0x08,0x02,0x30,0x8c,0x40,0x00,0x76,0x08,0x42,0x24,0x81, 0x98,0x89,0x9a,0x07,0x7a,0x90,0x87,0x7a,0x18,0x07,0x7a,0x70,0x83,0x76,0x28,0x07, 0x7a,0x08,0x07,0x76,0xd0,0x03,0x3d,0x68,0x87,0x70,0xa0,0x07,0x79,0x48,0x07,0x7c, 0x40,0x01,0x39,0x48,0x9a,0x22,0x4a,0x98,0xfc,0x4a,0xfa,0x1f,0x20,0x02,0x18,0x09, 0x05,0x65,0x10,0xc1,0x10,0x4a,0x31,0x42,0x10,0x87,0xd0,0x40,0xc0,0x1c,0x01,0x18, 0xa4,0xc0,0x9a,0x23,0x00,0x85,0x41,0x04,0x41,0x18,0x46,0x20,0x96,0x11,0x00,0x00, 0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83,0x70,0x80,0x07,0x78,0x60, 0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37,0x80,0x03,0x37,0x80,0x83, 0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, 0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80,0x07,0x7a,0x80,0x07,0x6d, 0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06,0xe9,0x10,0x07,0x76,0xa0, 0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9,0x30,0x07,0x72,0xa0,0x07, 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60,0x07,0x74,0xa0,0x07,0x76, 0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20, 0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, 0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a, 0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30, 0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07, 0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74, 0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0, 0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78,0xa0,0x07,0x71,0x20,0x07, 0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x7a, 0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07,0x72,0xa0,0x07,0x72,0x50, 0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x75,0x60,0x07, 0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x6d,0x60,0x0f,0x75, 0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xd0, 0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71,0x00,0x07,0x72,0x40,0x07, 0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80,0x07,0x7a,0x10,0x07,0x76, 0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x04,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x80, 0x2c,0x10,0x00,0x00,0x0a,0x00,0x00,0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90, 0x8c,0x09,0x26,0x47,0xc6,0x04,0x43,0x5a,0x25,0x30,0x02,0x50,0x04,0x05,0x18,0x50, 0x08,0x05,0x51,0x06,0x05,0x42,0x6d,0x04,0x80,0xd8,0x58,0x02,0x04,0x00,0x00,0x00, 0x79,0x18,0x00,0x00,0xea,0x00,0x00,0x00,0x1a,0x03,0x4c,0x10,0x97,0x29,0xa2,0x25, 0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x32,0x28,0x00,0xa3,0x50,0xb9, 0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62,0x2c,0x81,0x22,0x2c,0x05, 0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e,0x8c,0x0d,0xc4,0xae,0x4c, 0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6,0xc5,0x86,0x66,0x06,0x04, 0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06,0x06,0x26,0xc6,0xc5,0x86, 0x66,0xc6,0x85,0x26,0x65,0x88,0xa0,0x10,0x43,0x8c,0x25,0x58,0x8c,0x45,0x60,0xd1, 0x54,0x46,0x17,0xc6,0x36,0x04,0x51,0x8e,0x25,0x58,0x84,0x45,0xe0,0x16,0x96,0x26, 0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56,0xe6,0xf6,0x26,0xd7,0x36, 0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x50,0x12,0x72,0x61,0x69,0x72, 0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61,0x73,0x74,0x5f,0x6d,0x61, 0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x04,0x65,0x21,0x19,0x84,0xa5, 0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98,0xc9,0x85,0xb5,0x95,0x89, 0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1,0x7d,0x95,0xb9,0x85,0x89, 0xb1,0x95,0x0d,0x11,0x94,0x86,0x51,0x58,0x9a,0x9c,0x8b,0x5d,0x99,0x1c,0x5d,0x19, 0xde,0xd7,0x5b,0x1d,0x1d,0x5c,0x1d,0x1d,0x97,0xba,0xb9,0x32,0x39,0x14,0xb6,0xb7, 0x31,0x37,0x98,0x14,0x46,0x61,0x69,0x72,0x2e,0x61,0x72,0x67,0x5f,0x74,0x79,0x70, 0x65,0x5f,0x6e,0x61,0x6d,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64,0xc8,0x84,0xa5, 0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xb9,0x85,0xb5,0x95,0x51,0xa8,0xb3,0x1b,0xc2,0x28, 0x8f,0x02,0x29,0x91,0x22,0x29,0x93,0x42,0x71,0xa9,0x9b,0x2b,0x93,0x43,0x61,0x7b, 0x1b,0x73,0x8b,0x49,0xa1,0x61,0xc6,0xf6,0x16,0x46,0x47,0xc3,0x62,0xec,0x8d,0xed, 0x4d,0x6e,0x08,0xa3,0x3c,0x8a,0xa5,0x44,0xca,0xa5,0x4c,0x0a,0x46,0x26,0x2c,0x4d, 0xce,0x05,0xee,0x6d,0x2e,0x8d,0x2e,0xed,0xcd,0x8d,0xcb,0x19,0xdb,0x17,0xd4,0xdb, 0x5c,0x1a,0x5d,0xda,0x9b,0xdb,0x10,0x45,0xd1,0x94,0x48,0xb9,0x94,0x49,0xd9,0x86, 0x18,0x4a,0xa5,0x64,0x0a,0x47,0x28,0x2c,0x4d,0xce,0xc5,0xae,0x4c,0x8e,0xae,0x0c, 0xef,0x2b,0xcd,0x0d,0xae,0x8e,0x8e,0x52,0x58,0x9a,0x9c,0x0b,0xdb,0xdb,0x58,0x18, 0x5d,0xda,0x9b,0xdb,0x57,0x9a,0x1b,0x59,0x19,0x1e,0xbd,0xb3,0x32,0xb7,0x32,0xb9, 0x30,0xba,0x32,0x32,0x94,0xaf,0xaf,0xb0,0x34,0xb9,0x2f,0x38,0xb6,0xb0,0xb1,0x32, 0xb4,0x37,0x36,0xb2,0x32,0xb9,0xaf,0xaf,0x14,0x22,0x70,0x6f,0x73,0x69,0x74,0x69, 0x6f,0x6e,0x43,0xa8,0x45,0x50,0x3c,0xe5,0x5b,0x84,0x25,0x50,0xc0,0x40,0x89,0x14, 0x49,0x99,0x94,0x30,0x60,0x42,0x57,0x86,0x37,0xf6,0xf6,0x26,0x47,0x06,0x33,0x84, 0x5a,0x02,0xc5,0x53,0xbe,0x25,0x58,0x02,0x05,0x0c,0x94,0x48,0x91,0x94,0x49,0x19, 0x03,0x1a,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x43,0xa8,0x65,0x50,0x3c,0xe5,0x5b,0x86, 0x25,0x50,0xc0,0x40,0x89,0x94,0x4b,0x99,0x94,0x32,0xa0,0x12,0x96,0x26,0xe7,0x22, 0x56,0x67,0x66,0x56,0x26,0xc7,0x27,0x2c,0x4d,0xce,0x45,0xac,0xce,0xcc,0xac,0x4c, 0xee,0x6b,0x2e,0x4d,0xaf,0x8c,0x48,0x58,0x9a,0x9c,0x8b,0x5c,0x59,0x18,0x19,0xa9, 0xb0,0x34,0x39,0x97,0x39,0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba,0x3c,0xb8,0xb2,0xaf, 0x34,0x37,0xb3,0x37,0x22,0x66,0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x1c,0xda,0xec, 0xe0,0x86,0x28,0x8b,0xb0,0x10,0x8b,0xa0,0xac,0x81,0xc2,0x06,0x8c,0xc2,0xd2,0xe4, 0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0,0xca,0xbe,0xe6,0xd2,0xf4,0xca,0x78,0x85, 0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1,0xe5,0xc1,0x95,0x7d,0x85,0xb1,0xa5,0x9d, 0xb9,0x7d,0xcd,0xa5,0xe9,0x95,0x31,0xb1,0x9b,0xfb,0x82,0x0b,0x93,0x0b,0x6b,0x9b, 0xe3,0xf0,0x25,0x13,0x33,0x84,0x0c,0x96,0x43,0x39,0x03,0x05,0x0d,0x16,0x42,0xf9, 0x16,0x61,0x09,0x94,0x34,0x50,0xd4,0x40,0x69,0x03,0xc5,0x0d,0x16,0x42,0x79,0x83, 0x05,0x51,0x22,0x05,0x0e,0x94,0x49,0x89,0x83,0x21,0x88,0x22,0x06,0x0a,0x19,0x28, 0x66,0xa0,0xc8,0xc1,0x10,0x23,0x01,0x94,0x4e,0x99,0x03,0x3e,0x6f,0x6d,0x6e,0x69, 0x70,0x6f,0x74,0x65,0x6e,0x74,0x20,0x63,0x68,0x61,0x72,0x7c,0xa6,0xd2,0xda,0xe0, 0xd8,0xca,0x40,0x86,0x56,0x56,0x40,0xa8,0x84,0x82,0x82,0x86,0x08,0x8a,0x1d,0x0c, 0x31,0x94,0x3a,0x50,0xee,0xa0,0x49,0x86,0x18,0x0a,0x1e,0x28,0x78,0xd0,0x24,0x23, 0x22,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87,0x77,0x20,0x87,0x7a,0x60,0x87, 0x72,0x70,0x03,0x73,0x60,0x87,0x70,0x38,0x87,0x79,0x98,0x22,0x04,0xc3,0x08,0x85, 0x1d,0xd8,0xc1,0x1e,0xda,0xc1,0x0d,0xd2,0x81,0x1c,0xca,0xc1,0x1d,0xe8,0x61,0x4a, 0x50,0x8c,0x58,0xc2,0x21,0x1d,0xe4,0xc1,0x0d,0xec,0xa1,0x1c,0xe4,0x61,0x1e,0xd2, 0xe1,0x1d,0xdc,0x61,0x4a,0x60,0x8c,0xa0,0xc2,0x21,0x1d,0xe4,0xc1,0x0d,0xd8,0x21, 0x1c,0xdc,0xe1,0x1c,0xea,0x21,0x1c,0xce,0xa1,0x1c,0x7e,0xc1,0x1e,0xca,0x41,0x1e, 0xe6,0x21,0x1d,0xde,0xc1,0x1d,0xa6,0x04,0xc8,0x88,0x29,0x1c,0xd2,0x41,0x1e,0xdc, 0x60,0x1c,0xde,0xa1,0x1d,0xe0,0x21,0x1d,0xd8,0xa1,0x1c,0x7e,0xe1,0x1d,0xe0,0x81, 0x1e,0xd2,0xe1,0x1d,0xdc,0x61,0x1e,0xa6,0x0c,0x0a,0xe3,0x8c,0x50,0xc2,0x21,0x1d, 0xe4,0xc1,0x0d,0xec,0xa1,0x1c,0xe4,0x81,0x1e,0xca,0x01,0x1f,0xa6,0x04,0x74,0x00, 0x79,0x18,0x00,0x00,0x7b,0x00,0x00,0x00,0x33,0x08,0x80,0x1c,0xc4,0xe1,0x1c,0x66, 0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80,0x07,0x79,0x78,0x07,0x73, 0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80,0x0e,0x33,0x0c,0x42,0x1e, 0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88,0x43,0x38,0x84,0x83,0x1b, 0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78,0x8c,0x74,0x70,0x07,0x7b, 0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03,0x76,0x78,0x87,0x70,0x20, 0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f,0x6e,0x30,0x0f,0xe3,0xf0, 0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c,0xd8,0x21,0x1d,0xc2,0x61, 0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39,0xb4,0x03,0x3c,0xbc,0x83, 0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x68,0x87, 0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76, 0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87,0x71,0x18,0x87,0x72,0x98, 0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e,0xf5,0xc0,0x0e,0xec,0x30, 0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c,0xe4,0xa1,0x1c,0xdc,0x61, 0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6,0x90,0x43,0x39,0xc8,0x43, 0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94,0x43,0x38,0x88,0x03,0x3b, 0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03,0x3b,0xb0,0xc3,0x0c,0xc7, 0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07,0x78,0x60,0x87,0x74,0x18, 0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f,0xf2,0x50,0x0e,0xe4,0x90, 0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33,0x20,0x28,0x1d,0xdc,0xc1, 0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc,0xe0,0x1c,0xe4,0xe1,0x1d, 0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c,0x83,0x3b,0xcc,0x50,0x24, 0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78,0x07,0x78,0x98,0x51,0x4c, 0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca,0x61,0x1c,0xe8,0x21,0x1d, 0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21,0x1d,0xf0,0x61,0x06,0x54, 0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43,0x39,0xfc,0xc2,0x3c,0xe4, 0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87,0x79,0x98,0x87,0x77,0x18, 0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c,0xe3,0x10,0x0e,0xec,0xc0, 0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e,0xe4,0xe1,0x17,0xd8,0xe1, 0x1d,0xde,0x01,0x1e,0x66,0x50,0x59,0x38,0xa4,0x83,0x3c,0xb8,0x81,0x39,0xd4,0x83, 0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c,0x83,0x3c,0xbc,0x43,0x3d, 0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x50,0x30,0x00, 0xd2,0xd0,0x00,0x00,0x61,0x20,0x00,0x00,0x1e,0x00,0x00,0x00,0x13,0x04,0x41,0x2c, 0x10,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0xf4,0xc6,0x22,0x82,0x20,0x08,0x46,0x00, 0xa8,0x95,0x40,0x19,0xd0,0x98,0x01,0xa0,0x30,0x03,0x00,0x00,0xe3,0x15,0x07,0x33, 0x4d,0x0c,0x05,0x65,0x90,0x81,0x19,0x0e,0x13,0x02,0xf9,0x8c,0x57,0x2c,0xd0,0x75, 0x21,0x14,0x94,0x41,0x06,0xe8,0x60,0x4c,0x08,0xe4,0x63,0x41,0x01,0x9f,0xf1,0x0a, 0xa8,0xe2,0x38,0x86,0x82,0x62,0x43,0x00,0x9f,0xd9,0x06,0xa7,0x02,0x66,0x1b,0x82, 0x2a,0x98,0x6d,0x08,0x06,0x21,0x83,0x80,0x18,0x00,0x00,0x00,0x04,0x00,0x00,0x00, 0x5b,0x86,0x20,0xc8,0x83,0x2d,0x43,0x11,0xe4,0xc1,0x96,0x41,0x09,0xf2,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; static const uint8_t _sspine_fs_bytecode_metal_ios[3257] = { 0x4d,0x54,0x4c,0x42,0x01,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xb9,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x6d,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd1,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xe0,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6d,0x00,0x00,0x00, 0x4e,0x41,0x4d,0x45,0x06,0x00,0x6d,0x61,0x69,0x6e,0x30,0x00,0x54,0x59,0x50,0x45, 0x01,0x00,0x01,0x48,0x41,0x53,0x48,0x20,0x00,0xf9,0x30,0x1b,0xe4,0xb2,0x62,0xda, 0x23,0x88,0x1d,0xd4,0x13,0xb5,0x57,0x53,0xb9,0x44,0x7f,0x7f,0xe5,0xf3,0xc1,0xe4, 0x19,0x37,0x0b,0xd8,0xef,0xc5,0x9b,0xf1,0x0b,0x4f,0x46,0x46,0x54,0x18,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x45,0x52,0x53,0x08,0x00,0x01,0x00,0x08, 0x00,0x01,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44, 0x54,0x04,0x00,0x00,0x00,0x45,0x4e,0x44,0x54,0xde,0xc0,0x17,0x0b,0x00,0x00,0x00, 0x00,0x14,0x00,0x00,0x00,0xc0,0x0b,0x00,0x00,0xff,0xff,0xff,0xff,0x42,0x43,0xc0, 0xde,0x21,0x0c,0x00,0x00,0xed,0x02,0x00,0x00,0x0b,0x82,0x20,0x00,0x02,0x00,0x00, 0x00,0x12,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xc8,0x04,0x49,0x06,0x10,0x32, 0x39,0x92,0x01,0x84,0x0c,0x25,0x05,0x08,0x19,0x1e,0x04,0x8b,0x62,0x80,0x14,0x45, 0x02,0x42,0x92,0x0b,0x42,0xa4,0x10,0x32,0x14,0x38,0x08,0x18,0x49,0x0a,0x32,0x44, 0x24,0x48,0x0a,0x90,0x21,0x23,0xc4,0x52,0x80,0x0c,0x19,0x21,0x72,0x24,0x07,0xc8, 0x48,0x11,0x62,0xa8,0xa0,0xa8,0x40,0xc6,0xf0,0x01,0x00,0x00,0x00,0x51,0x18,0x00, 0x00,0x8e,0x00,0x00,0x00,0x1b,0xcc,0x25,0xf8,0xff,0xff,0xff,0xff,0x01,0x60,0x00, 0x09,0xa8,0x88,0x71,0x78,0x07,0x79,0x90,0x87,0x72,0x18,0x07,0x7a,0x60,0x87,0x7c, 0x68,0x03,0x79,0x78,0x87,0x7a,0x70,0x07,0x72,0x28,0x07,0x72,0x68,0x03,0x72,0x48, 0x07,0x7b,0x48,0x07,0x72,0x28,0x87,0x36,0x98,0x87,0x78,0x90,0x07,0x7a,0x68,0x03, 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xcc,0x21,0x1c,0xd8,0x61, 0x1e,0xca,0x01,0x20,0xc8,0x21,0x1d,0xe6,0x21,0x1c,0xc4,0x81,0x1d,0xca,0xa1,0x0d, 0xe8,0x21,0x1c,0xd2,0x81,0x1d,0xda,0x60,0x1c,0xc2,0x81,0x1d,0xd8,0x61,0x1e,0x00, 0x73,0x08,0x07,0x76,0x98,0x87,0x72,0x00,0x08,0x76,0x28,0x87,0x79,0x98,0x87,0x36, 0x80,0x07,0x79,0x28,0x87,0x71,0x48,0x87,0x79,0x28,0x87,0x36,0x30,0x07,0x78,0x68, 0x87,0x70,0x20,0x07,0xc0,0x1c,0xc2,0x81,0x1d,0xe6,0xa1,0x1c,0x00,0xc2,0x1d,0xde, 0xa1,0x0d,0xcc,0x41,0x1e,0xc2,0xa1,0x1d,0xca,0xa1,0x0d,0xe0,0xe1,0x1d,0xd2,0xc1, 0x1d,0xe8,0xa1,0x1c,0xe4,0xa1,0x0d,0xca,0x81,0x1d,0xd2,0xa1,0x1d,0x00,0x7a,0x90, 0x87,0x7a,0x28,0x07,0x60,0x70,0x87,0x77,0x68,0x03,0x73,0x90,0x87,0x70,0x68,0x87, 0x72,0x68,0x03,0x78,0x78,0x87,0x74,0x70,0x07,0x7a,0x28,0x07,0x79,0x68,0x83,0x72, 0x60,0x87,0x74,0x68,0x87,0x36,0x70,0x87,0x77,0x70,0x87,0x36,0x60,0x87,0x72,0x08, 0x07,0x73,0x00,0x08,0x77,0x78,0x87,0x36,0x48,0x07,0x77,0x30,0x87,0x79,0x68,0x03, 0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e,0xea,0xa1, 0x1c,0x00,0xc2,0x1d,0xde,0xa1,0x0d,0xd4,0xa1,0x1e,0xda,0x01,0x1e,0xda,0x80,0x1e, 0xc2,0x41,0x1c,0xd8,0xa1,0x1c,0xe6,0x01,0x30,0x87,0x70,0x60,0x87,0x79,0x28,0x07, 0x80,0x70,0x87,0x77,0x68,0x03,0x77,0x08,0x07,0x77,0x98,0x87,0x36,0x30,0x07,0x78, 0x68,0x83,0x76,0x08,0x07,0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20, 0xdc,0xe1,0x1d,0xda,0x60,0x1e,0xd2,0xe1,0x1c,0xdc,0xa1,0x1c,0xc8,0xa1,0x0d,0xf4, 0xa1,0x1c,0xe4,0xe1,0x1d,0xe6,0xa1,0x0d,0xcc,0x01,0x1e,0xda,0xa0,0x1d,0xc2,0x81, 0x1e,0xd0,0x01,0xa0,0x07,0x79,0xa8,0x87,0x72,0x00,0x08,0x77,0x78,0x87,0x36,0xa0, 0x07,0x79,0x08,0x07,0x78,0x80,0x87,0x74,0x70,0x87,0x73,0x68,0x83,0x76,0x08,0x07, 0x7a,0x40,0x07,0x80,0x1e,0xe4,0xa1,0x1e,0xca,0x01,0x20,0xe6,0x81,0x1e,0xc2,0x61, 0x1c,0xd6,0xa1,0x0d,0xe0,0x41,0x1e,0xde,0x81,0x1e,0xca,0x61,0x1c,0xe8,0xe1,0x1d, 0xe4,0xa1,0x0d,0xc4,0xa1,0x1e,0xcc,0xc1,0x1c,0xca,0x41,0x1e,0xda,0x60,0x1e,0xd2, 0x41,0x1f,0xca,0x01,0xc0,0x03,0x80,0xa8,0x07,0x77,0x98,0x87,0x70,0x30,0x87,0x72, 0x68,0x03,0x73,0x80,0x87,0x36,0x68,0x87,0x70,0xa0,0x07,0x74,0x00,0xe8,0x41,0x1e, 0xea,0xa1,0x1c,0x00,0xa2,0x1e,0xe6,0xa1,0x1c,0xda,0x60,0x1e,0xde,0xc1,0x1c,0xe8, 0xa1,0x0d,0xcc,0x81,0x1d,0xde,0x21,0x1c,0xe8,0x01,0x30,0x87,0x70,0x60,0x87,0x79, 0x28,0x07,0x60,0x03,0x22,0x0c,0x40,0x02,0x2c,0x40,0xb5,0xc1,0x18,0x08,0x60,0x01, 0xaa,0x0d,0x06,0x51,0x00,0x0b,0x50,0x6d,0x30,0x8a,0xff,0xff,0xff,0xff,0x1f,0x00, 0x09,0xa0,0x36,0x20,0xc6,0xff,0xff,0xff,0xff,0x0f,0x00,0x03,0x48,0x40,0xb5,0xc1, 0x38,0x02,0x60,0x01,0xaa,0x0d,0x06,0x22,0x00,0x0b,0x50,0x01,0x00,0x49,0x18,0x00, 0x00,0x03,0x00,0x00,0x00,0x13,0x88,0x40,0x18,0x88,0x09,0x41,0x31,0x61,0x30,0x0e, 0x04,0x89,0x20,0x00,0x00,0x27,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85, 0x04,0x93,0x22,0xa4,0x84,0x04,0x93,0x22,0xe3,0x84,0xa1,0x90,0x14,0x12,0x4c,0x8a, 0x8c,0x0b,0x84,0xa4,0x4c,0x10,0x6c,0x33,0x00,0xc3,0x08,0x04,0x60,0x83,0x30,0x8c, 0x20,0x00,0x07,0x49,0x53,0x44,0x09,0x93,0x5f,0x48,0xff,0x03,0x44,0x00,0x23,0xa1, 0x00,0x0c,0x22,0x10,0xc2,0x51,0xd2,0x14,0x51,0xc2,0xe4,0xff,0x13,0x71,0x4d,0x54, 0x44,0xfc,0xf6,0xf0,0x4f,0x63,0x04,0xc0,0x20,0x82,0x11,0x5c,0x24,0x4d,0x11,0x25, 0x4c,0xfe,0x2f,0x01,0xcc,0xb3,0x10,0xd1,0x3f,0x8d,0x11,0x00,0x83,0x08,0x88,0x50, 0x0c,0x31,0x42,0x39,0x89,0x54,0x21,0x42,0x08,0x81,0xd8,0x1c,0x41,0x30,0x47,0x00, 0x06,0xc3,0x08,0xc2,0x53,0x90,0x70,0xd2,0x70,0xd0,0x01,0x8a,0x03,0x01,0x29,0xf0, 0x86,0x11,0x86,0x67,0x18,0x61,0x00,0x86,0x11,0x88,0x67,0x8e,0x00,0x14,0x06,0x11, 0x00,0x61,0x04,0x00,0x00,0x13,0xa8,0x70,0x48,0x07,0x79,0xb0,0x03,0x3a,0x68,0x83, 0x70,0x80,0x07,0x78,0x60,0x87,0x72,0x68,0x83,0x74,0x78,0x87,0x79,0xc8,0x03,0x37, 0x80,0x03,0x37,0x80,0x83,0x0d,0xb7,0x51,0x0e,0x6d,0x00,0x0f,0x7a,0x60,0x07,0x74, 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe9,0x10,0x07,0x7a,0x80, 0x07,0x7a,0x80,0x07,0x6d,0x90,0x0e,0x78,0xa0,0x07,0x78,0xa0,0x07,0x78,0xd0,0x06, 0xe9,0x10,0x07,0x76,0xa0,0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xe9, 0x30,0x07,0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe9,0x60, 0x07,0x74,0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xe6,0x30,0x07, 0x72,0xa0,0x07,0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xe6,0x60,0x07,0x74, 0xa0,0x07,0x76,0x40,0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x10,0x07,0x76,0xa0, 0x07,0x71,0x60,0x07,0x7a,0x10,0x07,0x76,0xd0,0x06,0xf6,0x20,0x07,0x74,0xa0,0x07, 0x73,0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x30,0x07,0x72,0xa0,0x07,0x73, 0x20,0x07,0x7a,0x30,0x07,0x72,0xd0,0x06,0xf6,0x40,0x07,0x78,0xa0,0x07,0x76,0x40, 0x07,0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x60,0x07,0x74,0xa0,0x07,0x76,0x40,0x07, 0x7a,0x60,0x07,0x74,0xd0,0x06,0xf6,0x90,0x07,0x76,0xa0,0x07,0x71,0x20,0x07,0x78, 0xa0,0x07,0x71,0x20,0x07,0x78,0xd0,0x06,0xf6,0x10,0x07,0x72,0x80,0x07,0x7a,0x10, 0x07,0x72,0x80,0x07,0x7a,0x10,0x07,0x72,0x80,0x07,0x6d,0x60,0x0f,0x71,0x90,0x07, 0x72,0xa0,0x07,0x72,0x50,0x07,0x76,0xa0,0x07,0x72,0x50,0x07,0x76,0xd0,0x06,0xf6, 0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60,0x07,0x7a,0x20,0x07,0x75,0x60, 0x07,0x6d,0x60,0x0f,0x75,0x10,0x07,0x72,0xa0,0x07,0x75,0x10,0x07,0x72,0xa0,0x07, 0x75,0x10,0x07,0x72,0xd0,0x06,0xf6,0x10,0x07,0x70,0x20,0x07,0x74,0xa0,0x07,0x71, 0x00,0x07,0x72,0x40,0x07,0x7a,0x10,0x07,0x70,0x20,0x07,0x74,0xd0,0x06,0xee,0x80, 0x07,0x7a,0x10,0x07,0x76,0xa0,0x07,0x73,0x20,0x07,0x43,0x98,0x05,0x00,0x80,0x00, 0x00,0x00,0x00,0x00,0x80,0x21,0x4c,0x03,0x04,0x80,0x00,0x00,0x00,0x00,0x00,0xc0, 0x10,0x46,0x02,0x02,0x60,0x00,0x00,0x00,0x00,0x00,0x20,0x0b,0x04,0x09,0x00,0x00, 0x00,0x32,0x1e,0x98,0x10,0x19,0x11,0x4c,0x90,0x8c,0x09,0x26,0x47,0xc6,0x04,0x43, 0x7a,0x23,0x00,0x25,0x50,0x08,0x45,0x50,0x10,0x65,0x40,0x78,0x04,0x80,0xe8,0x58, 0x02,0x04,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xfa,0x00,0x00,0x00,0x1a,0x03,0x4c, 0x10,0x97,0x29,0xa2,0x25,0x10,0xab,0x32,0xb9,0xb9,0xb4,0x37,0xb7,0x21,0xc6,0x63, 0x4c,0x00,0xa5,0x50,0xb9,0x1b,0x43,0x0b,0x93,0xfb,0x9a,0x4b,0xd3,0x2b,0x1b,0x62, 0x3c,0xc4,0x24,0x3c,0x05,0xe7,0x20,0x08,0x0e,0x8e,0xad,0x0c,0xa4,0xad,0x8c,0x2e, 0x8c,0x0d,0xc4,0xae,0x4c,0x6e,0x2e,0xed,0xcd,0x0d,0x64,0x26,0x06,0x06,0x26,0xc6, 0xc5,0x86,0x66,0x06,0x04,0xa5,0xad,0x8c,0x2e,0x8c,0xcd,0xac,0xac,0x65,0x26,0x06, 0x06,0x26,0xc6,0xc5,0x86,0x66,0xc6,0x85,0x26,0x65,0x88,0x30,0x11,0x43,0x8c,0x87, 0x78,0x8e,0x67,0x60,0xd1,0x54,0x46,0x17,0xc6,0x36,0x04,0x99,0x8e,0x87,0x78,0x86, 0x67,0xe0,0x16,0x96,0x26,0xe7,0x32,0xf6,0xd6,0x06,0x97,0xc6,0x56,0xe6,0x42,0x56, 0xe6,0xf6,0x26,0xd7,0x36,0xf7,0x45,0x96,0x36,0x17,0x26,0xc6,0x56,0x36,0x44,0x98, 0x12,0x72,0x61,0x69,0x72,0x2e,0x63,0x6f,0x6d,0x70,0x69,0x6c,0x65,0x2e,0x66,0x61, 0x73,0x74,0x5f,0x6d,0x61,0x74,0x68,0x5f,0x65,0x6e,0x61,0x62,0x6c,0x65,0x43,0x84, 0x69,0x21,0x19,0x84,0xa5,0xc9,0xb9,0x8c,0xbd,0xb5,0xc1,0xa5,0xb1,0x95,0xb9,0x98, 0xc9,0x85,0xb5,0x95,0x89,0xd5,0x99,0x99,0x95,0xc9,0x7d,0x99,0x95,0xd1,0x8d,0xa1, 0x7d,0x95,0xb9,0x85,0x89,0xb1,0x95,0x0d,0x11,0xa6,0x86,0x51,0x58,0x9a,0x9c,0x8b, 0x5c,0x99,0x1b,0x59,0x99,0xdc,0x17,0x5d,0x98,0xdc,0x59,0x19,0x1d,0xa3,0xb0,0x34, 0x39,0x97,0x30,0xb9,0xb3,0x2f,0xba,0x3c,0xb8,0xb2,0x2f,0xb7,0xb0,0xb6,0x32,0x1a, 0x66,0x6c,0x6f,0x61,0x74,0x34,0x64,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xdc, 0xc2,0xda,0xca,0xa8,0x98,0xc9,0x85,0x9d,0x7d,0x8d,0xbd,0xb1,0xbd,0xc9,0x0d,0x61, 0xa6,0xe7,0x19,0x26,0x68,0x8a,0x26,0x69,0x9a,0x86,0x08,0x13,0x45,0x29,0x2c,0x4d, 0xce,0xc5,0x4c,0x2e,0xec,0xac,0xad,0xcc,0x8d,0xee,0x2b,0xcd,0x0d,0xae,0x8e,0x8e, 0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x1b,0x4c,0x0a,0x95,0xb0,0x34,0x39, 0x97,0xb1,0x32,0x37,0xba,0x32,0x39,0x3e,0x61,0x69,0x72,0x2e,0x70,0x65,0x72,0x73, 0x70,0x65,0x63,0x74,0x69,0x76,0x65,0x34,0xcc,0xd8,0xde,0xc2,0xe8,0x64,0x28,0xd4, 0xd9,0x0d,0x91,0x9e,0x61,0xb2,0xa6,0x6b,0xc2,0xa6,0x6c,0x82,0x26,0x6d,0x92,0xa6, 0x8d,0x4b,0xdd,0x5c,0x99,0x1c,0x0a,0xdb,0xdb,0x98,0x5b,0x4c,0x0a,0x8b,0xb1,0x37, 0xb6,0x37,0xb9,0x21,0xd2,0x43,0x4c,0xd6,0xd4,0x4d,0xd8,0x94,0x4d,0xd0,0x14,0x4d, 0xd2,0xe4,0x51,0x09,0x4b,0x93,0x73,0x11,0xab,0x33,0x33,0x2b,0x93,0xe3,0x13,0x96, 0x26,0xe7,0x22,0x56,0x67,0x66,0x56,0x26,0xf7,0x35,0x97,0xa6,0x57,0x46,0x29,0x2c, 0x4d,0xce,0x85,0xed,0x6d,0x2c,0x8c,0x2e,0xed,0xcd,0xed,0x2b,0xcd,0x8d,0xac,0x0c, 0x8f,0x48,0x58,0x9a,0x9c,0x8b,0x5c,0x59,0x18,0x19,0xa9,0xb0,0x34,0x39,0x97,0x39, 0x3a,0xb9,0xba,0x31,0xba,0x2f,0xba,0x3c,0xb8,0xb2,0xaf,0x34,0x37,0xb3,0x37,0x16, 0x66,0x6c,0x6f,0x61,0x74,0x1c,0xe0,0xda,0xc2,0x86,0x28,0xcf,0xf0,0x14,0xcf,0x30, 0x95,0xc1,0x64,0x06,0x8c,0xc2,0xd2,0xe4,0x5c,0xc2,0xe4,0xce,0xbe,0xe8,0xf2,0xe0, 0xca,0xbe,0xe6,0xd2,0xf4,0xca,0x78,0x85,0xa5,0xc9,0xb9,0x84,0xc9,0x9d,0x7d,0xd1, 0xe5,0xc1,0x95,0x7d,0x85,0xb1,0xa5,0x9d,0xb9,0x7d,0xcd,0xa5,0xe9,0x95,0x31,0x31, 0x9b,0xfb,0x82,0x0b,0x93,0x0b,0x6b,0x9b,0xe3,0xf0,0x55,0x03,0x33,0x84,0x0c,0x1e, 0x63,0x02,0x83,0x29,0x0c,0x9e,0x62,0x12,0x83,0x67,0x78,0x88,0x69,0x0c,0x26,0x32, 0x98,0xce,0x60,0x42,0x83,0xa7,0x98,0xd2,0xe0,0x29,0x26,0x68,0x52,0x83,0x49,0x9a, 0xd6,0x80,0x4b,0x58,0x9a,0x9c,0x0b,0x5d,0x19,0x1e,0x5d,0x9d,0x5c,0x19,0x95,0xb0, 0x34,0x39,0x97,0xb9,0xb0,0x36,0x38,0xb6,0x32,0x62,0x74,0x65,0x78,0x74,0x75,0x72, 0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c, 0x65,0x3e,0x1c,0xe8,0xca,0xf0,0x86,0x50,0x0f,0x32,0xb5,0xc1,0x24,0x06,0xcf,0xf0, 0x10,0x93,0x1b,0x4c,0xd0,0xf4,0x06,0x93,0x34,0xc1,0x01,0x97,0xb0,0x34,0x39,0x97, 0xb9,0xb0,0x36,0x38,0xb6,0x32,0x39,0x1e,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x44, 0xe8,0xca,0xf0,0xa6,0xda,0xe0,0xd8,0xe4,0x86,0x48,0x4f,0x31,0xc9,0xc1,0x24,0x06, 0xcf,0xf0,0x10,0x13,0x34,0xcd,0xc1,0x24,0x4d,0x74,0x30,0x44,0x99,0xb8,0xe9,0x9b, 0xd8,0x60,0x8a,0x83,0xa9,0x0e,0x86,0x18,0x0b,0x30,0x55,0x93,0x1d,0xd0,0xf9,0xd2, 0xa2,0x9a,0xca,0x31,0x9b,0xfb,0x82,0x0b,0x93,0x0b,0x6b,0x9b,0xe3,0xf3,0xd6,0xe6, 0x96,0x06,0xf7,0x46,0x57,0xe6,0x46,0x07,0x32,0x86,0x16,0x26,0xc7,0x67,0x2a,0xad, 0x0d,0x8e,0xad,0x0c,0x64,0x68,0x65,0x05,0x84,0x4a,0x28,0x28,0x68,0x88,0x30,0xe9, 0xc1,0x10,0x63,0xca,0x83,0x69,0x0f,0xb0,0x64,0x88,0x31,0x95,0xc1,0xc4,0x07,0x58, 0x32,0xc4,0x98,0xf0,0x60,0xea,0x03,0x2c,0x19,0x62,0x4c,0x7e,0x30,0xf5,0x01,0x96, 0x8c,0x88,0xd8,0x81,0x1d,0xec,0xa1,0x1d,0xdc,0xa0,0x1d,0xde,0x81,0x1c,0xea,0x81, 0x1d,0xca,0xc1,0x0d,0xcc,0x81,0x1d,0xc2,0xe1,0x1c,0xe6,0x61,0x8a,0x10,0x0c,0x23, 0x14,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x48,0x07,0x72,0x28,0x07,0x77,0xa0,0x87, 0x29,0x41,0x31,0x62,0x09,0x87,0x74,0x90,0x07,0x37,0xb0,0x87,0x72,0x90,0x87,0x79, 0x48,0x87,0x77,0x70,0x87,0x29,0x81,0x31,0x82,0x0a,0x87,0x74,0x90,0x07,0x37,0x60, 0x87,0x70,0x70,0x87,0x73,0xa8,0x87,0x70,0x38,0x87,0x72,0xf8,0x05,0x7b,0x28,0x07, 0x79,0x98,0x87,0x74,0x78,0x07,0x77,0x98,0x12,0x20,0x23,0xa6,0x70,0x48,0x07,0x79, 0x70,0x83,0x71,0x78,0x87,0x76,0x80,0x87,0x74,0x60,0x87,0x72,0xf8,0x85,0x77,0x80, 0x07,0x7a,0x48,0x87,0x77,0x70,0x87,0x79,0x98,0x32,0x28,0x8c,0x33,0x82,0x09,0x87, 0x74,0x90,0x07,0x37,0x30,0x07,0x79,0x08,0x87,0x73,0x68,0x87,0x72,0x70,0x07,0x7a, 0x98,0x12,0xdc,0x01,0x00,0x79,0x18,0x00,0x00,0x7b,0x00,0x00,0x00,0x33,0x08,0x80, 0x1c,0xc4,0xe1,0x1c,0x66,0x14,0x01,0x3d,0x88,0x43,0x38,0x84,0xc3,0x8c,0x42,0x80, 0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0c,0xe6,0x00,0x0f,0xed,0x10,0x0e,0xf4,0x80, 0x0e,0x33,0x0c,0x42,0x1e,0xc2,0xc1,0x1d,0xce,0xa1,0x1c,0x66,0x30,0x05,0x3d,0x88, 0x43,0x38,0x84,0x83,0x1b,0xcc,0x03,0x3d,0xc8,0x43,0x3d,0x8c,0x03,0x3d,0xcc,0x78, 0x8c,0x74,0x70,0x07,0x7b,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7a,0x70,0x03, 0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xcc,0x11,0x0e,0xec,0x90,0x0e,0xe1,0x30,0x0f, 0x6e,0x30,0x0f,0xe3,0xf0,0x0e,0xf0,0x50,0x0e,0x33,0x10,0xc4,0x1d,0xde,0x21,0x1c, 0xd8,0x21,0x1d,0xc2,0x61,0x1e,0x66,0x30,0x89,0x3b,0xbc,0x83,0x3b,0xd0,0x43,0x39, 0xb4,0x03,0x3c,0xbc,0x83,0x3c,0x84,0x03,0x3b,0xcc,0xf0,0x14,0x76,0x60,0x07,0x7b, 0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60, 0x07,0x76,0x28,0x07,0x76,0xf8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5f,0x08,0x87, 0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2c,0xee,0xf0,0x0e,0xee,0xe0,0x0e, 0xf5,0xc0,0x0e,0xec,0x30,0x03,0x62,0xc8,0xa1,0x1c,0xe4,0xa1,0x1c,0xcc,0xa1,0x1c, 0xe4,0xa1,0x1c,0xdc,0x61,0x1c,0xca,0x21,0x1c,0xc4,0x81,0x1d,0xca,0x61,0x06,0xd6, 0x90,0x43,0x39,0xc8,0x43,0x39,0x98,0x43,0x39,0xc8,0x43,0x39,0xb8,0xc3,0x38,0x94, 0x43,0x38,0x88,0x03,0x3b,0x94,0xc3,0x2f,0xbc,0x83,0x3c,0xfc,0x82,0x3b,0xd4,0x03, 0x3b,0xb0,0xc3,0x0c,0xc7,0x69,0x87,0x70,0x58,0x87,0x72,0x70,0x83,0x74,0x68,0x07, 0x78,0x60,0x87,0x74,0x18,0x87,0x74,0xa0,0x87,0x19,0xce,0x53,0x0f,0xee,0x00,0x0f, 0xf2,0x50,0x0e,0xe4,0x90,0x0e,0xe3,0x40,0x0f,0xe1,0x20,0x0e,0xec,0x50,0x0e,0x33, 0x20,0x28,0x1d,0xdc,0xc1,0x1e,0xc2,0x41,0x1e,0xd2,0x21,0x1c,0xdc,0x81,0x1e,0xdc, 0xe0,0x1c,0xe4,0xe1,0x1d,0xea,0x01,0x1e,0x66,0x18,0x51,0x38,0xb0,0x43,0x3a,0x9c, 0x83,0x3b,0xcc,0x50,0x24,0x76,0x60,0x07,0x7b,0x68,0x07,0x37,0x60,0x87,0x77,0x78, 0x07,0x78,0x98,0x51,0x4c,0xf4,0x90,0x0f,0xf0,0x50,0x0e,0x33,0x1e,0x6a,0x1e,0xca, 0x61,0x1c,0xe8,0x21,0x1d,0xde,0xc1,0x1d,0x7e,0x01,0x1e,0xe4,0xa1,0x1c,0xcc,0x21, 0x1d,0xf0,0x61,0x06,0x54,0x85,0x83,0x38,0xcc,0xc3,0x3b,0xb0,0x43,0x3d,0xd0,0x43, 0x39,0xfc,0xc2,0x3c,0xe4,0x43,0x3b,0x88,0xc3,0x3b,0xb0,0xc3,0x8c,0xc5,0x0a,0x87, 0x79,0x98,0x87,0x77,0x18,0x87,0x74,0x08,0x07,0x7a,0x28,0x07,0x72,0x98,0x81,0x5c, 0xe3,0x10,0x0e,0xec,0xc0,0x0e,0xe5,0x50,0x0e,0xf3,0x30,0x23,0xc1,0xd2,0x41,0x1e, 0xe4,0xe1,0x17,0xd8,0xe1,0x1d,0xde,0x01,0x1e,0x66,0x50,0x59,0x38,0xa4,0x83,0x3c, 0xb8,0x81,0x39,0xd4,0x83,0x3b,0x8c,0x03,0x3d,0xa4,0xc3,0x3b,0xb8,0xc3,0x2f,0x9c, 0x83,0x3c,0xbc,0x43,0x3d,0xc0,0xc3,0x3c,0x00,0x71,0x20,0x00,0x00,0x0b,0x00,0x00, 0x00,0x26,0xb0,0x01,0x48,0xe4,0x4b,0x00,0xf3,0x2c,0xc4,0x3f,0x11,0xd7,0x44,0x45, 0xc4,0x6f,0x0f,0x7e,0x85,0x17,0xb7,0x6d,0x00,0x05,0x03,0x20,0x0d,0x6d,0x01,0x0d, 0x80,0x44,0x3e,0x83,0x5c,0x7e,0x85,0x17,0xb7,0x0d,0x00,0x00,0x00,0x61,0x20,0x00, 0x00,0x25,0x00,0x00,0x00,0x13,0x04,0x41,0x2c,0x10,0x00,0x00,0x00,0x0c,0x00,0x00, 0x00,0x74,0x47,0x00,0xc6,0x22,0x80,0x40,0x38,0xe6,0x20,0x06,0xc2,0xa8,0xc8,0xd5, 0xc0,0x08,0x00,0xbd,0x19,0x00,0x82,0x23,0x00,0x54,0xc7,0x1a,0x80,0x40,0x18,0x6b, 0x18,0x86,0x81,0xec,0x0c,0x00,0x89,0x19,0x00,0x0a,0x33,0x00,0x04,0x46,0x00,0x00, 0x00,0x23,0x06,0xca,0x10,0x6c,0x8f,0x23,0x29,0x47,0x12,0x58,0x20,0xc9,0x67,0x90, 0x21,0x20,0x90,0x41,0x06,0xa1,0x40,0x4c,0x08,0xe4,0x33,0xc8,0x10,0x24,0xd0,0x20, 0x43,0x50,0x48,0x16,0x60,0xf2,0x19,0x6f,0xc0,0x38,0x31,0xa0,0x60,0xcc,0x31,0x30, 0x01,0x19,0x0c,0x32,0x04,0x0d,0x36,0x62,0x60,0x08,0x01,0x1a,0x2c,0x45,0x30,0xdb, 0x00,0x05,0x40,0x06,0x01,0x31,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x5b,0x86,0x24, 0xf8,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; static const char _sspine_vs_source_metal_sim[716] = { 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x76, 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, 0x6c,0x6f,0x61,0x74,0x34,0x78,0x34,0x20,0x6d,0x76,0x70,0x3b,0x0a,0x7d,0x3b,0x0a, 0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75, 0x74,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x75, 0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d, 0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f, 0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28,0x6c,0x6f,0x63,0x6e,0x31, 0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20, 0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x70,0x6f, 0x73,0x69,0x74,0x69,0x6f,0x6e,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74, 0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a, 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32,0x20,0x70,0x6f,0x73,0x69,0x74, 0x69,0x6f,0x6e,0x20,0x5b,0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28, 0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x32, 0x20,0x74,0x65,0x78,0x63,0x6f,0x6f,0x72,0x64,0x30,0x20,0x5b,0x5b,0x61,0x74,0x74, 0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20, 0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x30,0x20,0x5b, 0x5b,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x32,0x29,0x5d,0x5d,0x3b, 0x0a,0x7d,0x3b,0x0a,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x35,0x20,0x22,0x73, 0x73,0x70,0x69,0x6e,0x65,0x2e,0x67,0x6c,0x73,0x6c,0x22,0x0a,0x76,0x65,0x72,0x74, 0x65,0x78,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69, 0x6e,0x30,0x28,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b, 0x5b,0x73,0x74,0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e, 0x73,0x74,0x61,0x6e,0x74,0x20,0x76,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x26, 0x20,0x5f,0x32,0x31,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29, 0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f, 0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b,0x0a,0x23,0x6c, 0x69,0x6e,0x65,0x20,0x31,0x35,0x20,0x22,0x73,0x73,0x70,0x69,0x6e,0x65,0x2e,0x67, 0x6c,0x73,0x6c,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x67,0x6c,0x5f, 0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x5f,0x32,0x31,0x2e,0x6d, 0x76,0x70,0x20,0x2a,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x69,0x6e,0x2e,0x70, 0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e, 0x30,0x29,0x3b,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x36,0x20,0x22,0x73,0x73, 0x70,0x69,0x6e,0x65,0x2e,0x67,0x6c,0x73,0x6c,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f, 0x75,0x74,0x2e,0x75,0x76,0x20,0x3d,0x20,0x69,0x6e,0x2e,0x74,0x65,0x78,0x63,0x6f, 0x6f,0x72,0x64,0x30,0x3b,0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x37,0x20,0x22, 0x73,0x73,0x70,0x69,0x6e,0x65,0x2e,0x67,0x6c,0x73,0x6c,0x22,0x0a,0x20,0x20,0x20, 0x20,0x6f,0x75,0x74,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x69,0x6e,0x2e, 0x63,0x6f,0x6c,0x6f,0x72,0x30,0x3b,0x0a,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75, 0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a,0x00, }; static const char _sspine_fs_source_metal_sim[721] = { 0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x6d,0x65,0x74,0x61,0x6c,0x5f, 0x73,0x74,0x64,0x6c,0x69,0x62,0x3e,0x0a,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65, 0x20,0x3c,0x73,0x69,0x6d,0x64,0x2f,0x73,0x69,0x6d,0x64,0x2e,0x68,0x3e,0x0a,0x0a, 0x75,0x73,0x69,0x6e,0x67,0x20,0x6e,0x61,0x6d,0x65,0x73,0x70,0x61,0x63,0x65,0x20, 0x6d,0x65,0x74,0x61,0x6c,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x66, 0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, 0x6c,0x6f,0x61,0x74,0x20,0x70,0x6d,0x61,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74, 0x72,0x75,0x63,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x0a,0x7b, 0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x20,0x66,0x72,0x61,0x67, 0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x63,0x6f,0x6c,0x6f,0x72,0x28,0x30, 0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a,0x73,0x74,0x72,0x75,0x63,0x74,0x20, 0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x66, 0x6c,0x6f,0x61,0x74,0x32,0x20,0x75,0x76,0x20,0x5b,0x5b,0x75,0x73,0x65,0x72,0x28, 0x6c,0x6f,0x63,0x6e,0x30,0x29,0x5d,0x5d,0x3b,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c, 0x6f,0x61,0x74,0x34,0x20,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x5b,0x5b,0x75,0x73,0x65, 0x72,0x28,0x6c,0x6f,0x63,0x6e,0x31,0x29,0x5d,0x5d,0x3b,0x0a,0x7d,0x3b,0x0a,0x0a, 0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x34,0x20,0x22,0x73,0x73,0x70,0x69,0x6e,0x65, 0x2e,0x67,0x6c,0x73,0x6c,0x22,0x0a,0x66,0x72,0x61,0x67,0x6d,0x65,0x6e,0x74,0x20, 0x6d,0x61,0x69,0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6d,0x61,0x69,0x6e,0x30,0x28, 0x6d,0x61,0x69,0x6e,0x30,0x5f,0x69,0x6e,0x20,0x69,0x6e,0x20,0x5b,0x5b,0x73,0x74, 0x61,0x67,0x65,0x5f,0x69,0x6e,0x5d,0x5d,0x2c,0x20,0x63,0x6f,0x6e,0x73,0x74,0x61, 0x6e,0x74,0x20,0x66,0x73,0x5f,0x70,0x61,0x72,0x61,0x6d,0x73,0x26,0x20,0x5f,0x35, 0x30,0x20,0x5b,0x5b,0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5d,0x5d,0x2c, 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3c,0x66,0x6c,0x6f,0x61,0x74, 0x3e,0x20,0x74,0x65,0x78,0x20,0x5b,0x5b,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28, 0x30,0x29,0x5d,0x5d,0x2c,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x20,0x74,0x65, 0x78,0x53,0x6d,0x70,0x6c,0x72,0x20,0x5b,0x5b,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72, 0x28,0x30,0x29,0x5d,0x5d,0x29,0x0a,0x7b,0x0a,0x20,0x20,0x20,0x20,0x6d,0x61,0x69, 0x6e,0x30,0x5f,0x6f,0x75,0x74,0x20,0x6f,0x75,0x74,0x20,0x3d,0x20,0x7b,0x7d,0x3b, 0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x34,0x20,0x22,0x73,0x73,0x70,0x69,0x6e, 0x65,0x2e,0x67,0x6c,0x73,0x6c,0x22,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, 0x74,0x34,0x20,0x5f,0x32,0x35,0x20,0x3d,0x20,0x74,0x65,0x78,0x2e,0x73,0x61,0x6d, 0x70,0x6c,0x65,0x28,0x74,0x65,0x78,0x53,0x6d,0x70,0x6c,0x72,0x2c,0x20,0x69,0x6e, 0x2e,0x75,0x76,0x29,0x20,0x2a,0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x3b, 0x0a,0x23,0x6c,0x69,0x6e,0x65,0x20,0x31,0x35,0x20,0x22,0x73,0x73,0x70,0x69,0x6e, 0x65,0x2e,0x67,0x6c,0x73,0x6c,0x22,0x0a,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, 0x74,0x20,0x5f,0x33,0x34,0x20,0x3d,0x20,0x5f,0x32,0x35,0x2e,0x77,0x3b,0x0a,0x23, 0x6c,0x69,0x6e,0x65,0x20,0x31,0x36,0x20,0x22,0x73,0x73,0x70,0x69,0x6e,0x65,0x2e, 0x67,0x6c,0x73,0x6c,0x22,0x0a,0x20,0x20,0x20,0x20,0x6f,0x75,0x74,0x2e,0x66,0x72, 0x61,0x67,0x5f,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x5f, 0x32,0x35,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74,0x34,0x28,0x5f,0x32,0x35,0x2e,0x78, 0x79,0x7a,0x20,0x2a,0x20,0x5f,0x33,0x34,0x2c,0x20,0x5f,0x33,0x34,0x29,0x20,0x2a, 0x20,0x69,0x6e,0x2e,0x63,0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x66,0x6c,0x6f,0x61,0x74, 0x34,0x28,0x5f,0x35,0x30,0x2e,0x70,0x6d,0x61,0x29,0x29,0x3b,0x0a,0x20,0x20,0x20, 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6f,0x75,0x74,0x3b,0x0a,0x7d,0x0a,0x0a, 0x00, }; #elif defined(SOKOL_WGPU) #error "FIXME: wgpu shaders" #elif defined(SOKOL_DUMMY_BACKEND) static const char* _sspine_vs_source_dummy = ""; static const char* _sspine_fs_source_dummy = ""; #else #error "Please define one of SOKOL_GLCORE33, SOKOL_GLES3, SOKOL_D3D11, SOKOL_METAL, SOKOL_WGPU or SOKOL_DUMMY_BACKEND!" #endif // ███████ ████████ ██████ ██ ██ ██████ ████████ ███████ // ██ ██ ██ ██ ██ ██ ██ ██ ██ // ███████ ██ ██████ ██ ██ ██ ██ ███████ // ██ ██ ██ ██ ██ ██ ██ ██ ██ // ███████ ██ ██ ██ ██████ ██████ ██ ███████ // // >>structs #define _sspine_def(val, def) (((val) == 0) ? (def) : (val)) #define _SSPINE_INIT_COOKIE (0xABBAABBA) #define _SSPINE_INVALID_SLOT_INDEX (0) #define _SSPINE_DEFAULT_CONTEXT_POOL_SIZE (4) #define _SSPINE_DEFAULT_ATLAS_POOL_SIZE (64) #define _SSPINE_DEFAULT_SKELETON_POOL_SIZE (64) #define _SSPINE_DEFAULT_SKINSET_POOL_SIZE (64) #define _SSPINE_DEFAULT_INSTANCE_POOL_SIZE (1024) #define _SSPINE_DEFAULT_MAX_VERTICES (1<<16) #define _SSPINE_DEFAULT_MAX_COMMANDS (1<<14) #define _SSPINE_MAX_TRIGGERED_EVENTS (16) #define _SSPINE_SLOT_SHIFT (16) #define _SSPINE_MAX_POOL_SIZE (1<<_SSPINE_SLOT_SHIFT) #define _SSPINE_SLOT_MASK (_SSPINE_MAX_POOL_SIZE-1) typedef struct { float mvp[16]; } _sspine_vsparams_t; typedef struct { float pma; uint8_t _pad[12]; } _sspine_fsparams_t; typedef struct { uint32_t id; sspine_resource_state state; } _sspine_slot_t; typedef struct { int size; int queue_top; uint32_t* gen_ctrs; int* free_queue; } _sspine_pool_t; typedef struct { _sspine_slot_t slot; sspine_atlas_overrides overrides; spAtlas* sp_atlas; int num_pages; } _sspine_atlas_t; typedef struct { _sspine_pool_t pool; _sspine_atlas_t* items; } _sspine_atlas_pool_t; typedef struct { uint32_t id; _sspine_atlas_t* ptr; } _sspine_atlas_ref_t; typedef struct { _sspine_slot_t slot; _sspine_atlas_ref_t atlas; spSkeletonData* sp_skel_data; spAnimationStateData* sp_anim_data; struct { int cap; sspine_vec2* ptr; } tform_buf; } _sspine_skeleton_t; typedef struct { _sspine_pool_t pool; _sspine_skeleton_t* items; } _sspine_skeleton_pool_t; typedef struct { uint32_t id; _sspine_skeleton_t* ptr; } _sspine_skeleton_ref_t; typedef struct { _sspine_slot_t slot; _sspine_skeleton_ref_t skel; spSkin* sp_skin; } _sspine_skinset_t; typedef struct { _sspine_pool_t pool; _sspine_skinset_t* items; } _sspine_skinset_pool_t; typedef struct { uint32_t id; _sspine_skinset_t* ptr; } _sspine_skinset_ref_t; typedef struct { _sspine_slot_t slot; _sspine_atlas_ref_t atlas; _sspine_skeleton_ref_t skel; _sspine_skinset_ref_t skinset; spSkeleton* sp_skel; spAnimationState* sp_anim_state; spSkeletonClipping* sp_clip; int cur_triggered_event_index; sspine_triggered_event_info triggered_events[_SSPINE_MAX_TRIGGERED_EVENTS]; } _sspine_instance_t; typedef struct { _sspine_pool_t pool; _sspine_instance_t* items; } _sspine_instance_pool_t; typedef struct { sspine_vec2 pos; sspine_vec2 uv; uint32_t color; } _sspine_vertex_t; typedef struct { _sspine_vertex_t* ptr; int index; } _sspine_alloc_vertices_result_t; typedef struct { uint32_t* ptr; int index; } _sspine_alloc_indices_result_t; typedef struct { int layer; sg_pipeline pip; sg_image img; float pma; // pma = 0.0: use texture color as is, pma = 1.0: multiply texture rgb by texture alpha in fragment shader int base_element; int num_elements; } _sspine_command_t; typedef struct { _sspine_slot_t slot; float transform[16]; struct { int cap; int next; uint32_t rewind_frame_id; _sspine_vertex_t* ptr; } vertices; struct { int cap; int next; uint32_t rewind_frame_id; uint32_t* ptr; } indices; struct { int cap; int next; uint32_t rewind_frame_id; _sspine_command_t* ptr; } commands; uint32_t update_frame_id; sg_buffer vbuf; sg_buffer ibuf; struct { sg_pipeline normal_additive; sg_pipeline multiply; } pip; sg_bindings bind; } _sspine_context_t; typedef struct { _sspine_pool_t pool; _sspine_context_t* items; } _sspine_context_pool_t; typedef struct { uint32_t init_cookie; uint32_t frame_id; sspine_desc desc; sspine_context def_ctx_id; sspine_context cur_ctx_id; _sspine_context_t* cur_ctx; // may be 0! sg_shader shd; _sspine_context_pool_t context_pool; _sspine_atlas_pool_t atlas_pool; _sspine_skeleton_pool_t skeleton_pool; _sspine_skinset_pool_t skinset_pool; _sspine_instance_pool_t instance_pool; } _sspine_t; static _sspine_t _sspine; // dummy spine-c platform implementation functions #if defined(__cplusplus) extern "C" { #endif void _spAtlasPage_createTexture(spAtlasPage* self, const char* path) { // nothing to do here (void)self; (void)path; } void _spAtlasPage_disposeTexture(spAtlasPage* self) { if (self->rendererObject != 0) { const sg_image img = { (uint32_t)(uintptr_t)self->rendererObject }; sg_destroy_image(img); } } char* _spUtil_readFile(const char* path, int* length) { (void)path; *length = 0; return 0; } #if defined(__cplusplus) } // extern "C" #endif // ██ ██████ ██████ ██████ ██ ███ ██ ██████ // ██ ██ ██ ██ ██ ██ ████ ██ ██ // ██ ██ ██ ██ ███ ██ ███ ██ ██ ██ ██ ██ ███ // ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ // ███████ ██████ ██████ ██████ ██ ██ ████ ██████ // // >>logging #if defined(SOKOL_DEBUG) #define _SSPINE_LOGITEM_XMACRO(item,msg) #item ": " msg, static const char* _sspine_log_messages[] = { _SSPINE_LOG_ITEMS }; #undef _SSPINE_LOGITEM_XMACRO #endif // SOKOL_DEBUG #define _SSPINE_PANIC(code) _sspine_log(SSPINE_LOGITEM_ ##code, 0, __LINE__) #define _SSPINE_ERROR(code) _sspine_log(SSPINE_LOGITEM_ ##code, 1, __LINE__) #define _SSPINE_WARN(code) _sspine_log(SSPINE_LOGITEM_ ##code, 2, __LINE__) #define _SSPINE_INFO(code) _sspine_log(SSPINE_LOGITEM_ ##code, 3, __LINE__) static void _sspine_log(sspine_log_item log_item, uint32_t log_level, uint32_t line_nr) { if (_sspine.desc.logger.func) { #if defined(SOKOL_DEBUG) const char* filename = __FILE__; const char* message = _sspine_log_messages[log_item]; #else const char* filename = 0; const char* message = 0; #endif _sspine.desc.logger.func("sspine", log_level, log_item, message, line_nr, filename, _sspine.desc.logger.user_data); } else { // for log level PANIC it would be 'undefined behaviour' to continue if (log_level == 0) { abort(); } } } // ███ ███ ███████ ███ ███ ██████ ██████ ██ ██ // ████ ████ ██ ████ ████ ██ ██ ██ ██ ██ ██ // ██ ████ ██ █████ ██ ████ ██ ██ ██ ██████ ████ // ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ // ██ ██ ███████ ██ ██ ██████ ██ ██ ██ // // >>memory static void _sspine_clear(void* ptr, size_t size) { SOKOL_ASSERT(ptr && (size > 0)); memset(ptr, 0, size); } /* Copy a string into a fixed size buffer with guaranteed zero- termination. Return false if the string didn't fit into the buffer and had to be clamped. FIXME: Currently UTF-8 strings might become invalid if the string is clamped, because the last zero-byte might be written into the middle of a multi-byte sequence. */ static bool _sspine_strcpy(const char* src, char* dst, int max_len) { SOKOL_ASSERT(src && dst && (max_len > 0)); char* const end = &(dst[max_len-1]); char c = 0; for (int i = 0; i < max_len; i++) { c = *src; if (c != 0) { src++; } *dst++ = c; } // truncated? if (c != 0) { *end = 0; return false; } else { return true; } } static sspine_string _sspine_string(const char* cstr) { sspine_string res; _sspine_clear(&res, sizeof(res)); if (cstr) { res.valid = true; res.truncated = !_sspine_strcpy(cstr, res.cstr, sizeof(res.cstr)); res.len = (uint8_t)strlen(res.cstr); } return res; } static void* _sspine_malloc(size_t size) { SOKOL_ASSERT(size > 0); void* ptr; if (_sspine.desc.allocator.alloc) { ptr = _sspine.desc.allocator.alloc(size, _sspine.desc.allocator.user_data); } else { ptr = malloc(size); } if (0 == ptr) { _SSPINE_PANIC(MALLOC_FAILED); } return ptr; } static void* _sspine_malloc_clear(size_t size) { void* ptr = _sspine_malloc(size); _sspine_clear(ptr, size); return ptr; } static void _sspine_free(void* ptr) { if (_sspine.desc.allocator.free) { _sspine.desc.allocator.free(ptr, _sspine.desc.allocator.user_data); } else { free(ptr); } } // ██████ ███████ ███████ ███████ // ██ ██ ██ ██ ██ // ██████ █████ █████ ███████ // ██ ██ ██ ██ ██ // ██ ██ ███████ ██ ███████ // // >>refs static bool _sspine_atlas_ref_valid(const _sspine_atlas_ref_t* ref) { return ref->ptr && (ref->ptr->slot.id == ref->id); } static bool _sspine_skeleton_ref_valid(const _sspine_skeleton_ref_t* ref) { return ref->ptr && (ref->ptr->slot.id == ref->id); } static bool _sspine_skinset_ref_valid(const _sspine_skinset_ref_t* ref) { return ref->ptr && (ref->ptr->slot.id == ref->id); } static bool _sspine_skeleton_and_deps_valid(_sspine_skeleton_t* skeleton) { return skeleton && _sspine_atlas_ref_valid(&skeleton->atlas); } static bool _sspine_skinset_and_deps_valid(_sspine_skinset_t* skinset) { return skinset && _sspine_skeleton_ref_valid(&skinset->skel); } static bool _sspine_instance_and_deps_valid(_sspine_instance_t* instance) { return instance && _sspine_atlas_ref_valid(&instance->atlas) && _sspine_skeleton_ref_valid(&instance->skel) && ((instance->skinset.id == SSPINE_INVALID_ID) || _sspine_skinset_ref_valid(&instance->skinset)); } static sspine_image _sspine_image(uint32_t atlas_id, int index) { sspine_image img = { atlas_id, index }; return img; } static sspine_atlas_page _sspine_atlas_page(uint32_t atlas_id, int index) { sspine_atlas_page page = { atlas_id, index }; return page; } static sspine_anim _sspine_anim(uint32_t skeleton_id, int index) { sspine_anim anim = { skeleton_id, index }; return anim; } static sspine_bone _sspine_bone(uint32_t skeleton_id, int index) { sspine_bone bone = { skeleton_id, index }; return bone; } static sspine_slot _sspine_slot(uint32_t skeleton_id, int index) { sspine_slot slot = { skeleton_id, index }; return slot; } static sspine_event _sspine_event(uint32_t skeleton_id, int index) { sspine_event event = { skeleton_id, index }; return event; } static sspine_iktarget _sspine_iktarget(uint32_t skeleton_id, int index) { sspine_iktarget iktarget = { skeleton_id, index }; return iktarget; } static sspine_skin _sspine_skin(uint32_t skeleton_id, int index) { sspine_skin skin = { skeleton_id, index }; return skin; } // ██████ ██████ ██████ ██ // ██ ██ ██ ██ ██ ██ ██ // ██████ ██ ██ ██ ██ ██ // ██ ██ ██ ██ ██ ██ // ██ ██████ ██████ ███████ // // >>pool static void _sspine_init_pool(_sspine_pool_t* pool, int num) { SOKOL_ASSERT(pool && (num >= 1)); // slot 0 is reserved for the 'invalid id', so bump the pool size by 1 pool->size = num + 1; pool->queue_top = 0; // generation counters indexable by pool slot index, slot 0 is reserved size_t gen_ctrs_size = sizeof(uint32_t) * (size_t)pool->size; pool->gen_ctrs = (uint32_t*) _sspine_malloc_clear(gen_ctrs_size); // it's not a bug to only reserve 'num' here pool->free_queue = (int*) _sspine_malloc_clear(sizeof(int) * (size_t)num); // never allocate the zero-th pool item since the invalid id is 0 for (int i = pool->size-1; i >= 1; i--) { pool->free_queue[pool->queue_top++] = i; } } static void _sspine_discard_pool(_sspine_pool_t* pool) { SOKOL_ASSERT(pool); SOKOL_ASSERT(pool->free_queue); _sspine_free(pool->free_queue); pool->free_queue = 0; SOKOL_ASSERT(pool->gen_ctrs); _sspine_free(pool->gen_ctrs); pool->gen_ctrs = 0; pool->size = 0; pool->queue_top = 0; } static int _sspine_pool_alloc_index(_sspine_pool_t* pool) { SOKOL_ASSERT(pool); SOKOL_ASSERT(pool->free_queue); if (pool->queue_top > 0) { int slot_index = pool->free_queue[--pool->queue_top]; SOKOL_ASSERT((slot_index > 0) && (slot_index < pool->size)); return slot_index; } else { // pool exhausted return _SSPINE_INVALID_SLOT_INDEX; } } static void _sspine_pool_free_index(_sspine_pool_t* pool, int slot_index) { SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < pool->size)); SOKOL_ASSERT(pool); SOKOL_ASSERT(pool->free_queue); SOKOL_ASSERT(pool->queue_top < pool->size); #ifdef SOKOL_DEBUG // debug check against double-free for (int i = 0; i < pool->queue_top; i++) { SOKOL_ASSERT(pool->free_queue[i] != slot_index); } #endif pool->free_queue[pool->queue_top++] = slot_index; SOKOL_ASSERT(pool->queue_top <= (pool->size-1)); } /* initiailize a pool slot: - bump the slot's generation counter - create a resource id from the generation counter and slot index - set the slot's id to this id - set the slot's state to ALLOC - return the handle id */ static uint32_t _sspine_slot_init(_sspine_pool_t* pool, _sspine_slot_t* slot, int slot_index) { /* FIXME: add handling for an overflowing generation counter, for now, just overflow (another option is to disable the slot) */ SOKOL_ASSERT(pool && pool->gen_ctrs); SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < pool->size)); SOKOL_ASSERT((slot->state == SSPINE_RESOURCESTATE_INITIAL) && (slot->id == SSPINE_INVALID_ID)); uint32_t ctr = ++pool->gen_ctrs[slot_index]; slot->id = (ctr<<_SSPINE_SLOT_SHIFT)|(slot_index & _SSPINE_SLOT_MASK); slot->state = SSPINE_RESOURCESTATE_ALLOC; return slot->id; } // extract slot index from id static int _sspine_slot_index(uint32_t id) { int slot_index = (int) (id & _SSPINE_SLOT_MASK); SOKOL_ASSERT(_SSPINE_INVALID_SLOT_INDEX != slot_index); return slot_index; } static void _sspine_init_item_pool(_sspine_pool_t* pool, int pool_size, void** items_ptr, size_t item_size_bytes) { // NOTE: the pools will have an additional item, since slot 0 is reserved SOKOL_ASSERT(pool && (pool->size == 0)); SOKOL_ASSERT((pool_size > 0) && (pool_size < _SSPINE_MAX_POOL_SIZE)); SOKOL_ASSERT(items_ptr && (*items_ptr == 0)); SOKOL_ASSERT(item_size_bytes > 0); _sspine_init_pool(pool, pool_size); const size_t pool_size_bytes = item_size_bytes * (size_t)pool->size; *items_ptr = _sspine_malloc_clear(pool_size_bytes); } static void _sspine_discard_item_pool(_sspine_pool_t* pool, void** items_ptr) { SOKOL_ASSERT(pool && (pool->size != 0)); SOKOL_ASSERT(items_ptr && (*items_ptr != 0)); _sspine_free(*items_ptr); *items_ptr = 0; _sspine_discard_pool(pool); } // ██████ ██████ ███ ██ ████████ ███████ ██ ██ ████████ // ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ // ██ ██ ██ ██ ██ ██ ██ █████ ███ ██ // ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ // ██████ ██████ ██ ████ ██ ███████ ██ ██ ██ // // >>context static void _sspine_setup_context_pool(int pool_size) { _sspine_context_pool_t* p = &_sspine.context_pool; _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_context_t)); } static void _sspine_discard_context_pool(void) { _sspine_context_pool_t* p = &_sspine.context_pool; _sspine_discard_item_pool(&p->pool, (void**)&p->items); } static sspine_context _sspine_make_context_handle(uint32_t id) { sspine_context handle = { id }; return handle; } static _sspine_context_t* _sspine_context_at(uint32_t id) { SOKOL_ASSERT(SSPINE_INVALID_ID != id); const _sspine_context_pool_t* p = &_sspine.context_pool; int slot_index = _sspine_slot_index(id); SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); return &p->items[slot_index]; } static _sspine_context_t* _sspine_lookup_context(uint32_t id) { if (SSPINE_INVALID_ID != id) { _sspine_context_t* ctx = _sspine_context_at(id); if (ctx->slot.id == id) { return ctx; } } return 0; } static sspine_context _sspine_alloc_context(void) { _sspine_context_pool_t* p = &_sspine.context_pool; int slot_index = _sspine_pool_alloc_index(&p->pool); if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); return _sspine_make_context_handle(id); } else { // pool exhausted return _sspine_make_context_handle(SSPINE_INVALID_ID); } } static sspine_resource_state _sspine_init_context(_sspine_context_t* ctx, const sspine_context_desc* desc) { SOKOL_ASSERT(ctx && (ctx->slot.state == SSPINE_RESOURCESTATE_ALLOC)); SOKOL_ASSERT(desc); // setup vertex, index and command storage ctx->vertices.cap = desc->max_vertices; ctx->indices.cap = ctx->vertices.cap * 3; ctx->commands.cap = desc->max_commands; const size_t vbuf_size = (size_t)ctx->vertices.cap * sizeof(_sspine_vertex_t); const size_t ibuf_size = (size_t)ctx->indices.cap * sizeof(uint32_t); const size_t cbuf_size = (size_t)ctx->commands.cap * sizeof(_sspine_command_t); ctx->vertices.ptr = (_sspine_vertex_t*) _sspine_malloc(vbuf_size); ctx->indices.ptr = (uint32_t*) _sspine_malloc(ibuf_size); ctx->commands.ptr = (_sspine_command_t*) _sspine_malloc(cbuf_size); sg_buffer_desc vbuf_desc; _sspine_clear(&vbuf_desc, sizeof(vbuf_desc)); vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; vbuf_desc.usage = SG_USAGE_STREAM; vbuf_desc.size = vbuf_size; vbuf_desc.label = "sspine-vbuf"; ctx->vbuf = sg_make_buffer(&vbuf_desc); ctx->bind.vertex_buffers[0] = ctx->vbuf; sg_buffer_desc ibuf_desc; _sspine_clear(&ibuf_desc, sizeof(ibuf_desc)); ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER; ibuf_desc.usage = SG_USAGE_STREAM; ibuf_desc.size = ibuf_size; ibuf_desc.label = "sspine-ibuf"; ctx->ibuf = sg_make_buffer(&ibuf_desc); ctx->bind.index_buffer = ctx->ibuf; // for blend modes, see: https://wiki.libsdl.org/SDL_BlendMode // // NOTE: we're configuring the blend mode for premultiplied alpha, // and then do the premultiplication in the fragment shader // if needed sg_pipeline_desc pip_desc; _sspine_clear(&pip_desc, sizeof(pip_desc)); pip_desc.shader = _sspine.shd; pip_desc.layout.buffers[0].stride = sizeof(_sspine_vertex_t); pip_desc.layout.attrs[0].format = SG_VERTEXFORMAT_FLOAT2; pip_desc.layout.attrs[1].format = SG_VERTEXFORMAT_FLOAT2; pip_desc.layout.attrs[2].format = SG_VERTEXFORMAT_UBYTE4N; pip_desc.index_type = SG_INDEXTYPE_UINT32; pip_desc.sample_count = desc->sample_count; pip_desc.depth.pixel_format = desc->depth_format; pip_desc.colors[0].pixel_format = desc->color_format; pip_desc.colors[0].write_mask = desc->color_write_mask; pip_desc.colors[0].blend.enabled = true; pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_ONE; pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; pip_desc.colors[0].blend.src_factor_alpha = SG_BLENDFACTOR_ONE; pip_desc.colors[0].blend.dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; pip_desc.label = "sspine-pip-normal/additive"; ctx->pip.normal_additive = sg_make_pipeline(&pip_desc); pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_ZERO; pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_SRC_COLOR; pip_desc.colors[0].blend.src_factor_alpha = SG_BLENDFACTOR_ZERO; pip_desc.colors[0].blend.dst_factor_alpha = SG_BLENDFACTOR_ONE; pip_desc.label = "sspine-pip-multiply"; ctx->pip.multiply = sg_make_pipeline(&pip_desc); return SSPINE_RESOURCESTATE_VALID; } static void _sspine_deinit_context(_sspine_context_t* ctx) { // NOTE: it's ok to call sg_destroy functions with invalid handles sg_destroy_pipeline(ctx->pip.normal_additive); sg_destroy_pipeline(ctx->pip.multiply); sg_destroy_buffer(ctx->ibuf); sg_destroy_buffer(ctx->vbuf); if (ctx->commands.ptr) { _sspine_free(ctx->commands.ptr); ctx->commands.ptr = 0; } if (ctx->indices.ptr) { _sspine_free(ctx->indices.ptr); ctx->indices.ptr = 0; } if (ctx->vertices.ptr) { _sspine_free(ctx->vertices.ptr); ctx->vertices.ptr = 0; } } static void _sspine_destroy_context(sspine_context ctx_id) { _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); if (ctx) { _sspine_deinit_context(ctx); _sspine_context_pool_t* p = &_sspine.context_pool; _sspine_clear(ctx, sizeof(_sspine_context_t)); _sspine_pool_free_index(&p->pool, _sspine_slot_index(ctx_id.id)); } } static void _sspine_destroy_all_contexts(void) { _sspine_context_pool_t* p = &_sspine.context_pool; for (int i = 0; i < p->pool.size; i++) { _sspine_context_t* ctx = &p->items[i]; _sspine_destroy_context(_sspine_make_context_handle(ctx->slot.id)); } } static sspine_context_desc _sspine_context_desc_defaults(const sspine_context_desc* desc) { sspine_context_desc res = *desc; res.max_vertices = _sspine_def(desc->max_vertices, _SSPINE_DEFAULT_MAX_VERTICES); res.max_commands = _sspine_def(desc->max_commands, _SSPINE_DEFAULT_MAX_COMMANDS); return res; } static bool _sspine_is_default_context(sspine_context ctx_id) { return ctx_id.id == 0x00010001; } // █████ ████████ ██ █████ ███████ // ██ ██ ██ ██ ██ ██ ██ // ███████ ██ ██ ███████ ███████ // ██ ██ ██ ██ ██ ██ ██ // ██ ██ ██ ███████ ██ ██ ███████ // // >>atlas static void _sspine_setup_atlas_pool(int pool_size) { _sspine_atlas_pool_t* p = &_sspine.atlas_pool; _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_atlas_t)); } static void _sspine_discard_atlas_pool(void) { _sspine_atlas_pool_t* p = &_sspine.atlas_pool; _sspine_discard_item_pool(&p->pool, (void**)&p->items); } static sspine_atlas _sspine_make_atlas_handle(uint32_t id) { sspine_atlas handle = { id }; return handle; } static _sspine_atlas_t* _sspine_atlas_at(uint32_t id) { SOKOL_ASSERT(SSPINE_INVALID_ID != id); const _sspine_atlas_pool_t* p = &_sspine.atlas_pool; int slot_index = _sspine_slot_index(id); SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); return &p->items[slot_index]; } static _sspine_atlas_t* _sspine_lookup_atlas(uint32_t id) { if (SSPINE_INVALID_ID != id) { _sspine_atlas_t* atlas = _sspine_atlas_at(id); if (atlas->slot.id == id) { return atlas; } } return 0; } static sspine_atlas _sspine_alloc_atlas(void) { _sspine_atlas_pool_t* p = &_sspine.atlas_pool; int slot_index = _sspine_pool_alloc_index(&p->pool); if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); return _sspine_make_atlas_handle(id); } else { // pool exhausted return _sspine_make_atlas_handle(SSPINE_INVALID_ID); } } static sspine_resource_state _sspine_init_atlas(_sspine_atlas_t* atlas, const sspine_atlas_desc* desc) { SOKOL_ASSERT(atlas && (atlas->slot.state == SSPINE_RESOURCESTATE_ALLOC)); SOKOL_ASSERT(desc); SOKOL_ASSERT(atlas->sp_atlas == 0); if ((0 == desc->data.ptr) || (0 == desc->data.size)) { _SSPINE_ERROR(ATLAS_DESC_NO_DATA); return SSPINE_RESOURCESTATE_FAILED; } atlas->overrides = desc->override; // NOTE: Spine doesn't detect when invalid or corrupt data is passed here, // not much we can do about this... atlas->sp_atlas = spAtlas_create((const char*)desc->data.ptr, (int)desc->data.size, "", 0); if (0 == atlas->sp_atlas) { _SSPINE_ERROR(SPINE_ATLAS_CREATION_FAILED); return SSPINE_RESOURCESTATE_FAILED; } // allocate a sokol-gfx image handle for each page, but the actual image initialization // needs to be delegated to the application for (spAtlasPage* page = atlas->sp_atlas->pages; page != 0; page = page->next) { atlas->num_pages++; const sg_image img = sg_alloc_image(); if (sg_query_image_state(img) != SG_RESOURCESTATE_ALLOC) { _SSPINE_ERROR(SG_ALLOC_IMAGE_FAILED); return SSPINE_RESOURCESTATE_FAILED; } page->rendererObject = (void*)(uintptr_t)img.id; if (desc->override.premul_alpha_enabled) { // NOTE: -1 is spine-c convention for 'true' page->pma = -1; } else if (desc->override.premul_alpha_disabled) { page->pma = 0; } } return SSPINE_RESOURCESTATE_VALID; } static void _sspine_deinit_atlas(_sspine_atlas_t* atlas) { if (atlas->sp_atlas) { spAtlas_dispose(atlas->sp_atlas); atlas->sp_atlas = 0; } } static void _sspine_destroy_atlas(sspine_atlas atlas_id) { _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); if (atlas) { _sspine_deinit_atlas(atlas); _sspine_atlas_pool_t* p = &_sspine.atlas_pool; _sspine_clear(atlas, sizeof(_sspine_atlas_t)); _sspine_pool_free_index(&p->pool, _sspine_slot_index(atlas_id.id)); } } static void _sspine_destroy_all_atlases(void) { _sspine_atlas_pool_t* p = &_sspine.atlas_pool; for (int i = 0; i < p->pool.size; i++) { _sspine_atlas_t* atlas = &p->items[i]; _sspine_destroy_atlas(_sspine_make_atlas_handle(atlas->slot.id)); } } static sspine_atlas_desc _sspine_atlas_desc_defaults(const sspine_atlas_desc* desc) { sspine_atlas_desc res = *desc; return res; } static spAtlasPage* _sspine_lookup_atlas_page(uint32_t atlas_id, int page_index) { _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id); if (atlas) { if ((page_index >= 0) && (page_index < atlas->num_pages)) { int i = 0; for (spAtlasPage* page = atlas->sp_atlas->pages; page != 0; page = page->next, i++) { if (i == page_index) { return page; } } } } return 0; } // ███████ ██ ██ ███████ ██ ███████ ████████ ██████ ███ ██ // ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ // ███████ █████ █████ ██ █████ ██ ██ ██ ██ ██ ██ // ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ // ███████ ██ ██ ███████ ███████ ███████ ██ ██████ ██ ████ // // >>skeleton static void _sspine_setup_skeleton_pool(int pool_size) { _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_skeleton_t)); } static void _sspine_discard_skeleton_pool(void) { _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; _sspine_discard_item_pool(&p->pool, (void**)&p->items); } static sspine_skeleton _sspine_make_skeleton_handle(uint32_t id) { sspine_skeleton handle = { id }; return handle; } static _sspine_skeleton_t* _sspine_skeleton_at(uint32_t id) { SOKOL_ASSERT(SSPINE_INVALID_ID != id); const _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; int slot_index = _sspine_slot_index(id); SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); return &p->items[slot_index]; } static _sspine_skeleton_t* _sspine_lookup_skeleton(uint32_t id) { if (SSPINE_INVALID_ID != id) { _sspine_skeleton_t* skeleton = _sspine_skeleton_at(id); if (skeleton->slot.id == id) { return skeleton; } } return 0; } static sspine_skeleton _sspine_alloc_skeleton(void) { _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; int slot_index = _sspine_pool_alloc_index(&p->pool); if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); return _sspine_make_skeleton_handle(id); } else { // pool exhausted return _sspine_make_skeleton_handle(SSPINE_INVALID_ID); } } static sspine_resource_state _sspine_init_skeleton(_sspine_skeleton_t* skeleton, const sspine_skeleton_desc* desc) { SOKOL_ASSERT(skeleton && (skeleton->slot.state == SSPINE_RESOURCESTATE_ALLOC)); SOKOL_ASSERT(desc); if ((0 == desc->json_data) && ((0 == desc->binary_data.ptr) || (0 == desc->binary_data.size))) { _SSPINE_ERROR(SKELETON_DESC_NO_DATA); return SSPINE_RESOURCESTATE_FAILED; } if (desc->atlas.id == SSPINE_INVALID_ID) { _SSPINE_ERROR(SKELETON_DESC_NO_ATLAS); return SSPINE_RESOURCESTATE_FAILED; } skeleton->atlas.id = desc->atlas.id; skeleton->atlas.ptr = _sspine_lookup_atlas(skeleton->atlas.id); if (!_sspine_atlas_ref_valid(&skeleton->atlas)) { _SSPINE_ERROR(SKELETON_ATLAS_NOT_VALID); return SSPINE_RESOURCESTATE_FAILED; } _sspine_atlas_t* atlas = skeleton->atlas.ptr; if (SSPINE_RESOURCESTATE_VALID != atlas->slot.state) { _SSPINE_ERROR(SKELETON_ATLAS_NOT_VALID); return SSPINE_RESOURCESTATE_FAILED; } SOKOL_ASSERT(atlas->sp_atlas); if (desc->json_data) { spSkeletonJson* skel_json = spSkeletonJson_create(atlas->sp_atlas); SOKOL_ASSERT(skel_json); skel_json->scale = desc->prescale; skeleton->sp_skel_data = spSkeletonJson_readSkeletonData(skel_json, desc->json_data); spSkeletonJson_dispose(skel_json); skel_json = 0; if (0 == skeleton->sp_skel_data) { _SSPINE_ERROR(CREATE_SKELETON_DATA_FROM_JSON_FAILED); return SSPINE_RESOURCESTATE_FAILED; } } else { spSkeletonBinary* skel_bin = spSkeletonBinary_create(atlas->sp_atlas); SOKOL_ASSERT(skel_bin); skel_bin->scale = desc->prescale; skeleton->sp_skel_data = spSkeletonBinary_readSkeletonData(skel_bin, (const unsigned char*)desc->binary_data.ptr, (int)desc->binary_data.size); spSkeletonBinary_dispose(skel_bin); skel_bin = 0; if (0 == skeleton->sp_skel_data) { _SSPINE_ERROR(CREATE_SKELETON_DATA_FROM_BINARY_FAILED); return SSPINE_RESOURCESTATE_FAILED; } } SOKOL_ASSERT(skeleton->sp_skel_data); skeleton->sp_anim_data = spAnimationStateData_create(skeleton->sp_skel_data); SOKOL_ASSERT(skeleton->sp_anim_data); skeleton->sp_anim_data->defaultMix = desc->anim_default_mix; // get the max number of vertices in any mesh attachment int max_vertex_count = 4; // number of vertices in a 'region attachment' (a 2-triangle quad) const spSkeletonData* sp_skel_data = skeleton->sp_skel_data; for (int skinIndex = 0; skinIndex < sp_skel_data->skinsCount; skinIndex++) { const spSkin* sp_skin = sp_skel_data->skins[skinIndex]; const spSkinEntry* skin_entry = spSkin_getAttachments(sp_skin); if (skin_entry) do { if (skin_entry->attachment) { if (skin_entry->attachment->type == SP_ATTACHMENT_MESH) { const spMeshAttachment* mesh_attachment = (spMeshAttachment*)skin_entry->attachment; // worldVerticesLength is number of floats SOKOL_ASSERT((mesh_attachment->super.worldVerticesLength & 1) == 0); const int num_vertices = mesh_attachment->super.worldVerticesLength / 2; if (num_vertices > max_vertex_count) { max_vertex_count = num_vertices; } } } } while ((skin_entry = skin_entry->next) != 0); } // allocate a shared vertex transform buffer (big enough to hold vertices for biggest mesh attachment) skeleton->tform_buf.cap = max_vertex_count; skeleton->tform_buf.ptr = (sspine_vec2*) _sspine_malloc((size_t)skeleton->tform_buf.cap * sizeof(sspine_vec2)); return SSPINE_RESOURCESTATE_VALID; } static void _sspine_deinit_skeleton(_sspine_skeleton_t* skeleton) { if (skeleton->tform_buf.ptr) { _sspine_free(skeleton->tform_buf.ptr); skeleton->tform_buf.ptr = 0; } if (skeleton->sp_anim_data) { spAnimationStateData_dispose(skeleton->sp_anim_data); skeleton->sp_anim_data = 0; } if (skeleton->sp_skel_data) { spSkeletonData_dispose(skeleton->sp_skel_data); skeleton->sp_skel_data = 0; } } static void _sspine_destroy_skeleton(sspine_skeleton skeleton_id) { _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (skeleton) { _sspine_deinit_skeleton(skeleton); _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; _sspine_clear(skeleton, sizeof(_sspine_skeleton_t)); _sspine_pool_free_index(&p->pool, _sspine_slot_index(skeleton_id.id)); } } static void _sspine_destroy_all_skeletons(void) { _sspine_skeleton_pool_t* p = &_sspine.skeleton_pool; for (int i = 0; i < p->pool.size; i++) { _sspine_skeleton_t* skeleton = &p->items[i]; _sspine_destroy_skeleton(_sspine_make_skeleton_handle(skeleton->slot.id)); } } static sspine_skeleton_desc _sspine_skeleton_desc_defaults(const sspine_skeleton_desc* desc) { sspine_skeleton_desc res = *desc; res.prescale = _sspine_def(desc->prescale, 1.0f); res.anim_default_mix = _sspine_def(desc->anim_default_mix, 0.2f); return res; } static spBoneData* _sspine_lookup_bone_data(uint32_t skeleton_id, int bone_index) { _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->bones); if ((bone_index >= 0) && (bone_index <= skeleton->sp_skel_data->bonesCount)) { return skeleton->sp_skel_data->bones[bone_index]; } } return 0; } static spSlotData* _sspine_lookup_slot_data(uint32_t skeleton_id, int slot_index) { _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->slots); if ((slot_index >= 0) && (slot_index <= skeleton->sp_skel_data->slotsCount)) { return skeleton->sp_skel_data->slots[slot_index]; } } return 0; } static spEventData* _sspine_lookup_event_data(uint32_t skeleton_id, int event_index) { _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->events); if ((event_index >= 0) && (event_index < skeleton->sp_skel_data->eventsCount)) { return skeleton->sp_skel_data->events[event_index]; } } return 0; } static spIkConstraintData* _sspine_lookup_ikconstraint_data(uint32_t skeleton_id, int iktarget_index) { _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->ikConstraints); if ((iktarget_index >= 0) && (iktarget_index < skeleton->sp_skel_data->ikConstraintsCount)) { return skeleton->sp_skel_data->ikConstraints[iktarget_index]; } } return 0; } static spSkin* _sspine_lookup_skin(uint32_t skeleton_id, int skin_index) { _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data && skeleton->sp_skel_data->skins); if ((skin_index >= 0) && (skin_index < skeleton->sp_skel_data->skinsCount)) { return skeleton->sp_skel_data->skins[skin_index]; } } return 0; } // ███████ ██ ██ ██ ███ ██ ███████ ███████ ████████ // ██ ██ ██ ██ ████ ██ ██ ██ ██ // ███████ █████ ██ ██ ██ ██ ███████ █████ ██ // ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ // ███████ ██ ██ ██ ██ ████ ███████ ███████ ██ // // >>skinset static void _sspine_setup_skinset_pool(int pool_size) { _sspine_skinset_pool_t* p = &_sspine.skinset_pool; _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_skinset_t)); } static void _sspine_discard_skinset_pool(void) { _sspine_skinset_pool_t* p = &_sspine.skinset_pool; _sspine_discard_item_pool(&p->pool, (void**)&p->items); } static sspine_skinset _sspine_make_skinset_handle(uint32_t id) { sspine_skinset handle = { id }; return handle; } static _sspine_skinset_t* _sspine_skinset_at(uint32_t id) { SOKOL_ASSERT(SSPINE_INVALID_ID != id); const _sspine_skinset_pool_t* p = &_sspine.skinset_pool; int slot_index = _sspine_slot_index(id); SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); return &p->items[slot_index]; } static _sspine_skinset_t* _sspine_lookup_skinset(uint32_t id) { if (SSPINE_INVALID_ID != id) { _sspine_skinset_t* skinset = _sspine_skinset_at(id); if (skinset->slot.id == id) { return skinset; } } return 0; } static sspine_skinset _sspine_alloc_skinset(void) { _sspine_skinset_pool_t* p = &_sspine.skinset_pool; int slot_index = _sspine_pool_alloc_index(&p->pool); if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); return _sspine_make_skinset_handle(id); } else { // pool exhausted return _sspine_make_skinset_handle(SSPINE_INVALID_ID); } } static sspine_resource_state _sspine_init_skinset(_sspine_skinset_t* skinset, const sspine_skinset_desc* desc) { SOKOL_ASSERT(skinset && (skinset->slot.state == SSPINE_RESOURCESTATE_ALLOC)); SOKOL_ASSERT(desc); if (desc->skeleton.id == SSPINE_INVALID_ID) { _SSPINE_ERROR(SKINSET_DESC_NO_SKELETON); return SSPINE_RESOURCESTATE_FAILED; } skinset->skel.id = desc->skeleton.id; skinset->skel.ptr = _sspine_lookup_skeleton(desc->skeleton.id); if (!_sspine_skeleton_ref_valid(&skinset->skel)) { _SSPINE_ERROR(SKINSET_SKELETON_NOT_VALID); return SSPINE_RESOURCESTATE_FAILED; } _sspine_skeleton_t* skel = skinset->skel.ptr; if (SSPINE_RESOURCESTATE_VALID != skel->slot.state) { _SSPINE_ERROR(SKINSET_SKELETON_NOT_VALID); return SSPINE_RESOURCESTATE_FAILED; } SOKOL_ASSERT(skel->sp_skel_data); skinset->sp_skin = spSkin_create("skinset"); for (int i = 0; i < SSPINE_MAX_SKINSET_SKINS; i++) { if (desc->skins[i].skeleton_id != SSPINE_INVALID_ID) { spSkin* skin = _sspine_lookup_skin(desc->skins[i].skeleton_id, desc->skins[i].index); if (0 == skin) { _SSPINE_ERROR(SKINSET_INVALID_SKIN_HANDLE); return SSPINE_RESOURCESTATE_FAILED; } spSkin_addSkin(skinset->sp_skin, skin); } } return SSPINE_RESOURCESTATE_VALID; } static void _sspine_deinit_skinset(_sspine_skinset_t* skinset) { if (skinset->sp_skin) { spSkin_clear(skinset->sp_skin); spSkin_dispose(skinset->sp_skin); skinset->sp_skin = 0; } } static void _sspine_destroy_skinset(sspine_skinset skinset_id) { _sspine_skinset_t* skinset = _sspine_lookup_skinset(skinset_id.id); if (skinset) { _sspine_deinit_skinset(skinset); _sspine_skinset_pool_t* p = &_sspine.skinset_pool; _sspine_clear(skinset, sizeof(_sspine_skinset_t)); _sspine_pool_free_index(&p->pool, _sspine_slot_index(skinset_id.id)); } } static void _sspine_destroy_all_skinsets(void) { _sspine_skinset_pool_t* p = &_sspine.skinset_pool; for (int i = 0; i < p->pool.size; i++) { _sspine_skinset_t* skinset = &p->items[i]; _sspine_destroy_skinset(_sspine_make_skinset_handle(skinset->slot.id)); } } static sspine_skinset_desc _sspine_skinset_desc_defaults(const sspine_skinset_desc* desc) { sspine_skinset_desc res = *desc; return res; } // ██ ███ ██ ███████ ████████ █████ ███ ██ ██████ ███████ // ██ ████ ██ ██ ██ ██ ██ ████ ██ ██ ██ // ██ ██ ██ ██ ███████ ██ ███████ ██ ██ ██ ██ █████ // ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ // ██ ██ ████ ███████ ██ ██ ██ ██ ████ ██████ ███████ // // >>instance static void _sspine_setup_instance_pool(int pool_size) { _sspine_instance_pool_t* p = &_sspine.instance_pool; _sspine_init_item_pool(&p->pool, pool_size, (void**)&p->items, sizeof(_sspine_instance_t)); } static void _sspine_discard_instance_pool(void) { _sspine_instance_pool_t* p = &_sspine.instance_pool; _sspine_discard_item_pool(&p->pool, (void**)&p->items); } static sspine_instance _sspine_make_instance_handle(uint32_t id) { sspine_instance handle = { id }; return handle; } static _sspine_instance_t* _sspine_instance_at(uint32_t id) { SOKOL_ASSERT(SSPINE_INVALID_ID != id); const _sspine_instance_pool_t* p = &_sspine.instance_pool; int slot_index = _sspine_slot_index(id); SOKOL_ASSERT((slot_index > _SSPINE_INVALID_SLOT_INDEX) && (slot_index < p->pool.size)); return &p->items[slot_index]; } static _sspine_instance_t* _sspine_lookup_instance(uint32_t id) { if (SSPINE_INVALID_ID != id) { _sspine_instance_t* instance = _sspine_instance_at(id); if (instance->slot.id == id) { return instance; } } return 0; } static sspine_instance _sspine_alloc_instance(void) { _sspine_instance_pool_t* p = &_sspine.instance_pool; sspine_instance res; int slot_index = _sspine_pool_alloc_index(&p->pool); if (_SSPINE_INVALID_SLOT_INDEX != slot_index) { uint32_t id = _sspine_slot_init(&p->pool, &p->items[slot_index].slot, slot_index); res = _sspine_make_instance_handle(id); } else { // pool exhausted res = _sspine_make_instance_handle(SSPINE_INVALID_ID); } return res; } static void _sspine_rewind_triggered_events(_sspine_instance_t* instance) { instance->cur_triggered_event_index = 0; _sspine_clear(instance->triggered_events, sizeof(instance->triggered_events)); } static sspine_triggered_event_info* _sspine_next_triggered_event_info(_sspine_instance_t* instance) { if (instance->cur_triggered_event_index < _SSPINE_MAX_TRIGGERED_EVENTS) { return &instance->triggered_events[instance->cur_triggered_event_index++]; } else { return 0; } } static void _sspine_event_listener(spAnimationState* sp_anim_state, spEventType sp_event_type, spTrackEntry* sp_track_entry, spEvent* sp_event) { if (sp_event_type == SP_ANIMATION_EVENT) { SOKOL_ASSERT(sp_anim_state && sp_track_entry && sp_event); (void)sp_track_entry; SOKOL_ASSERT(sp_event->data && sp_event->data->name); _sspine_instance_t* instance = _sspine_lookup_instance((uint32_t)(uintptr_t)sp_anim_state->userData); if (_sspine_instance_and_deps_valid(instance)) { sspine_triggered_event_info* info = _sspine_next_triggered_event_info(instance); if (info) { // FIXME: this sucks, but we really need the event index _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(instance->skel.id); SOKOL_ASSERT(skeleton && skeleton->sp_skel_data->events); const spEventData* sp_event_data = sp_event->data; for (int i = 0; i < skeleton->sp_skel_data->eventsCount; i++) { if (sp_event_data == skeleton->sp_skel_data->events[i]) { info->event = _sspine_event(skeleton->slot.id, i); break; } } SOKOL_ASSERT(info->event.skeleton_id != SSPINE_INVALID_ID); info->valid = true; info->time = sp_event->time; info->int_value = sp_event->intValue; info->float_value = sp_event->floatValue; info->volume = sp_event->volume; info->balance = sp_event->balance; info->string_value = _sspine_string(sp_event->stringValue); if (info->string_value.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } } } } } static sspine_resource_state _sspine_init_instance(_sspine_instance_t* instance, const sspine_instance_desc* desc) { SOKOL_ASSERT(instance && (instance->slot.state == SSPINE_RESOURCESTATE_ALLOC)); SOKOL_ASSERT(desc); if (desc->skeleton.id == SSPINE_INVALID_ID) { _SSPINE_ERROR(INSTANCE_DESC_NO_SKELETON); return SSPINE_RESOURCESTATE_FAILED; } instance->skel.id = desc->skeleton.id; instance->skel.ptr = _sspine_lookup_skeleton(instance->skel.id); if (!_sspine_skeleton_ref_valid(&instance->skel)) { _SSPINE_ERROR(INSTANCE_SKELETON_NOT_VALID); return SSPINE_RESOURCESTATE_FAILED; } _sspine_skeleton_t* skel = instance->skel.ptr; if (SSPINE_RESOURCESTATE_VALID != skel->slot.state) { _SSPINE_ERROR(INSTANCE_SKELETON_NOT_VALID); return SSPINE_RESOURCESTATE_FAILED; } instance->atlas = skel->atlas; if (!_sspine_atlas_ref_valid(&instance->atlas)) { _SSPINE_ERROR(INSTANCE_ATLAS_NOT_VALID); return SSPINE_RESOURCESTATE_FAILED; } if (SSPINE_RESOURCESTATE_VALID != instance->atlas.ptr->slot.state) { _SSPINE_ERROR(INSTANCE_ATLAS_NOT_VALID); return SSPINE_RESOURCESTATE_FAILED; } SOKOL_ASSERT(skel->sp_skel_data); SOKOL_ASSERT(skel->sp_anim_data); instance->sp_skel = spSkeleton_create(skel->sp_skel_data); if (0 == instance->sp_skel) { _SSPINE_ERROR(SPINE_SKELETON_CREATION_FAILED); return SSPINE_RESOURCESTATE_FAILED; } instance->sp_anim_state = spAnimationState_create(skel->sp_anim_data); if (0 == instance->sp_anim_state) { _SSPINE_ERROR(SPINE_ANIMATIONSTATE_CREATION_FAILED); return SSPINE_RESOURCESTATE_FAILED; } instance->sp_clip = spSkeletonClipping_create(); if (0 == instance->sp_clip) { _SSPINE_ERROR(SPINE_SKELETONCLIPPING_CREATION_FAILED); return SSPINE_RESOURCESTATE_FAILED; } instance->sp_anim_state->userData = (void*)(uintptr_t)instance->slot.id; instance->sp_anim_state->listener = _sspine_event_listener; spSkeleton_setToSetupPose(instance->sp_skel); spAnimationState_update(instance->sp_anim_state, 0.0f); spAnimationState_apply(instance->sp_anim_state, instance->sp_skel); spSkeleton_updateWorldTransform(instance->sp_skel); return SSPINE_RESOURCESTATE_VALID; } static void _sspine_deinit_instance(_sspine_instance_t* instance) { if (instance->sp_clip) { spSkeletonClipping_dispose(instance->sp_clip); instance->sp_clip = 0; } if (instance->sp_anim_state) { spAnimationState_dispose(instance->sp_anim_state); instance->sp_anim_state = 0; } if (instance->sp_skel) { spSkeleton_dispose(instance->sp_skel); instance->sp_skel = 0; } } static void _sspine_destroy_instance(sspine_instance instance_id) { _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (instance) { _sspine_deinit_instance(instance); _sspine_instance_pool_t* p = &_sspine.instance_pool; _sspine_clear(instance, sizeof(_sspine_instance_t)); _sspine_pool_free_index(&p->pool, _sspine_slot_index(instance_id.id)); } } static void _sspine_destroy_all_instances(void) { _sspine_instance_pool_t* p = &_sspine.instance_pool; for (int i = 0; i < p->pool.size; i++) { _sspine_instance_t* instance = &p->items[i]; _sspine_destroy_instance(_sspine_make_instance_handle(instance->slot.id)); } } static spAnimation* _sspine_lookup_skeleton_anim(uint32_t skeleton_id, int anim_index) { _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); if ((anim_index >= 0) && (anim_index < skeleton->sp_skel_data->animationsCount)) { return skeleton->sp_skel_data->animations[anim_index]; } } return 0; } static spAnimation* _sspine_lookup_instance_anim(uint32_t instance_id, uint32_t skeleton_id, int anim_index) { _sspine_instance_t* instance = _sspine_lookup_instance(instance_id); if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skeleton_id)) { SOKOL_ASSERT(instance->sp_skel && instance->sp_skel->data); if ((anim_index >= 0) && (anim_index < instance->sp_skel->data->animationsCount)) { return instance->sp_skel->data->animations[anim_index]; } } return 0; } static spBone* _sspine_lookup_bone(uint32_t instance_id, uint32_t skeleton_id, int bone_index) { _sspine_instance_t* instance = _sspine_lookup_instance(instance_id); if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skeleton_id)) { SOKOL_ASSERT(instance->sp_skel && instance->sp_skel->bones); if ((bone_index >= 0) && (bone_index <= instance->sp_skel->bonesCount)) { return instance->sp_skel->bones[bone_index]; } } return 0; } static spSlot* _sspine_lookup_slot(uint32_t instance_id, uint32_t skeleton_id, int slot_index) { _sspine_instance_t* instance = _sspine_lookup_instance(instance_id); if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skeleton_id)) { SOKOL_ASSERT(instance->sp_skel && instance->sp_skel->slots); if ((slot_index >= 0) && (slot_index <= instance->sp_skel->slotsCount)) { return instance->sp_skel->slots[slot_index]; } } return 0; } static spIkConstraint* _sspine_lookup_ikconstraint(uint32_t instance_id, uint32_t skeleton_id, int iktarget_index) { _sspine_instance_t* instance = _sspine_lookup_instance(instance_id); if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skeleton_id)) { SOKOL_ASSERT(instance->sp_skel && instance->sp_skel->ikConstraints); if ((iktarget_index >= 0) && (iktarget_index < instance->sp_skel->ikConstraintsCount)) { return instance->sp_skel->ikConstraints[iktarget_index]; } } return 0; } static sspine_instance_desc _sspine_instance_desc_defaults(const sspine_instance_desc* desc) { sspine_instance_desc res = *desc; return res; } // ██████ ██████ █████ ██ ██ // ██ ██ ██ ██ ██ ██ ██ ██ // ██ ██ ██████ ███████ ██ █ ██ // ██ ██ ██ ██ ██ ██ ██ ███ ██ // ██████ ██ ██ ██ ██ ███ ███ // // >>draw static void _sspine_check_rewind_commands(_sspine_context_t* ctx) { if (_sspine.frame_id != ctx->commands.rewind_frame_id) { ctx->commands.next = 0; ctx->commands.rewind_frame_id = _sspine.frame_id; } } static _sspine_command_t* _sspine_next_command(_sspine_context_t* ctx) { _sspine_check_rewind_commands(ctx); if (ctx->commands.next < ctx->commands.cap) { return &(ctx->commands.ptr[ctx->commands.next++]); } else { _SSPINE_ERROR(COMMAND_BUFFER_FULL); return 0; } } static _sspine_command_t* _sspine_cur_command(_sspine_context_t* ctx) { _sspine_check_rewind_commands(ctx); if (ctx->commands.next > 0) { return &ctx->commands.ptr[ctx->commands.next - 1]; } else { return 0; } } static void _sspine_check_rewind_vertices(_sspine_context_t* ctx) { if (_sspine.frame_id != ctx->vertices.rewind_frame_id) { ctx->vertices.next = 0; ctx->vertices.rewind_frame_id = _sspine.frame_id; } } static _sspine_alloc_vertices_result_t _sspine_alloc_vertices(_sspine_context_t* ctx, int num) { _sspine_check_rewind_vertices(ctx); _sspine_alloc_vertices_result_t res; _sspine_clear(&res, sizeof(res)); if ((ctx->vertices.next + num) <= ctx->vertices.cap) { res.ptr = &(ctx->vertices.ptr[ctx->vertices.next]); res.index = ctx->vertices.next; ctx->vertices.next += num; } else { _SSPINE_ERROR(VERTEX_BUFFER_FULL); } return res; } static void _sspine_check_rewind_indices(_sspine_context_t* ctx) { if (_sspine.frame_id != ctx->indices.rewind_frame_id) { ctx->indices.next = 0; ctx->indices.rewind_frame_id = _sspine.frame_id; } } static _sspine_alloc_indices_result_t _sspine_alloc_indices(_sspine_context_t* ctx, int num) { _sspine_check_rewind_indices(ctx); _sspine_alloc_indices_result_t res; _sspine_clear(&res, sizeof(res)); if ((ctx->indices.next + num) <= ctx->indices.cap) { res.ptr = &(ctx->indices.ptr[ctx->indices.next]); res.index = ctx->indices.next; ctx->indices.next += num; } else { _SSPINE_ERROR(INDEX_BUFFER_FULL); } return res; } static void _sspine_draw_instance(_sspine_context_t* ctx, _sspine_instance_t* instance, int layer) { SOKOL_ASSERT(_sspine_instance_and_deps_valid(instance)); SOKOL_ASSERT(instance->sp_skel); SOKOL_ASSERT(instance->sp_anim_state); SOKOL_ASSERT(instance->sp_clip); // see: https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-sdl/src/spine-sdl-c.c const spSkeleton* sp_skel = instance->sp_skel; float* tform_buf = (float*)instance->skel.ptr->tform_buf.ptr; const int max_tform_buf_verts = instance->skel.ptr->tform_buf.cap; SOKOL_UNUSED(max_tform_buf_verts); // only used in asserts const int tform_buf_stride = 2; // each element is 2 floats spSkeletonClipping* sp_clip = instance->sp_clip; for (int slot_index = 0; slot_index < sp_skel->slotsCount; slot_index++) { spSlot* sp_slot = sp_skel->drawOrder[slot_index]; if (!sp_slot->attachment) { spSkeletonClipping_clipEnd(sp_clip, sp_slot); continue; } // early out if the slot alpha is 0 or the bone is not active // FIXME: does alpha 0 actually mean 'invisible' for all blend modes? if ((sp_slot->color.a == 0) || (!sp_slot->bone->active)) { spSkeletonClipping_clipEnd(sp_clip, sp_slot); continue; } int num_vertices = 0; float* uvs = 0; float* vertices = 0; int num_indices = 0; const uint16_t* indices = 0; const spColor* att_color = 0; sg_image img = { SG_INVALID_ID }; bool premul_alpha = false; if (sp_slot->attachment->type == SP_ATTACHMENT_REGION) { static const uint16_t quad_indices[] = { 0, 1, 2, 2, 3, 0 }; spRegionAttachment* region = (spRegionAttachment*)sp_slot->attachment; att_color = ®ion->color; // FIXME(?) early out if the slot alpha is 0 if (att_color->a == 0) { spSkeletonClipping_clipEnd(sp_clip, sp_slot); continue; } spRegionAttachment_computeWorldVertices(region, sp_slot, tform_buf, 0, tform_buf_stride); vertices = tform_buf; num_vertices = 4; indices = &quad_indices[0]; num_indices = 6; uvs = region->uvs; const spAtlasPage* sp_page = ((spAtlasRegion*)region->rendererObject)->page; img.id = (uint32_t)(uintptr_t)sp_page->rendererObject; premul_alpha = sp_page->pma != 0; } else if (sp_slot->attachment->type == SP_ATTACHMENT_MESH) { spMeshAttachment* mesh = (spMeshAttachment*)sp_slot->attachment; att_color = &mesh->color; // FIXME(?) early out if the slot alpha is 0 if (att_color->a == 0) { spSkeletonClipping_clipEnd(sp_clip, sp_slot); continue; } const int num_floats = mesh->super.worldVerticesLength; num_vertices = num_floats / 2; SOKOL_ASSERT(num_vertices <= max_tform_buf_verts); spVertexAttachment_computeWorldVertices(&mesh->super, sp_slot, 0, num_floats, tform_buf, 0, tform_buf_stride); vertices = tform_buf; indices = mesh->triangles; num_indices = mesh->trianglesCount; // actually indicesCount??? uvs = mesh->uvs; const spAtlasPage* sp_page = ((spAtlasRegion*)mesh->rendererObject)->page; img.id = (uint32_t)(uintptr_t)sp_page->rendererObject; premul_alpha = sp_page->pma != 0; } else if (sp_slot->attachment->type == SP_ATTACHMENT_CLIPPING) { spClippingAttachment* clip_attachment = (spClippingAttachment*) sp_slot->attachment; spSkeletonClipping_clipStart(sp_clip, sp_slot, clip_attachment); continue; } else { spSkeletonClipping_clipEnd(sp_clip, sp_slot); continue; } SOKOL_ASSERT(vertices && (num_vertices > 0)); SOKOL_ASSERT(indices && (num_indices > 0)); SOKOL_ASSERT(uvs); SOKOL_ASSERT(img.id != SG_INVALID_ID); if (spSkeletonClipping_isClipping(sp_clip)) { spSkeletonClipping_clipTriangles(sp_clip, tform_buf, num_vertices * 2, (uint16_t*)indices, num_indices, uvs, tform_buf_stride); vertices = sp_clip->clippedVertices->items; num_vertices = sp_clip->clippedVertices->size / 2; uvs = sp_clip->clippedUVs->items; indices = sp_clip->clippedTriangles->items; num_indices = sp_clip->clippedTriangles->size; } SOKOL_ASSERT(vertices); SOKOL_ASSERT(indices); SOKOL_ASSERT(uvs); SOKOL_ASSERT(img.id != SG_INVALID_ID); // there might be no geometry to render after clipping if ((0 == num_vertices) || (0 == num_indices)) { spSkeletonClipping_clipEnd(sp_clip, sp_slot); continue; } const _sspine_alloc_vertices_result_t dst_vertices = _sspine_alloc_vertices(ctx, num_vertices); const _sspine_alloc_indices_result_t dst_indices = _sspine_alloc_indices(ctx, num_indices); if ((0 == dst_vertices.ptr) || (0 == dst_indices.ptr)) { spSkeletonClipping_clipEnd(sp_clip, sp_slot); continue; } // write transformed and potentially clipped vertices and indices const uint8_t r = (uint8_t)(sp_skel->color.r * sp_slot->color.r * att_color->r * 255.0f); const uint8_t g = (uint8_t)(sp_skel->color.g * sp_slot->color.g * att_color->g * 255.0f); const uint8_t b = (uint8_t)(sp_skel->color.b * sp_slot->color.b * att_color->b * 255.0f); const uint8_t a = (uint8_t)(sp_skel->color.a * sp_slot->color.a * att_color->a * 255.0f); const uint32_t color = (((uint32_t)a<<24) | ((uint32_t)b<<16) | ((uint32_t)g<<8) | (uint32_t)r); for (int vi = 0; vi < num_vertices; vi++) { dst_vertices.ptr[vi].pos.x = vertices[vi*2]; dst_vertices.ptr[vi].pos.y = vertices[vi*2 + 1]; dst_vertices.ptr[vi].color = color; dst_vertices.ptr[vi].uv.x = uvs[vi*2]; dst_vertices.ptr[vi].uv.y = uvs[vi*2 + 1]; } for (int ii = 0; ii < num_indices; ii++) { dst_indices.ptr[ii] = (uint32_t)indices[ii] + (uint32_t)dst_vertices.index; } sg_pipeline pip = { SG_INVALID_ID }; // NOTE: pma == 0.0: use color from texture as is // pma == 1.0: multiply texture rgb by texture alpha in fragment shader float pma = 0.0f; switch (sp_slot->data->blendMode) { case SP_BLEND_MODE_NORMAL: case SP_BLEND_MODE_ADDITIVE: case SP_BLEND_MODE_SCREEN: pip = ctx->pip.normal_additive; pma = premul_alpha ? 0.0f : 1.0f; // NOT A BUG break; case SP_BLEND_MODE_MULTIPLY: pip = ctx->pip.multiply; pma = 0.0f; // always use texture color as is break; } // write new draw command, or merge with current draw command _sspine_command_t* cur_cmd = _sspine_cur_command(ctx); if (cur_cmd && (cur_cmd->layer == layer) && (cur_cmd->pip.id == pip.id) && (cur_cmd->img.id == img.id) && (cur_cmd->pma == pma)) { // merge with current command cur_cmd->num_elements += num_indices; } else { // record a new command _sspine_command_t* cmd_ptr = _sspine_next_command(ctx); if (cmd_ptr) { cmd_ptr->layer = layer; cmd_ptr->pip = pip; cmd_ptr->img = img; cmd_ptr->pma = pma; cmd_ptr->base_element = dst_indices.index; cmd_ptr->num_elements = num_indices; } } spSkeletonClipping_clipEnd(sp_clip, sp_slot); } spSkeletonClipping_clipEnd2(sp_clip); } // compute orthographic projection matrix static void _sspine_layer_transform_to_proj(const sspine_layer_transform* tform, float* res) { const float left = -tform->origin.x; const float right = tform->size.x - tform->origin.x; const float top = -tform->origin.y; const float bottom = tform->size.y - tform->origin.y; const float znear = -1.0f; const float zfar = 1.0f; res[0] = 2.0f / (right - left); res[1] = 0.0f; res[2] = 0.0f; res[3] = 0.0f; res[4] = 0.0f; res[5] = 2.0f / (top - bottom); res[6] = 0.0f; res[7] = 0.0f; res[8] = 0.0f; res[9] = 0.0f; res[10] = -2.0f / (zfar - znear); res[11] = 0.0f; res[12] = -(right + left) / (right - left); res[13] = -(top + bottom) / (top - bottom); res[14] = -(zfar + znear) / (zfar - znear); res[15] = 1.0f; } static _sspine_vsparams_t _sspine_compute_vsparams(const sspine_layer_transform* tform) { _sspine_vsparams_t p; _sspine_clear(&p, sizeof(p)); _sspine_layer_transform_to_proj(tform, p.mvp); return p; } static void _sspine_draw_layer(_sspine_context_t* ctx, int layer, const sspine_layer_transform* tform) { if ((ctx->vertices.next > 0) && (ctx->commands.next > 0)) { sg_push_debug_group("sokol-spine"); if (ctx->update_frame_id != _sspine.frame_id) { ctx->update_frame_id = _sspine.frame_id; const sg_range vtx_range = { ctx->vertices.ptr, (size_t)ctx->vertices.next * sizeof(_sspine_vertex_t) }; sg_update_buffer(ctx->vbuf, &vtx_range); const sg_range idx_range = { ctx->indices.ptr, (size_t)ctx->indices.next * sizeof(uint32_t) }; sg_update_buffer(ctx->ibuf, &idx_range); } _sspine_vsparams_t vsparams = _sspine_compute_vsparams(tform); const sg_range vsparams_range = { &vsparams, sizeof(vsparams) }; _sspine_fsparams_t fsparams; _sspine_clear(&fsparams, sizeof(fsparams)); const sg_range fsparams_range = { &fsparams, sizeof(fsparams) }; uint32_t cur_pip_id = SG_INVALID_ID; uint32_t cur_img_id = SG_INVALID_ID; float cur_pma = -1.0f; for (int i = 0; i < ctx->commands.next; i++) { const _sspine_command_t* cmd = &ctx->commands.ptr[i]; if ((layer == cmd->layer) && (sg_query_image_state(cmd->img) == SG_RESOURCESTATE_VALID)) { if (cur_pip_id != cmd->pip.id) { sg_apply_pipeline(cmd->pip); cur_pip_id = cmd->pip.id; sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &vsparams_range); cur_img_id = SG_INVALID_ID; } if (cur_img_id != cmd->img.id) { ctx->bind.fs_images[0] = cmd->img; sg_apply_bindings(&ctx->bind); cur_img_id = cmd->img.id; } if (cur_pma != cmd->pma) { fsparams.pma = cmd->pma; sg_apply_uniforms(SG_SHADERSTAGE_FS, 0, &fsparams_range); cur_pma = cmd->pma; } if (cmd->num_elements > 0) { sg_draw(cmd->base_element, cmd->num_elements, 1); } } } sg_pop_debug_group(); } } // ███ ███ ██ ███████ ██████ // ████ ████ ██ ██ ██ // ██ ████ ██ ██ ███████ ██ // ██ ██ ██ ██ ██ ██ // ██ ██ ██ ███████ ██████ // // >>misc // return sspine_desc with patched defaults static sspine_desc _sspine_desc_defaults(const sspine_desc* desc) { SOKOL_ASSERT((desc->allocator.alloc && desc->allocator.free) || (!desc->allocator.alloc && !desc->allocator.free)); sspine_desc res = *desc; res.max_vertices = _sspine_def(desc->max_vertices, _SSPINE_DEFAULT_MAX_VERTICES); res.max_commands = _sspine_def(desc->max_commands, _SSPINE_DEFAULT_MAX_COMMANDS); res.context_pool_size = _sspine_def(desc->context_pool_size, _SSPINE_DEFAULT_CONTEXT_POOL_SIZE); res.atlas_pool_size = _sspine_def(desc->atlas_pool_size, _SSPINE_DEFAULT_ATLAS_POOL_SIZE); res.skeleton_pool_size = _sspine_def(desc->skeleton_pool_size, _SSPINE_DEFAULT_SKELETON_POOL_SIZE); res.skinset_pool_size = _sspine_def(desc->skinset_pool_size, _SSPINE_DEFAULT_SKINSET_POOL_SIZE); res.instance_pool_size = _sspine_def(desc->instance_pool_size, _SSPINE_DEFAULT_INSTANCE_POOL_SIZE); return res; } static sspine_context_desc _sspine_as_context_desc(const sspine_desc* desc) { sspine_context_desc ctx_desc; _sspine_clear(&ctx_desc, sizeof(ctx_desc)); ctx_desc.max_vertices = desc->max_vertices; ctx_desc.max_commands = desc->max_commands; ctx_desc.color_format = desc->color_format; ctx_desc.depth_format = desc->depth_format; ctx_desc.sample_count = desc->sample_count; ctx_desc.color_write_mask = desc->color_write_mask; return ctx_desc; } static sg_filter _sspine_as_image_filter(spAtlasFilter filter) { switch (filter) { case SP_ATLAS_UNKNOWN_FILTER: return _SG_FILTER_DEFAULT; case SP_ATLAS_NEAREST: return SG_FILTER_NEAREST; case SP_ATLAS_LINEAR: return SG_FILTER_LINEAR; case SP_ATLAS_MIPMAP: return SG_FILTER_LINEAR_MIPMAP_NEAREST; case SP_ATLAS_MIPMAP_NEAREST_NEAREST: return SG_FILTER_NEAREST_MIPMAP_NEAREST; case SP_ATLAS_MIPMAP_LINEAR_NEAREST: return SG_FILTER_LINEAR_MIPMAP_NEAREST; case SP_ATLAS_MIPMAP_NEAREST_LINEAR: return SG_FILTER_NEAREST_MIPMAP_LINEAR; case SP_ATLAS_MIPMAP_LINEAR_LINEAR: return SG_FILTER_LINEAR_MIPMAP_LINEAR; default: return _SG_FILTER_DEFAULT; } } static sg_wrap _sspine_as_image_wrap(spAtlasWrap wrap) { switch (wrap) { case SP_ATLAS_MIRROREDREPEAT: return SG_WRAP_MIRRORED_REPEAT; case SP_ATLAS_CLAMPTOEDGE: return SG_WRAP_CLAMP_TO_EDGE; case SP_ATLAS_REPEAT: return SG_WRAP_REPEAT; default: return _SG_WRAP_DEFAULT; } } static void _sspine_init_image_info(const _sspine_atlas_t* atlas, int index, sspine_image_info* info, bool with_overrides) { spAtlasPage* page = _sspine_lookup_atlas_page(atlas->slot.id, index); SOKOL_ASSERT(page); SOKOL_ASSERT(page->name); info->valid = true; info->sgimage.id = (uint32_t)(uintptr_t)page->rendererObject; if (with_overrides && (atlas->overrides.min_filter != _SG_FILTER_DEFAULT)) { info->min_filter = atlas->overrides.min_filter; } else { info->min_filter = _sspine_as_image_filter(page->minFilter); } if (with_overrides && (atlas->overrides.mag_filter != _SG_FILTER_DEFAULT)) { info->mag_filter = atlas->overrides.mag_filter; } else { info->mag_filter = _sspine_as_image_filter(page->magFilter); } if (with_overrides && (atlas->overrides.wrap_u != _SG_WRAP_DEFAULT)) { info->wrap_u = atlas->overrides.wrap_u; } else { info->wrap_u = _sspine_as_image_wrap(page->uWrap); } if (with_overrides && (atlas->overrides.wrap_v != _SG_WRAP_DEFAULT)) { info->wrap_v = atlas->overrides.wrap_v; } else { info->wrap_v = _sspine_as_image_wrap(page->vWrap); } info->width = page->width; info->height = page->height; // NOTE: override already happened in atlas init info->premul_alpha = page->pma != 0; info->filename = _sspine_string(page->name); if (info->filename.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } } static void _sspine_init_shared(void) { sg_shader_desc shd_desc; _sspine_clear(&shd_desc, sizeof(shd_desc)); shd_desc.attrs[0].name = "position"; shd_desc.attrs[1].name = "texcoord0"; shd_desc.attrs[2].name = "color0"; shd_desc.attrs[0].sem_name = "TEXCOORD"; shd_desc.attrs[0].sem_index = 0; shd_desc.attrs[1].sem_name = "TEXCOORD"; shd_desc.attrs[1].sem_index = 1; shd_desc.attrs[2].sem_name = "TEXCOORD"; shd_desc.attrs[2].sem_index = 2; shd_desc.vs.uniform_blocks[0].size = sizeof(_sspine_vsparams_t); shd_desc.vs.uniform_blocks[0].layout = SG_UNIFORMLAYOUT_STD140; shd_desc.vs.uniform_blocks[0].uniforms[0].name = "vs_params"; shd_desc.vs.uniform_blocks[0].uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; shd_desc.vs.uniform_blocks[0].uniforms[0].array_count = 4; shd_desc.fs.uniform_blocks[0].size = 16; shd_desc.fs.uniform_blocks[0].layout = SG_UNIFORMLAYOUT_STD140; shd_desc.fs.uniform_blocks[0].uniforms[0].name = "fs_params"; shd_desc.fs.uniform_blocks[0].uniforms[0].type = SG_UNIFORMTYPE_FLOAT4; shd_desc.fs.uniform_blocks[0].uniforms[0].array_count = 1; shd_desc.fs.images[0].name = "tex"; shd_desc.fs.images[0].image_type = SG_IMAGETYPE_2D; shd_desc.fs.images[0].sampler_type = SG_SAMPLERTYPE_FLOAT; shd_desc.label = "sspine-shader"; #if defined(SOKOL_GLCORE33) shd_desc.vs.source = _sspine_vs_source_glsl330; shd_desc.fs.source = _sspine_fs_source_glsl330; #elif defined(SOKOL_GLES3) shd_desc.vs.source = _sspine_vs_source_glsl300es; shd_desc.fs.source = _sspine_fs_source_glsl300es; #elif defined(SOKOL_METAL) shd_desc.vs.entry = "main0"; shd_desc.fs.entry = "main0"; switch (sg_query_backend()) { case SG_BACKEND_METAL_MACOS: shd_desc.vs.bytecode = SG_RANGE(_sspine_vs_bytecode_metal_macos); shd_desc.fs.bytecode = SG_RANGE(_sspine_fs_bytecode_metal_macos); break; case SG_BACKEND_METAL_IOS: shd_desc.vs.bytecode = SG_RANGE(_sspine_vs_bytecode_metal_ios); shd_desc.fs.bytecode = SG_RANGE(_sspine_fs_bytecode_metal_ios); break; default: shd_desc.vs.source = _sspine_vs_source_metal_sim; shd_desc.fs.source = _sspine_fs_source_metal_sim; break; } #elif defined(SOKOL_D3D11) shd_desc.vs.bytecode = SG_RANGE(_sspine_vs_bytecode_hlsl4); shd_desc.fs.bytecode = SG_RANGE(_sspine_fs_bytecode_hlsl4); #elif defined(SOKOL_WGPU) shd_desc.vs.bytecode = SG_RANGE(_sspine_vs_bytecode_wgpu); shd_desc.fs.bytecode = SG_RANGE(_sspine_fs_bytecode_wgpu); #else shd_desc.vs.source = _sspine_vs_source_dummy; shd_desc.fs.source = _sspine_fs_source_dummy; #endif _sspine.shd = sg_make_shader(&shd_desc); } static void _sspine_destroy_shared(void) { sg_destroy_shader(_sspine.shd); } // called from inside sokol-gfx sg_commit() static void _sspine_commit_listener_func(void* userdata) { (void)userdata; SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine.frame_id++; } static sg_commit_listener _sspine_make_commit_listener(void) { sg_commit_listener commit_listener = { _sspine_commit_listener_func, 0 }; return commit_listener; } // ██████ ██ ██ ██████ ██ ██ ██████ // ██ ██ ██ ██ ██ ██ ██ ██ ██ // ██████ ██ ██ ██████ ██ ██ ██ // ██ ██ ██ ██ ██ ██ ██ ██ // ██ ██████ ██████ ███████ ██ ██████ // // >>public SOKOL_API_IMPL void sspine_setup(const sspine_desc* desc) { SOKOL_ASSERT(desc); spBone_setYDown(1); _sspine_clear(&_sspine, sizeof(_sspine)); _sspine.init_cookie = _SSPINE_INIT_COOKIE; _sspine.desc = _sspine_desc_defaults(desc); _sspine_init_shared(); // important, need to setup the frame id with a non-zero value, // otherwise updates won't trigger in the first frame _sspine.frame_id = 1; _sspine_setup_context_pool(_sspine.desc.context_pool_size); _sspine_setup_atlas_pool(_sspine.desc.atlas_pool_size); _sspine_setup_skeleton_pool(_sspine.desc.skeleton_pool_size); _sspine_setup_skinset_pool(_sspine.desc.skinset_pool_size); _sspine_setup_instance_pool(_sspine.desc.instance_pool_size); const sspine_context_desc ctx_desc = _sspine_as_context_desc(&_sspine.desc); _sspine.def_ctx_id = sspine_make_context(&ctx_desc); SOKOL_ASSERT(_sspine_is_default_context(_sspine.def_ctx_id)); sspine_set_context(_sspine.def_ctx_id); if (!sg_add_commit_listener(_sspine_make_commit_listener())) { _SSPINE_ERROR(ADD_COMMIT_LISTENER_FAILED); } } SOKOL_API_IMPL void sspine_shutdown(void) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sg_remove_commit_listener(_sspine_make_commit_listener()); _sspine_destroy_all_instances(); _sspine_destroy_all_skinsets(); _sspine_destroy_all_skeletons(); _sspine_destroy_all_atlases(); _sspine_destroy_all_contexts(); _sspine_discard_instance_pool(); _sspine_discard_skinset_pool(); _sspine_discard_skeleton_pool(); _sspine_discard_atlas_pool(); _sspine_discard_context_pool(); _sspine_destroy_shared(); _sspine.init_cookie = 0; } SOKOL_API_IMPL sspine_context sspine_make_context(const sspine_context_desc* desc) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(desc); const sspine_context_desc desc_def = _sspine_context_desc_defaults(desc); sspine_context ctx_id = _sspine_alloc_context(); _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); if (ctx) { ctx->slot.state = _sspine_init_context(ctx, &desc_def); SOKOL_ASSERT((ctx->slot.state == SSPINE_RESOURCESTATE_VALID) || (ctx->slot.state == SSPINE_RESOURCESTATE_FAILED)); if (ctx->slot.state == SSPINE_RESOURCESTATE_FAILED) { _sspine_deinit_context(ctx); } } else { ctx->slot.state = SSPINE_RESOURCESTATE_FAILED; _SSPINE_ERROR(CONTEXT_POOL_EXHAUSTED); } return ctx_id; } SOKOL_API_IMPL void sspine_destroy_context(sspine_context ctx_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); if (_sspine_is_default_context(ctx_id)) { _SSPINE_ERROR(CANNOT_DESTROY_DEFAULT_CONTEXT); return; } _sspine_destroy_context(ctx_id); // re-validate the current context pointer (this will return a nullptr // if we just destroyed the current context) _sspine.cur_ctx = _sspine_lookup_context(_sspine.cur_ctx_id.id); } SOKOL_API_IMPL void sspine_set_context(sspine_context ctx_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); if (_sspine_is_default_context(ctx_id)) { _sspine.cur_ctx_id = _sspine.def_ctx_id; } else { _sspine.cur_ctx_id = ctx_id; } // this will return null if the handle isn't valid _sspine.cur_ctx = _sspine_lookup_context(_sspine.cur_ctx_id.id); } SOKOL_API_IMPL sspine_context sspine_get_context(void) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); return _sspine.cur_ctx_id; } SOKOL_API_IMPL sspine_context sspine_default_context(void) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); return _sspine_make_context_handle(0x00010001); } SOKOL_API_IMPL sspine_context_info sspine_get_context_info(sspine_context ctx_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_context_info res; _sspine_clear(&res, sizeof(res)); const _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); if (ctx) { res.num_vertices = ctx->vertices.next; res.num_indices = ctx->indices.next; res.num_commands = ctx->commands.next; } return res; } SOKOL_API_IMPL void sspine_set_skinset(sspine_instance instance_id, sspine_skinset skinset_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); _sspine_skinset_t* skinset = _sspine_lookup_skinset(skinset_id.id); if (_sspine_instance_and_deps_valid(instance) && _sspine_skinset_and_deps_valid(skinset) && (instance->skel.id == skinset->skel.id)) { SOKOL_ASSERT(instance->sp_skel); SOKOL_ASSERT(instance->sp_anim_state); SOKOL_ASSERT(skinset->sp_skin); spSkeleton_setSkin(instance->sp_skel, 0); spSkeleton_setSkin(instance->sp_skel, skinset->sp_skin); spSkeleton_setSlotsToSetupPose(instance->sp_skel); spAnimationState_apply(instance->sp_anim_state, instance->sp_skel); } } SOKOL_API_IMPL void sspine_update_instance(sspine_instance instance_id, float delta_time) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (_sspine_instance_and_deps_valid(instance)) { SOKOL_ASSERT(instance->sp_skel); SOKOL_ASSERT(instance->sp_anim_state); _sspine_rewind_triggered_events(instance); spAnimationState_update(instance->sp_anim_state, delta_time); spAnimationState_apply(instance->sp_anim_state, instance->sp_skel); spSkeleton_updateWorldTransform(instance->sp_skel); } } SOKOL_API_IMPL int sspine_num_triggered_events(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (_sspine_instance_and_deps_valid(instance)) { SOKOL_ASSERT((instance->cur_triggered_event_index >= 0) && (instance->cur_triggered_event_index <= _SSPINE_MAX_TRIGGERED_EVENTS)); return instance->cur_triggered_event_index; } return 0; } SOKOL_API_IMPL sspine_triggered_event_info sspine_get_triggered_event_info(sspine_instance instance_id, int triggered_event_index) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); sspine_triggered_event_info res; _sspine_clear(&res, sizeof(res)); if (_sspine_instance_and_deps_valid(instance)) { if ((triggered_event_index >= 0) && (triggered_event_index < instance->cur_triggered_event_index)) { res = instance->triggered_events[triggered_event_index]; } } return res; } SOKOL_API_IMPL void sspine_draw_instance_in_layer(sspine_instance instance_id, int layer) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_context_t* ctx = _sspine.cur_ctx; _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (ctx && _sspine_instance_and_deps_valid(instance)) { _sspine_draw_instance(ctx, instance, layer); } } SOKOL_API_IMPL sspine_mat4 sspine_layer_transform_to_mat4(const sspine_layer_transform* tform) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_mat4 res; _sspine_layer_transform_to_proj(tform, res.m); return res; } SOKOL_API_IMPL void sspine_context_draw_instance_in_layer(sspine_context ctx_id, sspine_instance instance_id, int layer) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (ctx && _sspine_instance_and_deps_valid(instance)) { _sspine_draw_instance(ctx, instance, layer); } } SOKOL_API_IMPL void sspine_draw_layer(int layer, const sspine_layer_transform* tform) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(tform); _sspine_context_t* ctx = _sspine.cur_ctx; if (ctx) { _sspine_draw_layer(ctx, layer, tform); } } SOKOL_API_IMPL void sspine_context_draw_layer(sspine_context ctx_id, int layer, const sspine_layer_transform* tform) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(tform); _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); if (ctx) { _sspine_draw_layer(ctx, layer, tform); } } SOKOL_API_IMPL sspine_atlas sspine_make_atlas(const sspine_atlas_desc* desc) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(desc); const sspine_atlas_desc desc_def = _sspine_atlas_desc_defaults(desc); sspine_atlas atlas_id = _sspine_alloc_atlas(); _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); if (atlas) { atlas->slot.state = _sspine_init_atlas(atlas, &desc_def); SOKOL_ASSERT((atlas->slot.state == SSPINE_RESOURCESTATE_VALID) || (atlas->slot.state == SSPINE_RESOURCESTATE_FAILED)); if (atlas->slot.state == SSPINE_RESOURCESTATE_FAILED) { _sspine_deinit_atlas(atlas); } } else { _SSPINE_ERROR(ATLAS_POOL_EXHAUSTED); } return atlas_id; } SOKOL_API_IMPL void sspine_destroy_atlas(sspine_atlas atlas_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_destroy_atlas(atlas_id); } SOKOL_API_IMPL sspine_skeleton sspine_make_skeleton(const sspine_skeleton_desc* desc) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(desc); const sspine_skeleton_desc desc_def = _sspine_skeleton_desc_defaults(desc); sspine_skeleton skeleton_id = _sspine_alloc_skeleton(); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (skeleton) { skeleton->slot.state = _sspine_init_skeleton(skeleton, &desc_def); SOKOL_ASSERT((skeleton->slot.state == SSPINE_RESOURCESTATE_VALID) || (skeleton->slot.state == SSPINE_RESOURCESTATE_FAILED)); if (skeleton->slot.state == SSPINE_RESOURCESTATE_FAILED) { _sspine_deinit_skeleton(skeleton); } } else { _SSPINE_ERROR(SKELETON_POOL_EXHAUSTED); } return skeleton_id; } SOKOL_API_IMPL void sspine_destroy_skeleton(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_destroy_skeleton(skeleton_id); } SOKOL_API_IMPL sspine_skinset sspine_make_skinset(const sspine_skinset_desc* desc) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(desc); const sspine_skinset_desc desc_def = _sspine_skinset_desc_defaults(desc); sspine_skinset skinset_id = _sspine_alloc_skinset(); _sspine_skinset_t* skinset = _sspine_lookup_skinset(skinset_id.id); if (skinset) { skinset->slot.state = _sspine_init_skinset(skinset, &desc_def); SOKOL_ASSERT((skinset->slot.state == SSPINE_RESOURCESTATE_VALID) || (skinset->slot.state == SSPINE_RESOURCESTATE_FAILED)); if (skinset->slot.state == SSPINE_RESOURCESTATE_FAILED) { _sspine_deinit_skinset(skinset); } } else { _SSPINE_ERROR(SKINSET_POOL_EXHAUSTED); } return skinset_id; } SOKOL_API_IMPL void sspine_destroy_skinset(sspine_skinset skinset_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_destroy_skinset(skinset_id); } SOKOL_API_IMPL sspine_instance sspine_make_instance(const sspine_instance_desc* desc) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(desc); const sspine_instance_desc desc_def = _sspine_instance_desc_defaults(desc); sspine_instance instance_id = _sspine_alloc_instance(); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (instance) { instance->slot.state = _sspine_init_instance(instance, &desc_def); SOKOL_ASSERT((instance->slot.state == SSPINE_RESOURCESTATE_VALID) || (instance->slot.state == SSPINE_RESOURCESTATE_FAILED)); if (instance->slot.state == SSPINE_RESOURCESTATE_FAILED) { _sspine_deinit_instance(instance); } } else { _SSPINE_ERROR(INSTANCE_POOL_EXHAUSTED); } return instance_id; } SOKOL_API_IMPL void sspine_destroy_instance(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_destroy_instance(instance_id); } SOKOL_API_IMPL sspine_resource_state sspine_get_context_resource_state(sspine_context ctx_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); const _sspine_context_t* ctx = _sspine_lookup_context(ctx_id.id); if (ctx) { return ctx->slot.state; } else { return SSPINE_RESOURCESTATE_INVALID; } } SOKOL_API_IMPL sspine_resource_state sspine_get_atlas_resource_state(sspine_atlas atlas_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); const _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); if (atlas) { return atlas->slot.state; } else { return SSPINE_RESOURCESTATE_INVALID; } } SOKOL_API_IMPL sspine_resource_state sspine_get_skeleton_resource_state(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); const _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (skeleton) { return skeleton->slot.state; } else { return SSPINE_RESOURCESTATE_INVALID; } } SOKOL_API_IMPL sspine_resource_state sspine_get_skinset_resource_state(sspine_skinset skinset_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); const _sspine_skinset_t* skinset = _sspine_lookup_skinset(skinset_id.id); if (skinset) { return skinset->slot.state; } else { return SSPINE_RESOURCESTATE_INVALID; } } SOKOL_API_IMPL sspine_resource_state sspine_get_instance_resource_state(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); const _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (instance) { return instance->slot.state; } else { return SSPINE_RESOURCESTATE_INVALID; } } SOKOL_API_IMPL bool sspine_context_valid(sspine_context ctx_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); return sspine_get_context_resource_state(ctx_id) == SSPINE_RESOURCESTATE_VALID; } SOKOL_API_IMPL bool sspine_atlas_valid(sspine_atlas atlas_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); return sspine_get_atlas_resource_state(atlas_id) == SSPINE_RESOURCESTATE_VALID; } SOKOL_API_IMPL bool sspine_skeleton_valid(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); return sspine_get_skeleton_resource_state(skeleton_id) == SSPINE_RESOURCESTATE_VALID; } SOKOL_API_IMPL bool sspine_skinset_valid(sspine_skinset skinset_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); return sspine_get_skinset_resource_state(skinset_id) == SSPINE_RESOURCESTATE_VALID; } SOKOL_API_IMPL bool sspine_instance_valid(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); return sspine_get_instance_resource_state(instance_id) == SSPINE_RESOURCESTATE_VALID; } SOKOL_API_IMPL sspine_atlas sspine_get_skeleton_atlas(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); sspine_atlas res; _sspine_clear(&res, sizeof(res)); if (skeleton) { res.id = skeleton->atlas.id; } return res; } SOKOL_API_IMPL sspine_skeleton sspine_get_instance_skeleton(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); sspine_skeleton res; _sspine_clear(&res, sizeof(res)); if (instance) { res.id = instance->skel.id; } return res; } SOKOL_API_IMPL int sspine_num_images(sspine_atlas atlas_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); if (atlas) { return atlas->num_pages; } return 0; } SOKOL_API_IMPL sspine_image sspine_image_by_index(sspine_atlas atlas_id, int index) { return _sspine_image(atlas_id.id, index); } SOKOL_API_IMPL bool sspine_image_valid(sspine_image image) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_atlas_t* atlas = _sspine_lookup_atlas(image.atlas_id); return atlas && (image.index >= 0) && (image.index < atlas->num_pages); } SOKOL_API_IMPL bool sspine_image_equal(sspine_image first, sspine_image second) { return (first.atlas_id == second.atlas_id) && (first.index == second.index); } SOKOL_API_IMPL sspine_image_info sspine_get_image_info(sspine_image image) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_atlas_t* atlas = _sspine_lookup_atlas(image.atlas_id); sspine_image_info img_info; _sspine_clear(&img_info, sizeof(img_info)); if (atlas && (image.index >= 0) && (image.index < atlas->num_pages)) { _sspine_init_image_info(atlas, image.index, &img_info, true); } return img_info; } SOKOL_API_IMPL int sspine_num_atlas_pages(sspine_atlas atlas_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_atlas_t* atlas = _sspine_lookup_atlas(atlas_id.id); if (atlas) { return atlas->num_pages; } else { return 0; } } SOKOL_API_IMPL sspine_atlas_page sspine_atlas_page_by_index(sspine_atlas atlas_id, int index) { return _sspine_atlas_page(atlas_id.id, index); } SOKOL_API_IMPL bool sspine_atlas_page_valid(sspine_atlas_page page) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_atlas_t* atlas = _sspine_lookup_atlas(page.atlas_id); if (atlas) { return (page.index >= 0) && (page.index < atlas->num_pages); } return false; } SOKOL_API_IMPL bool sspine_atlas_page_equal(sspine_atlas_page first, sspine_atlas_page second) { return (first.atlas_id == second.atlas_id) && (first.index == second.index); } SOKOL_API_IMPL sspine_atlas_page_info sspine_get_atlas_page_info(sspine_atlas_page page) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_atlas_page_info res; _sspine_clear(&res, sizeof(res)); const spAtlasPage* sp_page = _sspine_lookup_atlas_page(page.atlas_id, page.index); if (sp_page) { // at this point, atlas is guaranteed to be valid const _sspine_atlas_t* atlas = _sspine_lookup_atlas(page.atlas_id); res.valid = true; res.atlas.id = page.atlas_id; // write image info without overrides _sspine_init_image_info(atlas, page.index, &res.image, false); // ...and provide the overrides separately res.overrides = atlas->overrides; } return res; } SOKOL_API_IMPL void sspine_set_position(sspine_instance instance_id, sspine_vec2 position) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (_sspine_instance_and_deps_valid(instance)) { SOKOL_ASSERT(instance->sp_skel); instance->sp_skel->x = position.x; instance->sp_skel->y = position.y; } } SOKOL_API_IMPL void sspine_set_scale(sspine_instance instance_id, sspine_vec2 scale) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (instance) { SOKOL_ASSERT(instance->sp_skel); instance->sp_skel->scaleX = scale.x; instance->sp_skel->scaleY = scale.y; } } SOKOL_API_IMPL void sspine_set_color(sspine_instance instance_id, sspine_color color) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (instance) { SOKOL_ASSERT(instance->sp_skel); instance->sp_skel->color.r = color.r; instance->sp_skel->color.g = color.g; instance->sp_skel->color.b = color.b; instance->sp_skel->color.a = color.a; } } SOKOL_API_IMPL sspine_vec2 sspine_get_position(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); sspine_vec2 v = { 0.0f, 0.0f }; if (instance) { SOKOL_ASSERT(instance->sp_skel); v.x = instance->sp_skel->x; v.y = instance->sp_skel->y; } return v; } SOKOL_API_IMPL sspine_vec2 sspine_get_scale(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); sspine_vec2 v = { 0.0f, 0.0f }; if (instance) { SOKOL_ASSERT(instance->sp_skel); v.x = instance->sp_skel->scaleX; v.y = instance->sp_skel->scaleY; } return v; } SOKOL_API_IMPL sspine_color sspine_get_color(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); sspine_color c = { 0.0f, 0.0f, 0.0f, 0.0f }; if (instance) { SOKOL_ASSERT(instance->sp_skel); c.r = instance->sp_skel->color.r; c.g = instance->sp_skel->color.g; c.b = instance->sp_skel->color.b; c.a = instance->sp_skel->color.a; } return c; } SOKOL_API_IMPL int sspine_num_anims(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return skeleton->sp_skel_data->animationsCount; } return 0; } SOKOL_API_IMPL sspine_anim sspine_anim_by_name(sspine_skeleton skeleton_id, const char* name) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(name); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { // NOTE: there's a spSkeletonData_findAnimation function, but that doesn't // give us access to the index, so we'll need to do the loop ourselves SOKOL_ASSERT(skeleton->sp_skel_data); const spSkeletonData* sp_skel_data = skeleton->sp_skel_data; const int num_anims = sp_skel_data->animationsCount; SOKOL_ASSERT(sp_skel_data->animations); for (int i = 0; i < num_anims; i++) { SOKOL_ASSERT(sp_skel_data->animations[i]); SOKOL_ASSERT(sp_skel_data->animations[i]->name); if (0 == strcmp(sp_skel_data->animations[i]->name, name)) { return _sspine_anim(skeleton_id.id, i); } } } return _sspine_anim(0, 0); } SOKOL_API_IMPL sspine_anim sspine_anim_by_index(sspine_skeleton skeleton_id, int index) { return _sspine_anim(skeleton_id.id, index); } SOKOL_API_IMPL bool sspine_anim_valid(sspine_anim anim) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(anim.skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return (anim.index >= 0) && (anim.index < skeleton->sp_skel_data->animationsCount); } return false; } SOKOL_API_IMPL bool sspine_anim_equal(sspine_anim first, sspine_anim second) { return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); } SOKOL_API_IMPL sspine_anim_info sspine_get_anim_info(sspine_anim anim) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_anim_info res; _sspine_clear(&res, sizeof(res)); const spAnimation* sp_anim = _sspine_lookup_skeleton_anim(anim.skeleton_id, anim.index); if (sp_anim) { res.valid = true; res.index = anim.index; res.duration = sp_anim->duration; res.name = _sspine_string(sp_anim->name); if (res.name.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } } return res; } SOKOL_API_IMPL void sspine_clear_animation_tracks(sspine_instance instance_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (_sspine_instance_and_deps_valid(instance)) { SOKOL_ASSERT(instance->sp_anim_state); spAnimationState_clearTracks(instance->sp_anim_state); } } SOKOL_API_IMPL void sspine_clear_animation_track(sspine_instance instance_id, int track_index) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (_sspine_instance_and_deps_valid(instance)) { SOKOL_ASSERT(instance->sp_anim_state); spAnimationState_clearTrack(instance->sp_anim_state, track_index); } } SOKOL_API_IMPL void sspine_set_animation(sspine_instance instance_id, sspine_anim anim, int track_index, bool loop) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spAnimation* sp_anim = _sspine_lookup_instance_anim(instance_id.id, anim.skeleton_id, anim.index); if (sp_anim) { // NOTE: at this point, instance is guaranteed to be valid _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); SOKOL_ASSERT(instance); spAnimationState_setAnimation(instance->sp_anim_state, track_index, sp_anim, loop?1:0); } } SOKOL_API_IMPL void sspine_add_animation(sspine_instance instance_id, sspine_anim anim, int track_index, bool loop, float delay) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spAnimation* sp_anim = _sspine_lookup_instance_anim(instance_id.id, anim.skeleton_id, anim.index); if (sp_anim) { // NOTE: at this point, instance is guaranteed to be valid _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); SOKOL_ASSERT(instance); SOKOL_ASSERT(instance->sp_anim_state); spAnimationState_addAnimation(instance->sp_anim_state, track_index, sp_anim, loop?1:0, delay); } } SOKOL_API_IMPL void sspine_set_empty_animation(sspine_instance instance_id, int track_index, float mix_duration) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (_sspine_instance_and_deps_valid(instance)) { SOKOL_ASSERT(instance->sp_anim_state); spAnimationState_setEmptyAnimation(instance->sp_anim_state, track_index, mix_duration); } } SOKOL_API_IMPL void sspine_add_empty_animation(sspine_instance instance_id, int track_index, float mix_duration, float delay) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (_sspine_instance_and_deps_valid(instance)) { SOKOL_ASSERT(instance->sp_anim_state); spAnimationState_addEmptyAnimation(instance->sp_anim_state, track_index, mix_duration, delay); } } SOKOL_API_IMPL int sspine_num_bones(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return skeleton->sp_skel_data->bonesCount; } return 0; } SOKOL_API_IMPL sspine_bone sspine_bone_by_name(sspine_skeleton skeleton_id, const char* name) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(name); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); spBoneData* sp_bone_data = spSkeletonData_findBone(skeleton->sp_skel_data, name); if (sp_bone_data) { return _sspine_bone(skeleton_id.id, sp_bone_data->index); } } return _sspine_bone(0, 0); } SOKOL_API_IMPL sspine_bone sspine_bone_by_index(sspine_skeleton skeleton_id, int index) { return _sspine_bone(skeleton_id.id, index); } SOKOL_API_IMPL bool sspine_bone_valid(sspine_bone bone) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(bone.skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return (bone.index >= 0) && (bone.index < skeleton->sp_skel_data->bonesCount); } return false; } SOKOL_API_IMPL bool sspine_bone_equal(sspine_bone first, sspine_bone second) { return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); } SOKOL_API_IMPL sspine_bone_info sspine_get_bone_info(sspine_bone bone) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_bone_info res; _sspine_clear(&res, sizeof(res)); const spBoneData* sp_bone_data = _sspine_lookup_bone_data(bone.skeleton_id, bone.index); if (sp_bone_data) { SOKOL_ASSERT(sp_bone_data->index == bone.index); SOKOL_ASSERT(sp_bone_data->name); res.valid = true; res.index = sp_bone_data->index; if (sp_bone_data->parent) { res.parent_bone = _sspine_bone(bone.skeleton_id, sp_bone_data->parent->index); } res.length = sp_bone_data->length; res.pose.position.x = sp_bone_data->x; res.pose.position.y = sp_bone_data->y; res.pose.rotation = sp_bone_data->rotation; res.pose.scale.x = sp_bone_data->scaleX; res.pose.scale.y = sp_bone_data->scaleY; res.pose.shear.x = sp_bone_data->shearX; res.pose.shear.y = sp_bone_data->shearY; res.color.r = sp_bone_data->color.r; res.color.g = sp_bone_data->color.g; res.color.b = sp_bone_data->color.b; res.color.a = sp_bone_data->color.a; res.name = _sspine_string(sp_bone_data->name); if (res.name.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } } return res; } SOKOL_API_IMPL void sspine_set_bone_transform(sspine_instance instance_id, sspine_bone bone, const sspine_bone_transform* transform) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(transform); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { sp_bone->x = transform->position.x; sp_bone->y = transform->position.y; sp_bone->rotation = transform->rotation; sp_bone->scaleX = transform->scale.x; sp_bone->scaleY = transform->scale.y; sp_bone->shearX = transform->shear.x; sp_bone->shearY = transform->shear.y; } } SOKOL_API_IMPL void sspine_set_bone_position(sspine_instance instance_id, sspine_bone bone, sspine_vec2 position) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { sp_bone->x = position.x; sp_bone->y = position.y; } } SOKOL_API_IMPL void sspine_set_bone_rotation(sspine_instance instance_id, sspine_bone bone, float rotation) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { sp_bone->rotation = rotation; } } SOKOL_API_IMPL void sspine_set_bone_scale(sspine_instance instance_id, sspine_bone bone, sspine_vec2 scale) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { sp_bone->scaleX = scale.x; sp_bone->scaleY = scale.y; } } SOKOL_API_IMPL void sspine_set_bone_shear(sspine_instance instance_id, sspine_bone bone, sspine_vec2 shear) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { sp_bone->shearX = shear.x; sp_bone->shearY = shear.y; } } SOKOL_API_IMPL sspine_bone_transform sspine_get_bone_transform(sspine_instance instance_id, sspine_bone bone) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_bone_transform res; _sspine_clear(&res, sizeof(res)); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { res.position.x = sp_bone->x; res.position.y = sp_bone->y; res.rotation = sp_bone->rotation; res.scale.x = sp_bone->scaleX; res.scale.y = sp_bone->scaleY; res.shear.x = sp_bone->shearX; res.shear.y = sp_bone->shearY; } return res; } SOKOL_API_IMPL sspine_vec2 sspine_get_bone_position(sspine_instance instance_id, sspine_bone bone) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_vec2 res; _sspine_clear(&res, sizeof(res)); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { res.x = sp_bone->x; res.y = sp_bone->y; } return res; } SOKOL_API_IMPL float sspine_get_bone_rotation(sspine_instance instance_id, sspine_bone bone) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { return sp_bone->rotation; } else { return 0.0f; } } SOKOL_API_IMPL sspine_vec2 sspine_get_bone_scale(sspine_instance instance_id, sspine_bone bone) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_vec2 res; _sspine_clear(&res, sizeof(res)); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { res.x = sp_bone->scaleX; res.y = sp_bone->scaleY; } return res; } SOKOL_API_IMPL sspine_vec2 sspine_get_bone_shear(sspine_instance instance_id, sspine_bone bone) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_vec2 res; _sspine_clear(&res, sizeof(res)); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { res.x = sp_bone->shearX; res.y = sp_bone->shearY; } return res; } SOKOL_API_IMPL sspine_vec2 sspine_get_bone_world_position(sspine_instance instance_id, sspine_bone bone) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_vec2 res; _sspine_clear(&res, sizeof(res)); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { res.x = sp_bone->worldX; res.y = sp_bone->worldY; } return res; } SOKOL_API_IMPL sspine_vec2 sspine_bone_local_to_world(sspine_instance instance_id, sspine_bone bone, sspine_vec2 local_pos) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_vec2 res; _sspine_clear(&res, sizeof(res)); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { spBone_localToWorld(sp_bone, local_pos.x, local_pos.y, &res.x, &res.y); } return res; } SOKOL_API_IMPL sspine_vec2 sspine_bone_world_to_local(sspine_instance instance_id, sspine_bone bone, sspine_vec2 world_pos) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_vec2 res; _sspine_clear(&res, sizeof(res)); spBone* sp_bone = _sspine_lookup_bone(instance_id.id, bone.skeleton_id, bone.index); if (sp_bone) { spBone_worldToLocal(sp_bone, world_pos.x, world_pos.y, &res.x, &res.y); } return res; } SOKOL_API_IMPL int sspine_num_slots(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return skeleton->sp_skel_data->slotsCount; } return 0; } SOKOL_API_IMPL sspine_slot sspine_slot_by_name(sspine_skeleton skeleton_id, const char* name) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(name); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); spSlotData* sp_slot_data = spSkeletonData_findSlot(skeleton->sp_skel_data, name); if (sp_slot_data) { return _sspine_slot(skeleton_id.id, sp_slot_data->index); } } return _sspine_slot(0, 0); } SOKOL_API_IMPL sspine_slot sspine_slot_by_index(sspine_skeleton skeleton_id, int index) { return _sspine_slot(skeleton_id.id, index); } SOKOL_API_IMPL bool sspine_slot_valid(sspine_slot slot) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(slot.skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return (slot.index >= 0) && (slot.index < skeleton->sp_skel_data->slotsCount); } return false; } SOKOL_API_IMPL bool sspine_slot_equal(sspine_slot first, sspine_slot second) { return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); } SOKOL_API_IMPL sspine_slot_info sspine_get_slot_info(sspine_slot slot) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_slot_info res; _sspine_clear(&res, sizeof(res)); const spSlotData* sp_slot_data = _sspine_lookup_slot_data(slot.skeleton_id, slot.index); if (sp_slot_data) { SOKOL_ASSERT(sp_slot_data->index == slot.index); SOKOL_ASSERT(sp_slot_data->name); SOKOL_ASSERT(sp_slot_data->boneData); res.valid = true; res.index = sp_slot_data->index; res.bone = _sspine_bone(slot.skeleton_id, sp_slot_data->boneData->index); res.color.r = sp_slot_data->color.r; res.color.g = sp_slot_data->color.g; res.color.b = sp_slot_data->color.b; res.color.a = sp_slot_data->color.a; res.attachment_name = _sspine_string(sp_slot_data->attachmentName); if (res.attachment_name.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } res.name = _sspine_string(sp_slot_data->name); if (res.name.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } } return res; } SOKOL_API_IMPL void sspine_set_slot_color(sspine_instance instance_id, sspine_slot slot, sspine_color color) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spSlot* sp_slot = _sspine_lookup_slot(instance_id.id, slot.skeleton_id, slot.index); if (sp_slot) { sp_slot->color.r = color.r; sp_slot->color.g = color.g; sp_slot->color.b = color.b; sp_slot->color.a = color.a; } } SOKOL_API_IMPL sspine_color sspine_get_slot_color(sspine_instance instance_id, sspine_slot slot) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_color color; _sspine_clear(&color, sizeof(color)); spSlot* sp_slot = _sspine_lookup_slot(instance_id.id, slot.skeleton_id, slot.index); if (sp_slot) { color.r = sp_slot->color.r; color.g = sp_slot->color.g; color.b = sp_slot->color.b; color.a = sp_slot->color.a; } return color; } SOKOL_API_IMPL int sspine_num_events(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return skeleton->sp_skel_data->eventsCount; } return 0; } SOKOL_API_IMPL sspine_event sspine_event_by_name(sspine_skeleton skeleton_id, const char* name) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(name); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); SOKOL_ASSERT(skeleton->sp_skel_data->events); // spEventData has no embedded index, so we need to loop over the events for (int i = 0; i < skeleton->sp_skel_data->eventsCount; i++) { SOKOL_ASSERT(skeleton->sp_skel_data->events[i]); SOKOL_ASSERT(skeleton->sp_skel_data->events[i]->name); if (0 == strcmp(skeleton->sp_skel_data->events[i]->name, name)) { return _sspine_event(skeleton_id.id, i); } } } return _sspine_event(0, 0); } SOKOL_API_IMPL sspine_event sspine_event_by_index(sspine_skeleton skeleton_id, int index) { return _sspine_event(skeleton_id.id, index); } SOKOL_API_IMPL bool sspine_event_valid(sspine_event event) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(event.skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return (event.index >= 0) && (event.index < skeleton->sp_skel_data->eventsCount); } return false; } SOKOL_API_IMPL bool sspine_event_equal(sspine_event first, sspine_event second) { return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); } SOKOL_API_IMPL sspine_event_info sspine_get_event_info(sspine_event event) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_event_info res; _sspine_clear(&res, sizeof(res)); const spEventData* sp_event_data = _sspine_lookup_event_data(event.skeleton_id, event.index); if (sp_event_data) { res.valid = true; res.index = event.index; res.int_value = sp_event_data->intValue; res.float_value = sp_event_data->floatValue; res.volume = sp_event_data->volume; res.balance = sp_event_data->balance; res.name = _sspine_string(sp_event_data->name); if (res.name.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } res.string_value = _sspine_string(sp_event_data->stringValue); if (res.string_value.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } res.audio_path = _sspine_string(sp_event_data->audioPath); if (res.audio_path.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } } return res; } SOKOL_API_IMPL int sspine_num_iktargets(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return skeleton->sp_skel_data->ikConstraintsCount; } return 0; } SOKOL_API_IMPL sspine_iktarget sspine_iktarget_by_name(sspine_skeleton skeleton_id, const char* name) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(name); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); SOKOL_ASSERT(skeleton->sp_skel_data->ikConstraints); // spIkConstraintData has no embedded index, so we need to loop over the events for (int i = 0; i < skeleton->sp_skel_data->ikConstraintsCount; i++) { SOKOL_ASSERT(skeleton->sp_skel_data->ikConstraints[i]); SOKOL_ASSERT(skeleton->sp_skel_data->ikConstraints[i]->name); if (0 == strcmp(skeleton->sp_skel_data->ikConstraints[i]->name, name)) { return _sspine_iktarget(skeleton_id.id, i); } } } return _sspine_iktarget(0, 0); } SOKOL_API_IMPL sspine_iktarget sspine_iktarget_by_index(sspine_skeleton skeleton_id, int index) { return _sspine_iktarget(skeleton_id.id, index); } SOKOL_API_IMPL bool sspine_iktarget_valid(sspine_iktarget iktarget) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(iktarget.skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return (iktarget.index >= 0) && (iktarget.index < skeleton->sp_skel_data->ikConstraintsCount); } return false; } SOKOL_API_IMPL bool sspine_iktarget_equal(sspine_iktarget first, sspine_iktarget second) { return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); } SOKOL_API_IMPL sspine_iktarget_info sspine_get_iktarget_info(sspine_iktarget iktarget) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_iktarget_info res; _sspine_clear(&res, sizeof(res)); const spIkConstraintData* ik_data = _sspine_lookup_ikconstraint_data(iktarget.skeleton_id, iktarget.index); if (ik_data) { res.valid = true; res.index = iktarget.index; res.target_bone = _sspine_bone(iktarget.skeleton_id, ik_data->target->index); res.name = _sspine_string(ik_data->name); if (res.name.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } } return res; } SOKOL_API_IMPL void sspine_set_iktarget_world_pos(sspine_instance instance_id, sspine_iktarget iktarget, sspine_vec2 world_pos) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); spIkConstraint* ik_data = _sspine_lookup_ikconstraint(instance_id.id, iktarget.skeleton_id, iktarget.index); if (ik_data) { spBone* bone = ik_data->target; spBone* parent_bone = bone->parent; if (parent_bone) { spBone_worldToLocal(parent_bone, world_pos.x, world_pos.y, &bone->x, &bone->y); } } } SOKOL_API_IMPL int sspine_num_skins(sspine_skeleton skeleton_id) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return skeleton->sp_skel_data->skinsCount; } return 0; } SOKOL_API_IMPL sspine_skin sspine_skin_by_name(sspine_skeleton skeleton_id, const char* name) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); SOKOL_ASSERT(name); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skeleton_id.id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); SOKOL_ASSERT(skeleton->sp_skel_data->skins); // spSkin has no embedded index, so we need to loop over the skins for (int i = 0; i < skeleton->sp_skel_data->skinsCount; i++) { SOKOL_ASSERT(skeleton->sp_skel_data->skins[i]); SOKOL_ASSERT(skeleton->sp_skel_data->skins[i]->name); if (0 == strcmp(skeleton->sp_skel_data->skins[i]->name, name)) { return _sspine_skin(skeleton_id.id, i); } } } return _sspine_skin(0, 0); } SOKOL_API_IMPL sspine_skin sspine_skin_by_index(sspine_skeleton skeleton_id, int index) { return _sspine_skin(skeleton_id.id, index); } SOKOL_API_IMPL bool sspine_skin_valid(sspine_skin skin) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_skeleton_t* skeleton = _sspine_lookup_skeleton(skin.skeleton_id); if (_sspine_skeleton_and_deps_valid(skeleton)) { SOKOL_ASSERT(skeleton->sp_skel_data); return (skin.index >= 0) && (skin.index < skeleton->sp_skel_data->skinsCount); } return false; } SOKOL_API_IMPL bool sspine_skin_equal(sspine_skin first, sspine_skin second) { return (first.skeleton_id == second.skeleton_id) && (first.index == second.index); } SOKOL_API_IMPL sspine_skin_info sspine_get_skin_info(sspine_skin skin) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); sspine_skin_info res; _sspine_clear(&res, sizeof(res)); const spSkin* sp_skin = _sspine_lookup_skin(skin.skeleton_id, skin.index); if (sp_skin) { res.valid = true; res.index = skin.index; res.name = _sspine_string(sp_skin->name); if (res.name.truncated) { _SSPINE_WARN(STRING_TRUNCATED); } } return res; } SOKOL_SPINE_API_DECL void sspine_set_skin(sspine_instance instance_id, sspine_skin skin) { SOKOL_ASSERT(_SSPINE_INIT_COOKIE == _sspine.init_cookie); _sspine_instance_t* instance = _sspine_lookup_instance(instance_id.id); if (_sspine_instance_and_deps_valid(instance) && (instance->skel.id == skin.skeleton_id)) { SOKOL_ASSERT(instance->sp_skel); SOKOL_ASSERT(instance->sp_anim_state); // clear any currently set skinset instance->skinset.id = SSPINE_INVALID_ID; instance->skinset.ptr = 0; spSkin* sp_skin = _sspine_lookup_skin(skin.skeleton_id, skin.index); if (sp_skin) { spSkeleton_setSkin(instance->sp_skel, 0); spSkeleton_setSkin(instance->sp_skel, sp_skin); spSkeleton_setSlotsToSetupPose(instance->sp_skel); spAnimationState_apply(instance->sp_anim_state, instance->sp_skel); } } } #endif // SOKOL_SPINE_IMPL #endif // SOKOL_SPINE_INCLUDED