From 9db5f90ccb6b46240635f9f4b05509950c0f5015 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 1 Jul 2024 14:57:05 +0200 Subject: [PATCH] [glfw] Rewrite using new SkeletonRenderer and switch to spine-cpp. --- spine-cpp/spine-cpp-lite/spine-cpp-lite.cpp | 1 + .../spine-cpp/include/spine/BlockAllocator.h | 110 ++++++++ .../include/spine/SkeletonRenderer.h | 68 +++++ spine-cpp/spine-cpp/include/spine/spine.h | 1 + .../spine-cpp/src/spine/SkeletonRenderer.cpp | 248 ++++++++++++++++++ spine-glfw/CMakeLists.txt | 4 +- spine-glfw/example/main.cpp | 43 +-- spine-glfw/src/spine-glfw.cpp | 145 +++------- spine-glfw/src/spine-glfw.h | 28 +- 9 files changed, 497 insertions(+), 151 deletions(-) create mode 100644 spine-cpp/spine-cpp/include/spine/BlockAllocator.h create mode 100644 spine-cpp/spine-cpp/include/spine/SkeletonRenderer.h create mode 100644 spine-cpp/spine-cpp/src/spine/SkeletonRenderer.cpp diff --git a/spine-cpp/spine-cpp-lite/spine-cpp-lite.cpp b/spine-cpp/spine-cpp-lite/spine-cpp-lite.cpp index 29b7ac027..7037f29a5 100644 --- a/spine-cpp/spine-cpp-lite/spine-cpp-lite.cpp +++ b/spine-cpp/spine-cpp-lite/spine-cpp-lite.cpp @@ -81,6 +81,7 @@ class BlockAllocator : public SpineObject { } void compress() { + if (blocks.size() == 1) return; int totalSize = 0; for (int i = 0, n = blocks.size(); i < n; i++) { totalSize += blocks[i].size; diff --git a/spine-cpp/spine-cpp/include/spine/BlockAllocator.h b/spine-cpp/spine-cpp/include/spine/BlockAllocator.h new file mode 100644 index 000000000..89eb5fd2a --- /dev/null +++ b/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 +#include +#include +#include +#include + +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 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 + 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(block.size, __FILE__, __LINE__); + return block; + } + }; +} + +#endif \ No newline at end of file diff --git a/spine-cpp/spine-cpp/include/spine/SkeletonRenderer.h b/spine-cpp/spine-cpp/include/spine/SkeletonRenderer.h new file mode 100644 index 000000000..f4403d863 --- /dev/null +++ b/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 +#include +#include + +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 _worldVertices; + Vector _quadIndices; + SkeletonClipping _clipping; + Vector _renderCommands; + }; +} + +#endif \ No newline at end of file diff --git a/spine-cpp/spine-cpp/include/spine/spine.h b/spine-cpp/spine-cpp/include/spine/spine.h index f6f6cfa12..c99d61c06 100644 --- a/spine-cpp/spine-cpp/include/spine/spine.h +++ b/spine-cpp/spine-cpp/include/spine/spine.h @@ -93,6 +93,7 @@ #include #include #include +#include #include #include #include diff --git a/spine-cpp/spine-cpp/src/spine/SkeletonRenderer.cpp b/spine-cpp/spine-cpp/src/spine/SkeletonRenderer.cpp new file mode 100644 index 000000000..ca7849ed4 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include + +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(1); + cmd->positions = allocator.allocate(numVertices << 1); + cmd->uvs = allocator.allocate(numVertices << 1); + cmd->colors = allocator.allocate(numVertices); + cmd->darkColors = allocator.allocate(numVertices); + cmd->numVertices = numVertices; + cmd->indices = allocator.allocate(numIndices); + cmd->numIndices = numIndices; + cmd->blendMode = blendMode; + cmd->texture = texture; + cmd->next = nullptr; + return cmd; +} + +static RenderCommand *batchSubCommands(BlockAllocator &allocator, Vector &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 &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 *worldVertices = &_worldVertices; + Vector *quadIndices = &_quadIndices; + Vector *vertices = worldVertices; + int32_t verticesCount; + Vector *uvs; + Vector *indices; + int32_t indicesCount; + Color *attachmentColor; + void *texture; + + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + RegionAttachment *regionAttachment = (RegionAttachment *) attachment; + attachmentColor = ®ionAttachment->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 = ®ionAttachment->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(skeleton.getColor().r * slot.getColor().r * attachmentColor->r * 255); + uint8_t g = static_cast(skeleton.getColor().g * slot.getColor().g * attachmentColor->g * 255); + uint8_t b = static_cast(skeleton.getColor().b * slot.getColor().b * attachmentColor->b * 255); + uint8_t a = static_cast(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(slotDarkColor.r * 255) << 16) | (static_cast(slotDarkColor.g * 255) << 8) | static_cast(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); +} \ No newline at end of file diff --git a/spine-glfw/CMakeLists.txt b/spine-glfw/CMakeLists.txt index f7a7a217d..dfd3a3dd7 100644 --- a/spine-glfw/CMakeLists.txt +++ b/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) diff --git a/spine-glfw/example/main.cpp b/spine-glfw/example/main.cpp index 8e2f387d9..2563cb5dd 100644 --- a/spine-glfw/example/main.cpp +++ b/spine-glfw/example/main.cpp @@ -5,6 +5,8 @@ #include #include +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(); diff --git a/spine-glfw/src/spine-glfw.cpp b/spine-glfw/src/spine-glfw.cpp index de8ca1031..87aff98de 100644 --- a/spine-glfw/src/spine-glfw.cpp +++ b/spine-glfw/src/spine-glfw.cpp @@ -1,10 +1,16 @@ #include "spine-glfw.h" -#include +#include #include #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); } \ No newline at end of file diff --git a/spine-glfw/src/spine-glfw.h b/spine-glfw/src/spine-glfw.h index 29e6e21b1..315cf91b7 100644 --- a/spine-glfw/src/spine-glfw.h +++ b/spine-glfw/src/spine-glfw.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include /// 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); \ No newline at end of file