taisei/src/resource/model.c
Andrei Alexeyev b6978178b1
memory: use custom memory allocation wrappers
Introduces wrappers around memory allocation functions in `memory.h`
that should be used instead of the standard C ones.

These never return NULL and, with the exception of `mem_realloc()`,
zero-initialize the allocated memory like `calloc()` does.

All allocations made with the memory.h API must be deallocated with
`mem_free()`. Although standard `free()` will work on some platforms,
it's not portable (currently it won't work on Windows). Likewise,
`mem_free()` must not be used to free foreign allocations.

The standard C allocation functions are now diagnosed as deprecated.
They are, however, available with the `libc_` prefix in case interfacing
with foreign APIs is required. So far they are only used to implement
`memory.h`.

Perhaps the most important change is the introduction of the `ALLOC()`,
`ALLOC_ARRAY()`, and `ALLOC_FLEX()` macros. They take a type as a
parameter, and allocate enough memory with the correct alignment for
that type. That includes overaligned types as well. In most
circumstances you should prefer to use these macros. See the `memory.h`
header for some usage examples.
2023-01-18 13:23:22 +01:00

497 lines
13 KiB
C

/*
* This software is licensed under the terms of the MIT License.
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#include "taisei.h"
#include "model.h"
#include "list.h"
#include "resource.h"
#include "renderer/api.h"
#include "iqm.h"
#define MDL_PATH_PREFIX "res/models/"
#define MDL_EXTENSION ".iqm"
// arbitrary allocation limits (raise as needed)
#define MAX_MESHES (1 << 8)
#define MAX_VERTICES (1 << 24)
#define MAX_TRIANGLES (1 << 22)
#define MAX_VERTEX_ARRAYS (1 << 8)
typedef struct ModelLoadData {
GenericModelVertex *vertices;
uint32_t *indices;
uint ofs_vertices, num_vertices;
uint ofs_indices, num_indices;
} ModelLoadData;
#define NUM_REQUIRED_VERTEX_ARRAYS 4
typedef union VertexArrayIndices {
struct { int position, texcoord, normal, tangent; };
int indices[NUM_REQUIRED_VERTEX_ARRAYS];
} VertexArrayIndices;
static const char *iqm_va_type_str(uint32_t type) {
switch(type) {
case IQM_POSITION: return "position";
case IQM_NORMAL: return "normal";
case IQM_TANGENT: return "tangent";
case IQM_TEXCOORD: return "texcoord";
case IQM_BLENDINDEXES: return "blend indices";
case IQM_BLENDWEIGHTS: return "blend weights";
case IQM_COLOR: return "color";
default: return "unknown";
}
}
static const char *iqm_va_format_str(uint32_t type) {
switch(type) {
case IQM_BYTE: return "int8_t";
case IQM_UBYTE: return "uint8_t";
case IQM_SHORT: return "int16_t";
case IQM_USHORT: return "uint16_t";
case IQM_INT: return "int32_t";
case IQM_UINT: return "uint32_t";
case IQM_HALF: return "half";
case IQM_FLOAT: return "float";
case IQM_DOUBLE: return "double";
default: return "unknown";
}
}
static bool read_fields(SDL_RWops *rw, size_t n, uint32_t fields[n]) {
if(SDL_RWread(rw, fields, sizeof(fields[0]), n) != n) {
return false;
}
for(size_t i = 0; i < n; ++i) {
fields[i] = SDL_SwapLE32(fields[i]);
}
return true;
}
static bool read_floats(SDL_RWops *rw, size_t n, float floats[n]) {
uint32_t fields[n];
if(read_fields(rw, n, fields)) {
memcpy(floats, fields, sizeof(fields[0]) * n);
return true;
}
return false;
}
static bool iqm_read_header(const char *fpath, SDL_RWops *rw, IQMHeader *hdr) {
if(SDL_RWread(rw, &hdr->magic, sizeof(hdr->magic), 1) != 1) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
if(memcmp(hdr->magic, IQM_MAGIC, sizeof(IQM_MAGIC))) {
log_error("%s: not an IQM file (bad magic number)", fpath);
return false;
}
if(!read_fields(rw, ARRAY_SIZE(hdr->u32_array), hdr->u32_array)) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
if(hdr->version != IQM_VERSION) {
log_error("%s: unsupported IQM version (got %u, expected %u)", fpath, hdr->version, IQM_VERSION);
return false;
}
if(hdr->num_meshes < 1) {
log_error("%s: no meshes in model", fpath);
return false;
}
if(hdr->num_meshes > MAX_MESHES) {
log_error("%s too many meshes in model (%u allowed; have %u)", fpath, MAX_MESHES, hdr->num_meshes);
return false;
}
if(hdr->num_vertexes < 1) {
log_error("%s: no vertices in model", fpath);
return false;
}
if(hdr->num_vertexes > MAX_VERTICES) {
log_error("%s too many vertices in model (%u allowed; have %u)", fpath, MAX_VERTICES, hdr->num_vertexes);
return false;
}
if(hdr->num_triangles < 1) {
log_error("%s: no triangles in model", fpath);
return false;
}
if(hdr->num_triangles > MAX_TRIANGLES) {
log_error("%s too many triangles in model (%u allowed; have %u)", fpath, MAX_TRIANGLES, hdr->num_triangles);
return false;
}
if(hdr->num_vertexarrays < NUM_REQUIRED_VERTEX_ARRAYS) {
log_error("%s: not enough vertex arrays (%u expected; got %u)", fpath, NUM_REQUIRED_VERTEX_ARRAYS, hdr->num_vertexarrays);
return false;
}
if(hdr->num_vertexarrays > MAX_VERTEX_ARRAYS) {
log_error("%s too many vertex arrays in model (%u allowed; have %u)", fpath, MAX_VERTEX_ARRAYS, hdr->num_vertexarrays);
return false;
}
log_debug("%s: IQM version %u; %u meshes; %u tris; %u vertices",
fpath,
hdr->version,
hdr->num_meshes,
hdr->num_triangles,
hdr->num_vertexes
);
return true;
}
static bool iqm_read_meshes(const char *fpath, SDL_RWops *rw, uint num_meshes, IQMMesh meshes[num_meshes]) {
for(uint i = 0; i < num_meshes; ++i) {
if(!read_fields(rw, ARRAY_SIZE(meshes[i].u32_array), meshes[i].u32_array)) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
log_debug("Mesh #0: %u tris; %u vertices", meshes[i].num_triangles, meshes[i].num_vertexes);
}
return true;
}
static bool iqm_read_vertex_arrays(const char *fpath, SDL_RWops *rw, uint num_varrs, IQMVertexArray varrs[num_varrs], VertexArrayIndices *indices) {
for(uint i = 0; i < num_varrs; ++i) {
IQMVertexArray *va = varrs + i;
if(!read_fields(rw, ARRAY_SIZE(va->u32_array), va->u32_array)) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
log_debug("Vertex array #%u: %s[%u] %s",
i,
iqm_va_format_str(va->format),
va->size,
iqm_va_type_str(va->type)
);
int *idx_p;
uint32_t expected_size;
switch(va->type) {
case IQM_POSITION:
idx_p = &indices->position;
expected_size = 3;
break;
case IQM_TEXCOORD:
idx_p = &indices->texcoord;
expected_size = 2;
break;
case IQM_NORMAL:
idx_p = &indices->normal;
expected_size = 3;
break;
case IQM_TANGENT:
idx_p = &indices->tangent;
expected_size = 4;
break;
default:
log_warn("%s: vertex array #%i ignored: unhandled type (%s)", fpath, i, iqm_va_type_str(va->type));
continue;
}
if(*idx_p > 0) {
log_warn("%s: vertex array #%i ignored: already using array #%i for %s data", fpath, i, *idx_p, iqm_va_type_str(va->type));
continue;
}
if(va->size != expected_size) {
log_warn("%s: vertex array #%i ignored: data size mismatch (expected %u; got %u)", fpath, i, expected_size, va->size);
continue;
}
if(va->format != IQM_FLOAT) {
log_warn("%s: vertex array #%i ignored: %s data type not supported", fpath, i, iqm_va_format_str(va->format));
continue;
}
*idx_p = i;
log_debug("Using vertex array #%u for %s data",
i,
iqm_va_type_str(va->type)
);
}
bool ok = true;
if(indices->position < 0) {
log_error("%s: no position data", fpath);
ok = false;
}
if(indices->texcoord < 0) {
log_error("%s: no texcoord data", fpath);
ok = false;
}
if(indices->normal < 0) {
log_error("%s: no normal data", fpath);
ok = false;
}
if(indices->tangent < 0) {
log_error("%s: no tangent data", fpath);
ok = false;
}
return ok;
}
static bool iqm_read_vert_positions(const char *fpath, SDL_RWops *rw, uint num_verts, GenericModelVertex vertices[num_verts]) {
for(uint i = 0; i < num_verts; ++i) {
if(!read_floats(rw, ARRAY_SIZE(vertices[i].position), vertices[i].position)) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
}
return true;
}
static bool iqm_read_vert_texcoords(const char *fpath, SDL_RWops *rw, uint num_verts, GenericModelVertex vertices[num_verts]) {
for(uint i = 0; i < num_verts; ++i) {
if(!read_floats(rw, ARRAY_SIZE(vertices[i].uv), vertices[i].uv)) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
vertices[i].uv[1] = 1.0 - vertices[i].uv[1];
}
return true;
}
static bool iqm_read_vert_normals(const char *fpath, SDL_RWops *rw, uint num_verts, GenericModelVertex vertices[num_verts]) {
for(uint i = 0; i < num_verts; ++i) {
if(!read_floats(rw, ARRAY_SIZE(vertices[i].normal), vertices[i].normal)) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
}
return true;
}
static bool iqm_read_vert_tangents(const char *fpath, SDL_RWops *rw, uint num_verts, GenericModelVertex vertices[num_verts]) {
for(uint i = 0; i < num_verts; ++i) {
if(!read_floats(rw, ARRAY_SIZE(vertices[i].tangent), vertices[i].tangent)) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
}
return true;
}
static bool iqm_read_triangles(const char *fpath, SDL_RWops *rw, uint num_tris, IQMTriangle triangles[num_tris]) {
if(!read_fields(rw, ARRAY_SIZE(triangles->u32_array) * num_tris, triangles->u32_array)) {
log_error("%s: read error: %s", fpath, SDL_GetError());
return false;
}
return true;
}
static bool range_is_valid(uint32_t total, uint32_t first, uint32_t num) {
if(((uint64_t)first + (uint64_t)num) != (first + num)) {
return false;
}
if(first + num > total) {
return false;
}
return true;
}
static void load_model_stage1(ResourceLoadState *st);
static void load_model_stage2(ResourceLoadState *st);
static void load_model_stage1(ResourceLoadState *st) {
const char *path = st->path;
SDL_RWops *rw = vfs_open(path, VFS_MODE_READ | VFS_MODE_SEEKABLE);
if(!rw) {
log_error("VFS error: %s", vfs_get_error());
res_load_failed(st);
return;
}
ModelLoadData *ldata = NULL;
IQMMesh *meshes = NULL;
IQMVertexArray *vert_arrays = NULL;
GenericModelVertex *vertices = NULL;
union { uint32_t indices[3]; IQMTriangle tri; } *indices = NULL;
#define TRY_SEEK(ofs) \
do { \
if(SDL_RWseek(rw, ofs, RW_SEEK_SET) < 0) { \
log_error("%s: %s", path, SDL_GetError()); \
goto fail; \
} \
assert(SDL_RWtell(rw) == ofs); \
} while(0)
#define TRY(...) \
do { \
if(!(__VA_ARGS__)) { \
goto fail; \
} \
} while(0)
IQMHeader hdr;
TRY(iqm_read_header(path, rw, &hdr));
assume(hdr.num_meshes > 0);
meshes = ALLOC_ARRAY(hdr.num_meshes, typeof(*meshes));
TRY_SEEK(hdr.ofs_meshes);
TRY(iqm_read_meshes(path, rw, hdr.num_meshes, meshes));
for(uint i = 0; i < hdr.num_meshes; ++i) {
IQMMesh *mesh = meshes + i;
if(!range_is_valid(hdr.num_vertexes, mesh->first_vertex, mesh->num_vertexes)) {
log_error("%s: mesh %i: vertices out of range", path, i);
goto fail;
}
if(!range_is_valid(hdr.num_triangles, mesh->first_triangle, mesh->num_triangles)) {
log_error("%s: mesh %i: triangles out of range", path, i);
goto fail;
}
}
assume(hdr.num_vertexarrays > 0);
vert_arrays = ALLOC_ARRAY(hdr.num_vertexarrays, typeof(*vert_arrays));
TRY_SEEK(hdr.ofs_vertexarrays);
VertexArrayIndices va_indices;
for(uint i = 0; i < NUM_REQUIRED_VERTEX_ARRAYS; ++i) {
va_indices.indices[i] = -1;
}
TRY(iqm_read_vertex_arrays(path, rw, hdr.num_vertexarrays, vert_arrays, &va_indices));
assume(hdr.num_vertexes > 0);
vertices = ALLOC_ARRAY(hdr.num_vertexes, typeof(*vertices));
TRY_SEEK(vert_arrays[va_indices.position].offset);
TRY(iqm_read_vert_positions(path, rw, hdr.num_vertexes, vertices));
TRY_SEEK(vert_arrays[va_indices.texcoord].offset);
TRY(iqm_read_vert_texcoords(path, rw, hdr.num_vertexes, vertices));
TRY_SEEK(vert_arrays[va_indices.normal].offset);
TRY(iqm_read_vert_normals(path, rw, hdr.num_vertexes, vertices));
TRY_SEEK(vert_arrays[va_indices.tangent].offset);
TRY(iqm_read_vert_tangents(path, rw, hdr.num_vertexes, vertices));
assume(hdr.num_triangles > 0);
indices = ALLOC_ARRAY(hdr.num_triangles, typeof(*indices));
TRY_SEEK(hdr.ofs_triangles);
TRY(iqm_read_triangles(path, rw, hdr.num_triangles, &indices->tri));
ldata = ALLOC(typeof(*ldata));
ldata->vertices = vertices;
ldata->indices = indices->indices;
ldata->ofs_vertices = 0;
ldata->num_vertices = hdr.num_vertexes;
ldata->ofs_indices = 0;
ldata->num_indices = hdr.num_triangles * 3;
cleanup:
mem_free(meshes);
mem_free(vert_arrays);
SDL_RWclose(rw);
if(ldata) {
res_load_continue_on_main(st, load_model_stage2, ldata);
} else {
res_load_failed(st);
}
return;
fail:
mem_free(vertices);
mem_free(indices);
mem_free(ldata);
ldata = NULL;
goto cleanup;
}
static void load_model_stage2(ResourceLoadState *st) {
ModelLoadData *ldata = NOT_NULL(st->opaque);
auto mdl = ALLOC(Model);
r_model_add_static(
mdl,
PRIM_TRIANGLES,
ldata->num_vertices,
ldata->vertices + ldata->ofs_vertices,
ldata->num_indices,
ldata->indices + ldata->ofs_indices
);
mem_free(ldata->vertices);
mem_free(ldata->indices);
mem_free(ldata);
res_load_finished(st, mdl);
}
static char *model_path(const char *name) {
return strjoin(MDL_PATH_PREFIX, name, MDL_EXTENSION, NULL);
}
static bool check_model_path(const char *path) {
return strendswith(path, MDL_EXTENSION);
}
static void unload_model(void *model) { // Does not delete elements from the VBO, so doing this at runtime is leaking VBO space
mem_free(model);
}
ResourceHandler model_res_handler = {
.type = RES_MODEL,
.typename = "model",
.subdir = MDL_PATH_PREFIX,
.procs = {
.find = model_path,
.check = check_model_path,
.load = load_model_stage1,
.unload = unload_model,
},
};