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

This commit is contained in:
Mario Zechner 2024-07-01 14:57:05 +02:00
parent 072d5f1539
commit 9db5f90ccb
9 changed files with 497 additions and 151 deletions

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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)

View File

@ -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();

View File

@ -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);
}

View File

@ -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);