Преглед на файлове

[glfw] Rewrite using new SkeletonRenderer and switch to spine-cpp.

Mario Zechner преди 1 година
родител
ревизия
9db5f90ccb

+ 1 - 0
spine-cpp/spine-cpp-lite/spine-cpp-lite.cpp

@@ -81,6 +81,7 @@ public:
 	}
 
 	void compress() {
+        if (blocks.size() == 1) return;
 		int totalSize = 0;
 		for (int i = 0, n = blocks.size(); i < n; i++) {
 			totalSize += blocks[i].size;

+ 110 - 0
spine-cpp/spine-cpp/include/spine/BlockAllocator.h

@@ -0,0 +1,110 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated July 28, 2023. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2023, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software or
+ * otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
+ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+#ifndef Spine_BlockAllocator_h
+#define Spine_BlockAllocator_h
+
+#include <cstdint>
+#include <spine/SpineObject.h>
+#include <spine/Extension.h>
+#include <spine/MathUtil.h>
+#include <spine/Vector.h>
+
+namespace spine {
+    struct Block {
+        int size;
+        int allocated;
+        uint8_t *memory;
+
+        int free() {
+            return size - allocated;
+        }
+
+        bool canFit(int numBytes) {
+            return free() >= numBytes;
+        }
+
+        uint8_t *allocate(int numBytes) {
+            uint8_t *ptr = memory + allocated;
+            allocated += numBytes;
+            return ptr;
+        }
+    };
+
+    class BlockAllocator : public SpineObject {
+        int initialBlockSize;
+        Vector <Block> blocks;
+
+    public:
+        BlockAllocator(int initialBlockSize) : initialBlockSize(initialBlockSize) {
+            blocks.add(newBlock(initialBlockSize));
+        }
+
+        ~BlockAllocator() {
+            for (int i = 0, n = (int) blocks.size(); i < n; i++) {
+                SpineExtension::free(blocks[i].memory, __FILE__, __LINE__);
+            }
+        }
+
+        template<typename T>
+        T *allocate(size_t num) {
+            return (T *) _allocate((int) (sizeof(T) * num));
+        }
+
+        void compress() {
+            if (blocks.size() == 1) return;
+            int totalSize = 0;
+            for (int i = 0, n = blocks.size(); i < n; i++) {
+                totalSize += blocks[i].size;
+                SpineExtension::free(blocks[i].memory, __FILE__, __LINE__);
+            }
+            blocks.clear();
+            blocks.add(newBlock(totalSize));
+        }
+
+    private:
+        void *_allocate(int numBytes) {
+            // 16-byte align allocations
+            int alignedNumBytes = numBytes + (numBytes % 16 != 0 ? 16 - (numBytes % 16) : 0);
+            Block *block = &blocks[blocks.size() - 1];
+            if (!block->canFit(alignedNumBytes)) {
+                blocks.add(newBlock(MathUtil::max(initialBlockSize, alignedNumBytes)));
+                block = &blocks[blocks.size() - 1];
+            }
+            return block->allocate(alignedNumBytes);
+        }
+
+        Block newBlock(int numBytes) {
+            Block block = {MathUtil::max(initialBlockSize, numBytes), 0, nullptr};
+            block.memory = SpineExtension::alloc<uint8_t>(block.size, __FILE__, __LINE__);
+            return block;
+        }
+    };
+}
+
+#endif

+ 68 - 0
spine-cpp/spine-cpp/include/spine/SkeletonRenderer.h

@@ -0,0 +1,68 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated July 28, 2023. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2023, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software or
+ * otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
+ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+#ifndef Spine_SkeletonRenderer_h
+#define Spine_SkeletonRenderer_h
+
+#include <spine/BlockAllocator.h>
+#include <spine/BlendMode.h>
+#include <spine/SkeletonClipping.h>
+
+namespace spine {
+    class Skeleton;
+
+    struct SP_API RenderCommand {
+        float *positions;
+        float *uvs;
+        int32_t *colors;
+        int32_t *darkColors;
+        int32_t numVertices;
+        uint16_t *indices;
+        int32_t numIndices;
+        BlendMode blendMode;
+        void *texture;
+        RenderCommand *next;
+    };
+
+    class SP_API SkeletonRenderer: public SpineObject {
+    public:
+        explicit SkeletonRenderer();
+
+        ~SkeletonRenderer();
+
+        RenderCommand *render(Skeleton &skeleton);
+    private:
+        BlockAllocator _allocator;
+        Vector<float> _worldVertices;
+        Vector<unsigned short> _quadIndices;
+        SkeletonClipping _clipping;
+        Vector<RenderCommand *> _renderCommands;
+    };
+}
+
+#endif

+ 1 - 0
spine-cpp/spine-cpp/include/spine/spine.h

@@ -93,6 +93,7 @@
 #include <spine/SkeletonClipping.h>
 #include <spine/SkeletonData.h>
 #include <spine/SkeletonJson.h>
+#include <spine/SkeletonRenderer.h>
 #include <spine/Skin.h>
 #include <spine/Slot.h>
 #include <spine/SlotData.h>

+ 248 - 0
spine-cpp/spine-cpp/src/spine/SkeletonRenderer.cpp

@@ -0,0 +1,248 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated July 28, 2023. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2023, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software or
+ * otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
+ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+#include <spine/SkeletonRenderer.h>
+#include <spine/Skeleton.h>
+#include <spine/Slot.h>
+#include <spine/SlotData.h>
+#include <spine/RegionAttachment.h>
+#include <spine/Atlas.h>
+#include <spine/MeshAttachment.h>
+#include <spine/ClippingAttachment.h>
+#include <spine/Bone.h>
+
+using namespace spine;
+
+SkeletonRenderer::SkeletonRenderer(): _allocator(4096), _worldVertices(), _quadIndices(), _clipping(), _renderCommands() {
+    _quadIndices.add(0);
+    _quadIndices.add(1);
+    _quadIndices.add(2);
+    _quadIndices.add(2);
+    _quadIndices.add(3);
+    _quadIndices.add(0);
+}
+
+SkeletonRenderer::~SkeletonRenderer() {
+}
+
+static RenderCommand *createRenderCommand(BlockAllocator &allocator, int numVertices, int32_t numIndices, BlendMode blendMode, void *texture) {
+    RenderCommand *cmd = allocator.allocate<RenderCommand>(1);
+    cmd->positions = allocator.allocate<float>(numVertices << 1);
+    cmd->uvs = allocator.allocate<float>(numVertices << 1);
+    cmd->colors = allocator.allocate<int32_t>(numVertices);
+    cmd->darkColors = allocator.allocate<int32_t>(numVertices);
+    cmd->numVertices = numVertices;
+    cmd->indices = allocator.allocate<uint16_t>(numIndices);
+    cmd->numIndices = numIndices;
+    cmd->blendMode = blendMode;
+    cmd->texture = texture;
+    cmd->next = nullptr;
+    return cmd;
+}
+
+static RenderCommand *batchSubCommands(BlockAllocator &allocator, Vector<RenderCommand *> &commands, int first, int last, int numVertices, int numIndices) {
+    RenderCommand *batched = createRenderCommand(allocator, numVertices, numIndices, commands[first]->blendMode, commands[first]->texture);
+    float *positions = batched->positions;
+    float *uvs = batched->uvs;
+    int32_t *colors = batched->colors;
+    int32_t *darkColors = batched->darkColors;
+    uint16_t *indices = batched->indices;
+    int indicesOffset = 0;
+    for (int i = first; i <= last; i++) {
+        RenderCommand *cmd = commands[i];
+        memcpy(positions, cmd->positions, sizeof(float) * 2 * cmd->numVertices);
+        memcpy(uvs, cmd->uvs, sizeof(float) * 2 * cmd->numVertices);
+        memcpy(colors, cmd->colors, sizeof(int32_t) * cmd->numVertices);
+        memcpy(darkColors, cmd->darkColors, sizeof(int32_t) * cmd->numVertices);
+        for (int ii = 0; ii < cmd->numIndices; ii++)
+            indices[ii] = cmd->indices[ii] + indicesOffset;
+        indicesOffset += cmd->numVertices;
+        positions += 2 * cmd->numVertices;
+        uvs += 2 * cmd->numVertices;
+        colors += cmd->numVertices;
+        darkColors += cmd->numVertices;
+        indices += cmd->numIndices;
+    }
+    return batched;
+}
+
+static RenderCommand *batchCommands(BlockAllocator &allocator, Vector<RenderCommand *> &commands) {
+    if (commands.size() == 0) return nullptr;
+
+    RenderCommand *root = nullptr;
+    RenderCommand *last = nullptr;
+
+    RenderCommand *first = commands[0];
+    int startIndex = 0;
+    int i = 1;
+    int numVertices = first->numVertices;
+    int numIndices = first->numIndices;
+    while (i <= (int) commands.size()) {
+        RenderCommand *cmd = i < (int) commands.size() ? commands[i] : nullptr;
+
+        if (cmd && cmd->numVertices == 0 && cmd->numIndices == 0) {
+            i++;
+            continue;
+        }
+
+        if (cmd != nullptr && cmd->texture == first->texture &&
+            cmd->blendMode == first->blendMode &&
+            cmd->colors[0] == first->colors[0] &&
+            cmd->darkColors[0] == first->darkColors[0] &&
+            numIndices + cmd->numIndices < 0xffff) {
+            numVertices += cmd->numVertices;
+            numIndices += cmd->numIndices;
+        } else {
+            RenderCommand *batched = batchSubCommands(allocator, commands, startIndex, i - 1, numVertices, numIndices);
+            if (!last) {
+                root = last = batched;
+            } else {
+                last->next = batched;
+                last = batched;
+            }
+            if (i == (int) commands.size()) break;
+            first = commands[i];
+            startIndex = i;
+            numVertices = first->numVertices;
+            numIndices = first->numIndices;
+        }
+        i++;
+    }
+    return root;
+}
+
+
+RenderCommand *SkeletonRenderer::render(Skeleton &skeleton) {
+    _allocator.compress();
+    _renderCommands.clear();
+
+    SkeletonClipping &clipper = _clipping;
+
+    for (unsigned i = 0; i < skeleton.getSlots().size(); ++i) {
+        Slot &slot = *skeleton.getDrawOrder()[i];
+        Attachment *attachment = slot.getAttachment();
+        if (!attachment) {
+            clipper.clipEnd(slot);
+            continue;
+        }
+
+        // Early out if the slot color is 0 or the bone is not active
+        if (slot.getColor().a == 0 || !slot.getBone().isActive()) {
+            clipper.clipEnd(slot);
+            continue;
+        }
+
+        Vector<float> *worldVertices = &_worldVertices;
+        Vector<unsigned short> *quadIndices = &_quadIndices;
+        Vector<float> *vertices = worldVertices;
+        int32_t verticesCount;
+        Vector<float> *uvs;
+        Vector<unsigned short> *indices;
+        int32_t indicesCount;
+        Color *attachmentColor;
+        void *texture;
+
+        if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) {
+            RegionAttachment *regionAttachment = (RegionAttachment *) attachment;
+            attachmentColor = &regionAttachment->getColor();
+
+            // Early out if the slot color is 0
+            if (attachmentColor->a == 0) {
+                clipper.clipEnd(slot);
+                continue;
+            }
+
+            worldVertices->setSize(8, 0);
+            regionAttachment->computeWorldVertices(slot, *worldVertices, 0, 2);
+            verticesCount = 4;
+            uvs = &regionAttachment->getUVs();
+            indices = quadIndices;
+            indicesCount = 6;
+            texture = regionAttachment->getRegion()->rendererObject;
+
+        } else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) {
+            MeshAttachment *mesh = (MeshAttachment *) attachment;
+            attachmentColor = &mesh->getColor();
+
+            // Early out if the slot color is 0
+            if (attachmentColor->a == 0) {
+                clipper.clipEnd(slot);
+                continue;
+            }
+
+            worldVertices->setSize(mesh->getWorldVerticesLength(), 0);
+            mesh->computeWorldVertices(slot, 0, mesh->getWorldVerticesLength(), worldVertices->buffer(), 0, 2);
+            verticesCount = (int32_t) (mesh->getWorldVerticesLength() >> 1);
+            uvs = &mesh->getUVs();
+            indices = &mesh->getTriangles();
+            indicesCount = (int32_t) indices->size();
+            texture = mesh->getRegion()->rendererObject;
+
+        } else if (attachment->getRTTI().isExactly(ClippingAttachment::rtti)) {
+            ClippingAttachment *clip = (ClippingAttachment *) slot.getAttachment();
+            clipper.clipStart(slot, clip);
+            continue;
+        } else
+            continue;
+
+        uint8_t r = static_cast<uint8_t>(skeleton.getColor().r * slot.getColor().r * attachmentColor->r * 255);
+        uint8_t g = static_cast<uint8_t>(skeleton.getColor().g * slot.getColor().g * attachmentColor->g * 255);
+        uint8_t b = static_cast<uint8_t>(skeleton.getColor().b * slot.getColor().b * attachmentColor->b * 255);
+        uint8_t a = static_cast<uint8_t>(skeleton.getColor().a * slot.getColor().a * attachmentColor->a * 255);
+        uint32_t color = (a << 24) | (r << 16) | (g << 8) | b;
+        uint32_t darkColor = 0xff000000;
+        if (slot.hasDarkColor()) {
+            Color &slotDarkColor = slot.getDarkColor();
+            darkColor = 0xff000000 | (static_cast<uint8_t>(slotDarkColor.r * 255) << 16) | (static_cast<uint8_t>(slotDarkColor.g * 255) << 8) | static_cast<uint8_t>(slotDarkColor.b * 255);
+        }
+
+        if (clipper.isClipping()) {
+            clipper.clipTriangles(*worldVertices, *indices, *uvs, 2);
+            vertices = &clipper.getClippedVertices();
+            verticesCount = (int32_t) (clipper.getClippedVertices().size() >> 1);
+            uvs = &clipper.getClippedUVs();
+            indices = &clipper.getClippedTriangles();
+            indicesCount = (int32_t) (clipper.getClippedTriangles().size());
+        }
+
+        RenderCommand *cmd = createRenderCommand(_allocator, verticesCount, indicesCount, slot.getData().getBlendMode(), texture);
+        _renderCommands.add(cmd);
+        memcpy(cmd->positions, vertices->buffer(), (verticesCount << 1) * sizeof(float));
+        memcpy(cmd->uvs, uvs->buffer(), (verticesCount << 1) * sizeof(float));
+        for (int ii = 0; ii < verticesCount; ii++) {
+            cmd->colors[ii] = color;
+            cmd->darkColors[ii] = darkColor;
+        }
+        memcpy(cmd->indices, indices->buffer(), indices->size() * sizeof(uint16_t));
+        clipper.clipEnd(slot);
+    }
+    clipper.clipEnd();
+
+    return batchCommands(_allocator, _renderCommands);
+}

+ 2 - 2
spine-glfw/CMakeLists.txt

@@ -4,7 +4,7 @@ project(spine-glfw)
 # Default flags
 include(${CMAKE_SOURCE_DIR}/../flags.cmake)
 
-# Add spine-cpp and spine-cpp-lite
+# Add spine-cpp
 add_subdirectory(${CMAKE_SOURCE_DIR}/../spine-cpp ${CMAKE_BINARY_DIR}/spine-cpp-build)
 
 include(FetchContent)
@@ -41,7 +41,7 @@ find_package(OpenGL REQUIRED)
 # spine-glfw library
 add_library(spine-glfw STATIC src/spine-glfw.cpp src/spine-glfw.h src/stb_image.h)
 target_link_libraries(spine-glfw PRIVATE glbinding::glbinding)
-target_link_libraries(spine-glfw LINK_PUBLIC spine-cpp spine-cpp-lite)
+target_link_libraries(spine-glfw LINK_PUBLIC spine-cpp)
 
 # Example
 add_executable(spine-glfw-example example/main.cpp)

+ 24 - 19
spine-glfw/example/main.cpp

@@ -5,6 +5,8 @@
 #include <iostream>
 #include <spine-glfw.h>
 
+using namespace spine;
+
 int width = 800, height = 600;
 
 GLFWwindow* init_glfw() {
@@ -32,22 +34,26 @@ int main() {
     if (!window) return -1;
 
     // We use a y-down coordinate system, see renderer_set_viewport_size()
-    spine_bone_set_is_y_down(true);
+    Bone::setYDown(true);
 
     // Load the atlas and the skeleton data
-    atlas_t *atlas = atlas_load("data/spineboy-pma.atlas");
-    spine_skeleton_data skeleton_data = skeleton_data_load("data/spineboy-pro.json", atlas);
+    GlTextureLoader textureLoader;
+    Atlas *atlas = new Atlas("data/spineboy-pma.atlas", &textureLoader);
+    SkeletonJson json(atlas);
+    SkeletonData *skeletonData = json.readSkeletonDataFile("data/spineboy-pro.json");
 
-    // Create a skeleton drawable from the data, set the skeleton's position to the bottom center of
+    // Create a skeleton from the data, set the skeleton's position to the bottom center of
     // the screen and scale it to make it smaller.
-    spine_skeleton_drawable drawable = spine_skeleton_drawable_create(skeleton_data);
-    spine_skeleton skeleton = spine_skeleton_drawable_get_skeleton(drawable);
-    spine_skeleton_set_position(skeleton, width / 2, height - 100);
-    spine_skeleton_set_scale(skeleton, 0.3, 0.3);
+    Skeleton skeleton(skeletonData);
+    skeleton.setPosition(width / 2, height - 100);
+    skeleton.setScaleX(0.3);
+    skeleton.setScaleY(0.3);
 
-    // Set the "portal" animation on track 0 of the animation state, looping
-    spine_animation_state animation_state = spine_skeleton_drawable_get_animation_state(drawable);
-    spine_animation_state_set_animation_by_name(animation_state, 0, "portal", true);
+    // Create an AnimationState to drive animations on the skeleton. Set the "portal" animation
+    // on track with index 0.
+    AnimationStateData animationStateData(skeletonData);
+    AnimationState animationState(&animationStateData);
+    animationState.setAnimation(0, "portal", true);
 
     // Create the renderer and set the viewport size to match the window size. This sets up a
     // pixel perfect orthogonal projection for 2D rendering.
@@ -63,20 +69,20 @@ int main() {
         lastTime = currTime;
 
         // Update and apply the animation state to the skeleton
-        spine_animation_state_update(animation_state, delta);
-        spine_animation_state_apply(animation_state, skeleton);
+        animationState.update(delta);
+        animationState.apply(skeleton);
 
         // Update the skeleton time (used for physics)
-        spine_skeleton_update(skeleton, delta);
+        skeleton.update(delta);
 
         // Calculate the new pose
-        spine_skeleton_update_world_transform(skeleton, SPINE_PHYSICS_UPDATE);
+        skeleton.updateWorldTransform(spine::Physics_Update);
 
         // Clear the screen
         gl::glClear(gl::GL_COLOR_BUFFER_BIT);
 
         // Render the skeleton in its current pose
-        renderer_draw(renderer, drawable, atlas);
+        renderer_draw(renderer, &skeleton, true);
 
         // Present the rendering results and poll for events
         glfwSwapBuffers(window);
@@ -85,9 +91,8 @@ int main() {
 
     // Dispose everything
     renderer_dispose(renderer);
-    spine_skeleton_drawable_dispose(drawable);
-    spine_skeleton_data_dispose(skeleton_data);
-    atlas_dispose(atlas);
+    delete skeletonData;
+    delete atlas;
 
     // Kill the window and GLFW
     glfwTerminate();

+ 33 - 112
spine-glfw/src/spine-glfw.cpp

@@ -1,10 +1,16 @@
 #include "spine-glfw.h"
-#include <stdio.h>
+#include <cstdio>
 #include <glbinding/gl/gl.h>
 #define STB_IMAGE_IMPLEMENTATION
 #include "stb_image.h"
 
 using namespace gl;
+using namespace spine;
+
+/// Set the default extension used for memory allocations and file I/O
+SpineExtension *spine::getDefaultExtension() {
+    return new spine::DefaultSpineExtension();
+}
 
 /// A blend mode, see https://en.esotericsoftware.com/spine-slots#Blending
 /// Encodes the OpenGL source and destination blend function for both premultiplied and
@@ -50,7 +56,7 @@ mesh_t *mesh_create() {
 
     glBindVertexArray(0);
 
-    mesh_t *mesh = (mesh_t*)malloc(sizeof(mesh_t));
+    auto *mesh = (mesh_t*)malloc(sizeof(mesh_t));
     mesh->vao = vao;
     mesh->vbo = vbo;
     mesh->num_vertices = 0;
@@ -63,10 +69,10 @@ void mesh_update(mesh_t *mesh, vertex_t *vertices, int num_vertices, uint16_t *i
     glBindVertexArray(mesh->vao);
 
     glBindBuffer(GL_ARRAY_BUFFER, mesh->vbo);
-    glBufferData(GL_ARRAY_BUFFER, num_vertices * sizeof(vertex_t), vertices, GL_STATIC_DRAW);
+    glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(num_vertices * sizeof(vertex_t)), vertices, GL_STATIC_DRAW);
     mesh->num_vertices = num_vertices;
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->ibo);
-    glBufferData(GL_ELEMENT_ARRAY_BUFFER, num_indices * sizeof(uint16_t), indices, GL_STATIC_DRAW);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(num_indices * sizeof(uint16_t)), indices, GL_STATIC_DRAW);
     mesh->num_indices = num_indices;
 
     glBindVertexArray(0);
@@ -74,7 +80,7 @@ void mesh_update(mesh_t *mesh, vertex_t *vertices, int num_vertices, uint16_t *i
 
 void mesh_draw(mesh_t *mesh) {
     glBindVertexArray(mesh->vao);
-    glDrawElements(GL_TRIANGLES, mesh->num_indices, GL_UNSIGNED_SHORT, 0);
+    glDrawElements(GL_TRIANGLES, mesh->num_indices, GL_UNSIGNED_SHORT, nullptr);
     glBindVertexArray(0);
 }
 
@@ -218,99 +224,12 @@ void matrix_ortho_projection(float *matrix, float width, float height) {
     matrix[15] = 1.0f;
 }
 
-const uint8_t *file_read(const char *path, int *length) {
-    uint8_t *data;
-    FILE *file = fopen(path, "rb");
-    if (!file) return 0;
-    fseek(file, 0, SEEK_END);
-    *length = (int) ftell(file);
-    fseek(file, 0, SEEK_SET);
-    data = (uint8_t*)(malloc(*length + 1));
-    fread(data, 1, *length, file);
-    fclose(file);
-    data[*length] = 0;
-    return data;
+void GlTextureLoader::load(spine::AtlasPage &page, const spine::String &path) {
+    page.texture = (void *)(uintptr_t)texture_load(path.buffer());
 }
 
-atlas_t *atlas_load(const char *file_path) {
-    int length = 0;
-    utf8 *atlas_data = (utf8*)file_read(file_path, &length);
-    if (!atlas_data) {
-        printf("Could not load atlas %s\n", file_path);
-        return nullptr;
-    }
-
-    spine_atlas spine_atlas = spine_atlas_load(atlas_data);
-    free(atlas_data);
-    if (!spine_atlas) {
-        printf("Could not load atlas %s\n", file_path);
-        return nullptr;
-    }
-    atlas_t *atlas = (atlas_t*)malloc(sizeof(atlas_t));
-    atlas->atlas = spine_atlas;
-    int num_textures = spine_atlas_get_num_image_paths(spine_atlas);
-    atlas->textures = (texture_t*)malloc(sizeof(texture_t) * num_textures);
-    memset(atlas->textures, 0, sizeof(texture_t) * num_textures);
-
-    char parent_dir[1024];
-    strncpy(parent_dir, file_path, sizeof(parent_dir));
-    char *last_slash = strrchr(parent_dir, '/');
-    if (last_slash) {
-        *(last_slash + 1) = '\0';
-    } else {
-        parent_dir[0] = '\0';
-    }
-
-    for (int i = 0; i < num_textures; i++) {
-        char *relative_path = spine_atlas_get_image_path(spine_atlas, i);
-        char full_path[1024];
-        snprintf(full_path, sizeof(full_path), "%s%s", parent_dir, relative_path);
-        texture_t texture = texture_load(full_path);
-        if (!texture) {
-            printf("Could not load atlas texture %s\n", full_path);
-            atlas_dispose(atlas);
-            return nullptr;
-        }
-        atlas->textures[i] = texture;
-    }
-
-    return atlas;
-}
-
-void atlas_dispose(atlas_t *atlas) {
-    for (int i = 0; i < spine_atlas_get_num_image_paths(atlas->atlas); i++) {
-        texture_dispose(atlas->textures[i]);
-    }
-    spine_atlas_dispose(atlas->atlas);
-    free(atlas->textures);
-    free(atlas);
-}
-
-spine_skeleton_data skeleton_data_load(const char *file_path, atlas_t *atlas) {
-    int length = 0;
-    uint8_t *data = (uint8_t*)file_read(file_path, &length);
-    if (!data) {
-        printf("Could not load skeleton data file %s\n", file_path);
-        return nullptr;
-    }
-
-    spine_skeleton_data_result result;
-    const char *ext = strrchr(file_path, '.');
-    if (ext && strcmp(ext, ".skel") == 0) {
-        result = spine_skeleton_data_load_binary(atlas->atlas, data, length);
-    } else {
-        result = spine_skeleton_data_load_json(atlas->atlas, (utf8*)data);
-    }
-    free(data);
-
-    if (spine_skeleton_data_result_get_error(result)) {
-        printf("Could not load skeleton data file %s:\n%s\n", file_path, spine_skeleton_data_result_get_error(result));
-        spine_skeleton_data_result_dispose(result);
-        return nullptr;
-    }
-    spine_skeleton_data skeleton_data = spine_skeleton_data_result_get_data(result);
-    spine_skeleton_data_result_dispose(result);
-    return skeleton_data;
+void GlTextureLoader::unload(void *texture) {
+    texture_dispose((texture_t)(uintptr_t)texture);
 }
 
 renderer_t *renderer_create() {
@@ -350,38 +269,39 @@ renderer_t *renderer_create() {
     )");
     if (!shader) return nullptr;
     mesh_t *mesh = mesh_create();
-    renderer_t *renderer = (renderer_t*)malloc(sizeof(renderer_t));
+    auto *renderer = (renderer_t*)malloc(sizeof(renderer_t));
     renderer->shader = shader;
     renderer->mesh = mesh;
     renderer->vertex_buffer_size = 0;
     renderer->vertex_buffer = nullptr;
+    renderer->renderer = new SkeletonRenderer();
     return renderer;
 }
 
 void renderer_set_viewport_size(renderer_t *renderer, int width, int height) {
     float matrix[16];
-    matrix_ortho_projection(matrix, width, height);
+    matrix_ortho_projection(matrix, (float)width, (float)height);
     shader_use(renderer->shader);
     shader_set_matrix4(renderer->shader, "uMatrix", matrix);
 }
 
-void renderer_draw(renderer_t *renderer, spine_skeleton_drawable drawable, atlas_t *atlas) {
+void renderer_draw(renderer_t *renderer, Skeleton *skeleton, bool premultipliedAlpha) {
     shader_use(renderer->shader);
     shader_set_int(renderer->shader, "uTexture", 0);
-    gl::glEnable(gl::GLenum::GL_BLEND);
+    glEnable(GL_BLEND);
 
-    spine_render_command command = spine_skeleton_drawable_render(drawable);
+    RenderCommand *command = renderer->renderer->render(*skeleton);
     while (command) {
-        int num_command_vertices = spine_render_command_get_num_vertices(command);
+        int num_command_vertices = command->numVertices;
         if (renderer->vertex_buffer_size < num_command_vertices) {
             renderer->vertex_buffer_size = num_command_vertices;
             free(renderer->vertex_buffer);
             renderer->vertex_buffer = (vertex_t *)malloc(sizeof(vertex_t) * renderer->vertex_buffer_size);
         }
-        float *positions = spine_render_command_get_positions(command);
-        float *uvs = spine_render_command_get_uvs(command);
-        int32_t *colors = spine_render_command_get_colors(command);
-        int32_t *darkColors = spine_render_command_get_dark_colors(command);
+        float *positions = command->positions;
+        float *uvs = command->uvs;
+        int32_t *colors = command->colors;
+        int32_t *darkColors = command->darkColors;
         for (int i = 0, j = 0; i < num_command_vertices; i++, j += 2) {
             vertex_t *vertex = &renderer->vertex_buffer[i];
             vertex->x = positions[j];
@@ -393,18 +313,18 @@ void renderer_draw(renderer_t *renderer, spine_skeleton_drawable drawable, atlas
             uint32_t darkColor = darkColors[i];
             vertex->darkColor = (darkColor & 0xFF00FF00) | ((darkColor & 0x00FF0000) >> 16) | ((darkColor & 0x000000FF) << 16);
         }
-        int num_command_indices = spine_render_command_get_num_indices(command);
-        uint16_t *indices = spine_render_command_get_indices(command);
+        int num_command_indices = command->numIndices;
+        uint16_t *indices = command->indices;
         mesh_update(renderer->mesh, renderer->vertex_buffer, num_command_vertices, indices, num_command_indices);
 
-        blend_mode_t blend_mode = blend_modes[spine_render_command_get_blend_mode(command)];
-        gl::glBlendFuncSeparate(spine_atlas_is_pma(atlas->atlas) ? (gl::GLenum)blend_mode.source_color_pma : (gl::GLenum)blend_mode.source_color, (gl::GLenum)blend_mode.dest_color, (gl::GLenum)blend_mode.source_alpha, (gl::GLenum)blend_mode.dest_color);
+        blend_mode_t blend_mode = blend_modes[command->blendMode];
+        glBlendFuncSeparate(premultipliedAlpha ? (GLenum)blend_mode.source_color_pma : (GLenum)blend_mode.source_color, (GLenum)blend_mode.dest_color, (GLenum)blend_mode.source_alpha, (GLenum)blend_mode.dest_color);
 
-        texture_t texture = atlas->textures[spine_render_command_get_atlas_page(command)];
+        auto texture = (texture_t)(uintptr_t)command->texture;
         texture_use(texture);
 
         mesh_draw(renderer->mesh);
-        command = spine_render_command_get_next(command);
+        command = command->next;
     }
 }
 
@@ -412,5 +332,6 @@ void renderer_dispose(renderer_t *renderer) {
     shader_dispose(renderer->shader);
     mesh_dispose(renderer->mesh);
     free(renderer->vertex_buffer);
+    delete renderer->renderer;
     free(renderer);
 }

+ 10 - 18
spine-glfw/src/spine-glfw.h

@@ -1,7 +1,7 @@
 #pragma once
 
 #include <stdint.h>
-#include <spine-cpp-lite.h>
+#include <spine/spine.h>
 
 /// A vertex of a mesh generated from a Spine skeleton
 struct vertex_t {
@@ -59,21 +59,12 @@ void texture_use(texture_t texture);
 /// Disposes the texture
 void texture_dispose(texture_t texture);
 
-/// Helper struct that contains a Spine atlas and the textures for each
-/// atlas page
-typedef struct {
-    spine_atlas atlas;
-    texture_t *textures;
-} atlas_t;
-
-/// Loads the .atlas file and its associated atlas pages as OpenGL textures
-atlas_t *atlas_load(const char *file_path);
-
-/// Disposes the atlas data and its associated OpenGL textures
-void atlas_dispose(atlas_t *atlas);
-
-/// Loads the skeleton data from the .skel or .json file using the given atlas
-spine_skeleton_data skeleton_data_load(const char *file_path, atlas_t *atlas);
+/// A TextureLoader implementation for OpenGL. Use this with spine::Atlas.
+class GlTextureLoader: public spine::TextureLoader {
+public:
+    void load(spine::AtlasPage &page, const spine::String &path);
+    void unload(void *texture);
+};
 
 /// Renderer capable of rendering a spine_skeleton_drawable, using a shader, a mesh, and a
 /// temporary CPU-side vertex buffer used to update the GPU-side mesh
@@ -82,6 +73,7 @@ typedef struct {
     mesh_t *mesh;
     int vertex_buffer_size;
     vertex_t *vertex_buffer;
+    spine::SkeletonRenderer *renderer;
 } renderer_t;
 
 /// Creates a new renderer
@@ -90,9 +82,9 @@ renderer_t *renderer_create();
 /// Sets the viewport size for the 2D orthographic projection
 void renderer_set_viewport_size(renderer_t *renderer, int width, int height);
 
-/// Draws the given skeleton drawbale. The atlas must be the atlas from which the drawable
+/// Draws the given skeleton. The atlas must be the atlas from which the drawable
 /// was constructed.
-void renderer_draw(renderer_t *renderer, spine_skeleton_drawable drawable, atlas_t *atlas);
+void renderer_draw(renderer_t *renderer, spine::Skeleton *skeleton, bool premultipliedAlpha);
 
 /// Disposes the renderer
 void renderer_dispose(renderer_t *renderer);