bluenoise-raytracer/raytracer/nvpro_core/fileformats/cadscenefile.inl
2024-05-25 11:53:25 +02:00

3637 lines
No EOL
106 KiB
C++

/*
* Copyright (c) 2011-2021, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2011-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if CSF_SUPPORT_ZLIB
#include <zlib.h>
#endif
#include <algorithm>
#include <map>
#include <mutex>
#include <thread>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/type_ptr.hpp>
#define CADSCENEFILE_MAGIC 1567262451
#ifdef WIN32
#define xfreads(buf, bufsz, sz, count, f) fread_s(buf, bufsz, sz, count, f)
#else
#define xfreads(buf, bufsz, sz, count, f) fread(buf, sz, count, f)
#endif
#if defined(WIN32) && (defined(__amd64__) || defined(__x86_64__) || defined(_M_X64) || defined(__AMD64__))
#define xftell(f) _ftelli64(f)
#define xfseek(f, pos, encoded) _fseeki64(f, pos, encoded)
#else
#define xftell(f) ftell(f)
#define xfseek(f, pos, encoded) fseek(f, (long)pos, encoded)
#endif
#if defined(LINUX)
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
//////////////////////////////////////////////////////////////////////////
// FILEMAPPING
namespace csfutils {
class FileMapping
{
public:
FileMapping(FileMapping&& other) noexcept { this->operator=(std::move(other)); };
FileMapping& operator=(FileMapping&& other)
{
m_isValid = other.m_isValid;
m_fileSize = other.m_fileSize;
m_mappingType = other.m_mappingType;
m_mappingPtr = other.m_mappingPtr;
m_mappingSize = other.m_mappingSize;
#ifdef _WIN32
m_win32.file = other.m_win32.file;
m_win32.fileMapping = other.m_win32.fileMapping;
other.m_win32.file = nullptr;
other.m_win32.fileMapping = nullptr;
#else
m_unix.file = other.m_unix.file;
other.m_unix.file = -1;
#endif
other.m_isValid = false;
other.m_mappingPtr = nullptr;
return *this;
}
FileMapping(const FileMapping&) = delete;
FileMapping& operator=(const FileMapping& other) = delete;
FileMapping() {}
~FileMapping() { close(); }
enum MappingType
{
MAPPING_READONLY, // opens existing file for read-only access
MAPPING_READOVERWRITE, // creates new file with read/write access, overwriting existing files
};
// fileSize only for write access
bool open(const char* filename, MappingType mappingType, size_t fileSize = 0);
void close();
const void* data() const { return m_mappingPtr; }
void* data() { return m_mappingPtr; }
size_t size() const { return m_mappingSize; }
bool valid() const { return m_isValid; }
protected:
static size_t g_pageSize;
#ifdef _WIN32
struct
{
void* file = nullptr;
void* fileMapping = nullptr;
} m_win32;
#else
struct
{
int file = -1;
} m_unix;
#endif
bool m_isValid = false;
size_t m_fileSize = 0;
MappingType m_mappingType;
void* m_mappingPtr = nullptr;
size_t m_mappingSize = 0;
};
// convenience types
class FileReadMapping : private FileMapping
{
public:
bool open(const char* filename) { return FileMapping::open(filename, MAPPING_READONLY, 0); }
void close() { FileMapping::close(); }
const void* data() const { return m_mappingPtr; }
size_t size() const { return m_fileSize; }
bool valid() const { return m_isValid; }
};
class FileReadOverWriteMapping : private FileMapping
{
public:
bool open(const char* filename, size_t fileSize)
{
return FileMapping::open(filename, MAPPING_READOVERWRITE, fileSize);
}
void close() { FileMapping::close(); }
void* data() { return m_mappingPtr; }
size_t size() const { return m_fileSize; }
bool valid() const { return m_isValid; }
};
} // namespace csfutils
//////////////////////////////////////////////////////////////////////////
// GENERICS
static inline void csfMatrix44Copy(float* __restrict dst, const float* __restrict a)
{
memcpy(dst, a, sizeof(float) * 16);
}
static inline void csfMatrix44MultiplyFull(float* __restrict clip, const float* __restrict proj, const float* __restrict modl)
{
clip[0] = modl[0] * proj[0] + modl[1] * proj[4] + modl[2] * proj[8] + modl[3] * proj[12];
clip[1] = modl[0] * proj[1] + modl[1] * proj[5] + modl[2] * proj[9] + modl[3] * proj[13];
clip[2] = modl[0] * proj[2] + modl[1] * proj[6] + modl[2] * proj[10] + modl[3] * proj[14];
clip[3] = modl[0] * proj[3] + modl[1] * proj[7] + modl[2] * proj[11] + modl[3] * proj[15];
clip[4] = modl[4] * proj[0] + modl[5] * proj[4] + modl[6] * proj[8] + modl[7] * proj[12];
clip[5] = modl[4] * proj[1] + modl[5] * proj[5] + modl[6] * proj[9] + modl[7] * proj[13];
clip[6] = modl[4] * proj[2] + modl[5] * proj[6] + modl[6] * proj[10] + modl[7] * proj[14];
clip[7] = modl[4] * proj[3] + modl[5] * proj[7] + modl[6] * proj[11] + modl[7] * proj[15];
clip[8] = modl[8] * proj[0] + modl[9] * proj[4] + modl[10] * proj[8] + modl[11] * proj[12];
clip[9] = modl[8] * proj[1] + modl[9] * proj[5] + modl[10] * proj[9] + modl[11] * proj[13];
clip[10] = modl[8] * proj[2] + modl[9] * proj[6] + modl[10] * proj[10] + modl[11] * proj[14];
clip[11] = modl[8] * proj[3] + modl[9] * proj[7] + modl[10] * proj[11] + modl[11] * proj[15];
clip[12] = modl[12] * proj[0] + modl[13] * proj[4] + modl[14] * proj[8] + modl[15] * proj[12];
clip[13] = modl[12] * proj[1] + modl[13] * proj[5] + modl[14] * proj[9] + modl[15] * proj[13];
clip[14] = modl[12] * proj[2] + modl[13] * proj[6] + modl[14] * proj[10] + modl[15] * proj[14];
clip[15] = modl[12] * proj[3] + modl[13] * proj[7] + modl[14] * proj[11] + modl[15] * proj[15];
}
static void csfTraverseHierarchy(CSFile* csf, CSFNode* __restrict node, CSFNode* __restrict parent)
{
if(parent)
{
csfMatrix44MultiplyFull(node->worldTM, parent->worldTM, node->objectTM);
}
else
{
csfMatrix44Copy(node->worldTM, node->objectTM);
}
for(int i = 0; i < node->numChildren; i++)
{
CSFNode* __restrict child = csf->nodes + node->children[i];
csfTraverseHierarchy(csf, child, node);
}
}
static inline size_t csfGeometryPartChannelSize(CSFGeometryPartChannel channel)
{
switch(channel)
{
case CSFGEOMETRY_PARTCHANNEL_BBOX:
return sizeof(CSFGeometryPartBbox);
case CSFGEOMETRY_PARTCHANNEL_VERTEXRANGE:
return sizeof(CSFGeometryPartVertexRange);
default:
return 0;
}
}
static inline size_t csfGeometryPerPartSize(int numPartChannels, CSFGeometryPartChannel* perpartStorageOrder, int numParts)
{
size_t size = 0;
for(int i = 0; i < numPartChannels; i++)
{
size += CSFGeometryPartChannel_getSize(perpartStorageOrder[i]) * numParts;
}
return size;
}
CSFAPI int CSFile_transform(CSFile* csf)
{
if(csf->rootIDX < 0)
return CADSCENEFILE_NOERROR;
if(!(csf->fileFlags & CADSCENEFILE_FLAG_UNIQUENODES))
return CADSCENEFILE_ERROR_OPERATION;
csfTraverseHierarchy(csf, csf->nodes + csf->rootIDX, nullptr);
return CADSCENEFILE_NOERROR;
}
static void CSFile_versionFixHeader(CSFile* csf)
{
if(csf->version < CADSCENEFILE_VERSION_FILEFLAGS)
{
csf->fileFlags = csf->fileFlags ? CADSCENEFILE_FLAG_UNIQUENODES : 0;
}
}
static const CSFMeta* CSFile_getNodeMetas(const CSFile* csf)
{
if(csf->version >= CADSCENEFILE_VERSION_META)
{
return csf->nodeMetas;
}
return nullptr;
}
static const CSFMeta* CSFile_getGeometryMetas(const CSFile* csf)
{
if(csf->version >= CADSCENEFILE_VERSION_META)
{
return csf->geometryMetas;
}
return nullptr;
}
static const CSFMeta* CSFile_getFileMeta(const CSFile* csf)
{
if(csf->version >= CADSCENEFILE_VERSION_META)
{
return csf->fileMeta;
}
return nullptr;
}
static inline const CSFBytePacket* csfGetBytePacket(const unsigned char* bytes, CSFoffset numBytes, const CSFGuid guid)
{
if(numBytes < sizeof(CSFBytePacket))
return nullptr;
do
{
const CSFBytePacket* packet = (const CSFBytePacket*)bytes;
if(memcmp(&guid, &packet->guid, sizeof(CSFGuid)) == 0)
{
return packet;
}
numBytes -= packet->numBytes;
bytes += packet->numBytes;
} while(numBytes >= sizeof(CSFBytePacket));
return nullptr;
}
CSFAPI const CSFBytePacket* CSFMeta_getBytePacket(const CSFMeta* meta, const CSFGuid guid)
{
return meta ? csfGetBytePacket(meta->bytes, meta->numBytes, guid) : nullptr;
}
CSFAPI const CSFBytePacket* CSFMaterial_getBytePacket(const CSFMaterial* material, const CSFGuid guid)
{
return csfGetBytePacket(material->bytes, material->numBytes, guid);
}
CSFAPI const CSFBytePacket* CSFile_getMaterialBytePacket(const CSFile* csf, int materialIDX, const CSFGuid guid)
{
if(materialIDX < 0 || materialIDX >= csf->numMaterials)
{
return nullptr;
}
return csfGetBytePacket(csf->materials[materialIDX].bytes, csf->materials[materialIDX].numBytes, guid);
}
CSFAPI const CSFBytePacket* CSFile_getFileBytePacket(const CSFile* csf, const CSFGuid guid)
{
return CSFMeta_getBytePacket(csf->fileMeta, guid);
}
CSFAPI void CSFMatrix_identity(float* matrix)
{
memset(matrix, 0, sizeof(float) * 16);
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0f;
}
CSFAPI void CSFGeometry_clearDeprecated(CSFGeometry* geo)
{
memset(geo->_deprecated, 0, sizeof(geo->_deprecated));
}
CSFAPI void CSFile_clearDeprecated(CSFile* csf)
{
for(int g = 0; g < csf->numGeometries; g++)
{
CSFGeometry_clearDeprecated(csf->geometries + g);
}
}
CSFAPI void CSFGeometry_setupDefaultChannels(CSFGeometry* geo)
{
geo->numNormalChannels = geo->normal ? 1 : 0;
geo->numTexChannels = geo->tex ? 1 : 0;
geo->numAuxChannels = 0;
geo->numPartChannels = 0;
geo->aux = nullptr;
geo->auxStorageOrder = nullptr;
geo->perpart = nullptr;
}
CSFAPI void CSFGeometry_removeAllPartChannels(CSFGeometry* geo, CSFileMemoryPTR mem)
{
geo->numPartChannels = 0;
geo->perpart = nullptr;
geo->perpartStorageOrder = nullptr;
}
CSFAPI void CSFMeta_setOrAddBytePacket(CSFMeta** metaPtr, CSFileMemoryPTR csfmem, size_t size, const void* data)
{
assert(metaPtr && data);
CSFMeta* meta = *metaPtr;
if(!meta)
{
*metaPtr = meta = (CSFMeta*)CSFileMemory_alloc(csfmem, sizeof(CSFMeta), nullptr);
memset(meta, 0, sizeof(CSFMeta));
}
const CSFBytePacket* packetIn = (const CSFBytePacket*)data;
assert(packetIn->numBytes == uint32_t(size));
CSFBytePacket* packetFile = (CSFBytePacket*)CSFMeta_getBytePacket(meta, packetIn->guid);
if(packetFile)
{
assert(packetFile->numBytes == uint32_t(size));
memcpy(packetFile, data, size);
}
else
{
meta->bytes = (unsigned char*)CSFileMemory_allocPartial(csfmem, meta->numBytes + size, meta->numBytes, meta->bytes);
memcpy(meta->bytes + meta->numBytes, data, size);
meta->numBytes += size;
}
}
CSFAPI void CSFGeometry_removePartChannels(CSFGeometry* geo, CSFileMemoryPTR mem, uint32_t numChannels, const CSFGeometryPartChannel* channels)
{
uint32_t numNew = 0;
for(int p = 0; p < geo->numPartChannels; p++)
{
CSFGeometryPartChannel channel = geo->perpartStorageOrder[p];
bool remove = false;
for(uint32_t i = 0; i < numChannels; i++)
{
if(channel == channels[i])
{
remove = true;
break;
}
}
if(!remove)
{
const uint8_t* oldContent = (const uint8_t*)CSFGeometry_getPartChannel(geo, channel);
geo->perpartStorageOrder[numNew++] = channel;
uint8_t* newContent = (uint8_t*)CSFGeometry_getPartChannel(geo, channel);
if(newContent != oldContent)
{
size_t size = CSFGeometryPartChannel_getSize(channel) * geo->numParts;
if(newContent + size >= oldContent)
{
memmove(newContent, oldContent, size);
}
else
{
memcpy(newContent, oldContent, size);
}
}
}
}
geo->numPartChannels = numNew;
}
CSFAPI void CSFGeometry_requirePartChannels(CSFGeometry* geo, CSFileMemoryPTR mem, uint32_t numChannels, const CSFGeometryPartChannel* channels)
{
uint32_t numNew = 0;
for(uint32_t i = 0; i < numChannels; i++)
{
bool exists = CSFGeometry_getPartChannel(geo, channels[i]) != nullptr;
numNew += exists ? 0 : 1;
}
if(!numNew)
return;
size_t oldSize = CSFGeometry_getPerPartSize(geo);
// realloc storage
geo->perpartStorageOrder =
CSFileMemory_allocPartialT(mem, geo->numPartChannels + numNew, geo->numPartChannels, geo->perpartStorageOrder);
numNew = 0;
for(uint32_t i = 0; i < numChannels; i++)
{
bool exists = CSFGeometry_getPartChannel(geo, channels[i]) != nullptr;
if(!exists)
{
geo->perpartStorageOrder[geo->numPartChannels + (numNew++)] = channels[i];
}
}
geo->numPartChannels += numNew;
size_t newSize = CSFGeometry_getPerPartSize(geo);
// realloc content
geo->perpart = CSFileMemory_allocPartialT(mem, newSize, oldSize, geo->perpart);
}
CSFAPI void CSFGeometry_requirePartChannel(CSFGeometry* geo, CSFileMemoryPTR mem, CSFGeometryPartChannel channel)
{
CSFGeometry_requirePartChannels(geo, mem, 1, &channel);
}
CSFAPI void CSFGeometry_requireAuxChannel(CSFGeometry* geo, CSFileMemoryPTR mem, CSFGeometryAuxChannel channel)
{
if(CSFGeometry_getAuxChannel(geo, channel))
return;
size_t oldSize = sizeof(float) * 4 * geo->numVertices * geo->numAuxChannels;
// realloc storage
geo->auxStorageOrder = CSFileMemory_allocPartialT(mem, geo->numAuxChannels + 1, geo->numAuxChannels, geo->auxStorageOrder);
geo->auxStorageOrder[geo->numAuxChannels++] = channel;
size_t newSize = sizeof(float) * 4 * geo->numVertices * geo->numAuxChannels;
// realloc content
geo->aux = CSFileMemory_allocPartialT(mem, newSize, oldSize, geo->aux);
}
CSFAPI void CSFile_setupDefaultChannels(CSFile* csf)
{
for(int g = 0; g < csf->numGeometries; g++)
{
CSFGeometry_setupDefaultChannels(csf->geometries + g);
}
}
CSFAPI const float* CSFGeometry_getNormalChannel(const CSFGeometry* geo, CSFGeometryNormalChannel channel)
{
return int(channel) < geo->numNormalChannels ? geo->normal + size_t(geo->numVertices * 3 * channel) : nullptr;
}
CSFAPI const float* CSFGeometry_getTexChannel(const CSFGeometry* geo, CSFGeometryTexChannel channel)
{
return int(channel) < geo->numTexChannels ? geo->tex + size_t(geo->numVertices * 2 * channel) : nullptr;
}
CSFAPI const float* CSFGeometry_getAuxChannel(const CSFGeometry* geo, CSFGeometryAuxChannel channel)
{
for(int i = 0; i < geo->numAuxChannels; i++)
{
if(geo->auxStorageOrder[i] == channel)
{
return geo->aux + size_t(geo->numVertices * 4 * i);
}
}
return nullptr;
}
CSFAPI size_t CSFGeometryPartChannel_getSize(CSFGeometryPartChannel channel)
{
return csfGeometryPartChannelSize(channel);
}
CSFAPI const void* CSFGeometry_getPartChannel(const CSFGeometry* geo, CSFGeometryPartChannel channel)
{
size_t offset = CSFGeometry_getPerPartRequiredOffset(geo, geo->numParts, channel);
if(offset != ~0ull)
{
return geo->perpart + offset;
}
return nullptr;
}
CSFAPI size_t CSFGeometry_getPerPartSize(const CSFGeometry* geo)
{
return csfGeometryPerPartSize(geo->numPartChannels, geo->perpartStorageOrder, geo->numParts);
}
CSFAPI size_t CSFGeometry_getPerPartRequiredSize(const CSFGeometry* geo, int numParts)
{
return csfGeometryPerPartSize(geo->numPartChannels, geo->perpartStorageOrder, numParts);
}
CSFAPI size_t CSFGeometry_getPerPartRequiredOffset(const CSFGeometry* geo, int numParts, CSFGeometryPartChannel channel)
{
size_t offset = 0;
for(int i = 0; i < geo->numPartChannels; i++)
{
if(geo->perpartStorageOrder[i] == channel)
{
return offset;
}
offset += csfGeometryPartChannelSize(geo->perpartStorageOrder[i]) * numParts;
}
return ~0ull;
}
static int CSFile_invalidVersion(const CSFile* csf)
{
return csf->magic != CADSCENEFILE_MAGIC || csf->version < CADSCENEFILE_VERSION_COMPAT || csf->version > CADSCENEFILE_VERSION;
}
static size_t CSFile_getHeaderSize(const CSFile* csf)
{
if(csf->version >= CADSCENEFILE_VERSION_META)
{
return sizeof(CSFile);
}
else
{
return offsetof(CSFile, nodeMetas);
}
}
// Adds two unsigned size_t numbers and returns the result. If the sum would
// overflow, sets the provided flag to true and returns 0.
// This is used for range checking; for instance, imagine a file where
// pointersOFFSET = 0x100 and numPointers = 0x20000000`00000001. Then
// pointersOFFSET + numPointers * sizeof(CSFoffset) = 0x108 - so if we didn't
// check for this, both the start and end of the pointer array could appear to
// be in-bounds, even though the header would be invalid.
static size_t CSFile_checkedAdd(const size_t a, const size_t b, bool& overflow)
{
if(a > SIZE_MAX - b) // Safe way of checking a+b > SIZE_MAX without overflow
{
overflow = true;
return 0;
}
return a + b;
}
// Multiplies two unsigned size_t numbers and returns the result. If the sum
// would overflow, sets the provided flag to true and returns 0.
static size_t CSFile_checkedMul(const size_t a, const size_t b, bool& overflow)
{
if(b != 0 && (a > SIZE_MAX / b))
{
overflow = true;
return 0;
}
return a * b;
}
static size_t CSFile_max(const size_t a, const size_t b)
{
if(a > b)
{
return a;
}
return b;
}
// Returns the minimum required size of the file based on the header, or 0 if
// the file fails validation checks. This assumes that csf points to at least
// CSFile_getHeaderSize(csf) bytes of data.
static size_t CSFile_getRawSize(const CSFile* csf)
{
// The header must have a valid version number.
if(CSFile_invalidVersion(csf))
{
return 0;
}
// All length-type fields in the header must not be negative.
if((csf->numPointers < 0) //
|| (csf->numGeometries < 0) //
|| (csf->numMaterials < 0) //
|| (csf->numNodes < 0))
{
return 0;
}
// rootIDX must be in-bounds.
if(csf->rootIDX >= csf->numNodes)
{
return 0;
}
// Determine the minimum file length from the different ranges specified
// in the header.
bool overflow = false;
size_t minimum_file_length = CSFile_getHeaderSize(csf);
minimum_file_length =
CSFile_max(minimum_file_length, CSFile_checkedAdd(csf->pointersOFFSET, csf->numPointers * sizeof(CSFoffset), overflow));
minimum_file_length =
CSFile_max(minimum_file_length, CSFile_checkedAdd(csf->geometriesOFFSET, csf->numGeometries * sizeof(CSFGeometry), overflow));
minimum_file_length =
CSFile_max(minimum_file_length, CSFile_checkedAdd(csf->materialsOFFSET, csf->numMaterials * sizeof(CSFMaterial), overflow));
minimum_file_length =
CSFile_max(minimum_file_length, CSFile_checkedAdd(csf->nodesOFFSET, csf->numNodes * sizeof(CSFNode), overflow));
if(csf->version >= CADSCENEFILE_VERSION_META)
{
if(csf->nodeMetas)
{
minimum_file_length =
CSFile_max(minimum_file_length, CSFile_checkedAdd(csf->nodeMetasOFFSET, csf->numNodes * sizeof(CSFMeta), overflow));
}
if(csf->fileMeta)
{
minimum_file_length = CSFile_max(minimum_file_length, CSFile_checkedAdd(csf->fileMetaOFFSET, sizeof(CSFMeta), overflow));
}
if(csf->geometryMetas)
{
minimum_file_length =
CSFile_max(minimum_file_length,
CSFile_checkedAdd(csf->geometryMetasOFFSET, csf->numGeometries * sizeof(CSFMeta), overflow));
}
}
if(overflow)
{
return 0;
}
return minimum_file_length;
}
//////////////////////////////////////////////////////////////////
// MEMORY
struct CSFileMemory_s
{
CSFLoaderConfig m_config;
std::vector<void*> m_allocations;
std::mutex m_mutex;
std::vector<csfutils::FileReadMapping> m_readMappings;
void* alloc(size_t size, const void* indata = nullptr, size_t indataSize = 0)
{
if(size == 0)
return nullptr;
void* data = malloc(size);
if(indata == CSF_MEMORY_ZEROED_FILL)
{
memset(data, 0, size);
}
else if(indata)
{
indataSize = indataSize ? indataSize : size;
memcpy(data, indata, indataSize);
}
{
std::lock_guard<std::mutex> lock(m_mutex);
m_allocations.push_back(data);
}
return data;
}
template <typename T>
T* allocT(size_t size, const T* indata, size_t indataSize = 0)
{
return (T*)alloc(size, indata, indataSize);
}
CSFileMemory_s()
{
m_config.secondariesReadOnly = 0;
m_config.validate = 1;
m_config.gltfFindUniqueGeometries = 1;
}
~CSFileMemory_s()
{
size_t numThreads = (std::thread::hardware_concurrency() + 1) / 2;
if(m_allocations.size() > 2048)
{
std::vector<std::thread> threads(numThreads);
size_t range = (m_allocations.size() + numThreads - 1) / numThreads;
for(size_t t = 0; t < numThreads; t++)
{
threads[t] = std::thread(
[&](size_t offset, size_t range) {
for(size_t i = offset; i < std::min(offset + range, m_allocations.size()); i++)
{
free(m_allocations[i]);
}
},
t * range, range);
}
for(size_t t = 0; t < numThreads; t++)
{
threads[t].join();
}
}
else
{
for(size_t i = 0; i < m_allocations.size(); i++)
{
free(m_allocations[i]);
}
}
m_readMappings.clear();
}
};
CSFAPI CSFileMemoryPTR CSFileMemory_new()
{
return new CSFileMemory_s;
}
CSFAPI CSFileMemoryPTR CSFileMemory_newCfg(const CSFLoaderConfig* config)
{
CSFileMemoryPTR mem = new CSFileMemory_s;
mem->m_config = *config;
return mem;
}
CSFAPI void CSFileMemory_delete(CSFileMemoryPTR mem)
{
delete mem;
}
CSFAPI void* CSFileMemory_alloc(CSFileMemoryPTR mem, size_t sz, const void* fill)
{
return mem->alloc(sz, fill);
}
CSFAPI void* CSFileMemory_allocPartial(CSFileMemoryPTR mem, size_t sz, size_t szPartial, const void* fillPartial)
{
return mem->alloc(sz, szPartial == 0 ? nullptr : fillPartial, szPartial);
}
CSFAPI void CSFileMemory_getCfg(CSFileMemoryPTR mem, CSFLoaderConfig* config)
{
memcpy(config, &mem->m_config, sizeof(CSFLoaderConfig));
}
CSFAPI int CSFileMemory_areSecondariesReadOnly(CSFileMemoryPTR mem)
{
return mem->m_config.secondariesReadOnly;
}
//////////////////////////////////////////////////////////////////////////
// LOADING
struct CSFileHandle_s
{
FILE* m_file;
CSFile m_header;
size_t m_fileSize;
void close()
{
if(m_file)
{
fclose(m_file);
m_file = nullptr;
m_fileSize = 0;
}
}
void seek(CSFoffset offset, int pos) { xfseek(m_file, offset, pos); }
CSFoffset tell() { return xftell(m_file); }
// returns number of bytes read (either matches dataSize or is 0)
size_t read(void* data, size_t dataSize) { return xfreads(data, dataSize, dataSize, 1, m_file) * dataSize; }
int open(const char* filename)
{
int result = 0;
#ifdef WIN32
result = !fopen_s(&m_file, filename, "rb");
#else
m_file = fopen(filename, "rb");
result = (m_file) != nullptr;
#endif
if(!result)
{
return CADSCENEFILE_ERROR_NOFILE;
}
size_t sizeshould = 0;
if(read(&m_header, sizeof(m_header)) != sizeof(m_header))
{
return CADSCENEFILE_ERROR_VERSION;
}
// zero things as expected
size_t headerSize = CSFile_getHeaderSize(&m_header);
size_t delta = sizeof(m_header) - headerSize;
if(delta)
{
memset(((char*)&m_header) + (sizeof(m_header) - delta), 0, delta);
}
CSFile_versionFixHeader(&m_header);
sizeshould = CSFile_getRawSize(&m_header);
if(sizeshould == 0)
{
return CADSCENEFILE_ERROR_VERSION;
}
seek(0, SEEK_END);
m_fileSize = tell();
seek(0, SEEK_SET);
if(m_fileSize < sizeshould)
{
return CADSCENEFILE_ERROR_VERSION;
}
return CADSCENEFILE_NOERROR;
}
size_t read(CSFoffset offset, void* data, size_t dataSize)
{
seek(offset, SEEK_SET);
size_t readSize = read(data, dataSize);
return readSize;
}
void* allocAndRead(CSFoffset offset, size_t size, CSFileMemoryPTR mem)
{
if(offset == 0 || size == 0)
{
return nullptr;
}
void* data = mem->alloc(size);
seek(offset, SEEK_SET);
size_t readSize = read(data, size);
if(size != readSize)
{
return nullptr;
}
return data;
}
template <class T>
T* allocAndReadT(CSFoffset offset, uint32_t num, CSFileMemoryPTR mem, int valid = 1)
{
if(!valid)
{
return nullptr;
}
T* data = (T*)allocAndRead(offset, sizeof(T) * num, mem);
return data;
}
CSFileHandle_s() { memset(this, 0, sizeof(CSFileHandle_s)); }
~CSFileHandle_s() { close(); }
};
template <typename T>
static void fixPointer(T*& ptr, CSFoffset offset, void* base)
{
if(offset)
{
ptr = (T*)(((uint8_t*)base) + offset);
}
}
static void CSFile_fixSecondaryPointers(CSFile* csf, void* base)
{
// setup pointers
for(int m = 0; m < csf->numMaterials; m++)
{
CSFMaterial& material = csf->materials[m];
fixPointer(material.bytes, material.bytesOFFSET, base);
}
for(int g = 0; g < csf->numGeometries; g++)
{
CSFGeometry& geo = csf->geometries[g];
fixPointer(geo.vertex, geo.vertexOFFSET, base);
fixPointer(geo.normal, geo.normalOFFSET, base);
fixPointer(geo.tex, geo.texOFFSET, base);
fixPointer(geo.indexSolid, geo.indexSolidOFFSET, base);
fixPointer(geo.indexWire, geo.indexWireOFFSET, base);
fixPointer(geo.parts, geo.partsOFFSET, base);
fixPointer(geo.auxStorageOrder, geo.auxStorageOrderOFFSET, base);
fixPointer(geo.aux, geo.auxOFFSET, base);
fixPointer(geo.perpart, geo.perpartOFFSET, base);
fixPointer(geo.perpartStorageOrder, geo.perpartStorageOrderOFFSET, base);
}
csfutils::parallel_ranges(csf->numNodes, [&](uint64_t idxBegin, uint64_t idxEnd, uint32_t threadIndex, void* userData) {
for(int i = int(idxBegin); i < int(idxEnd); i++)
{
CSFNode& node = csf->nodes[i];
fixPointer(node.children, node.childrenOFFSET, base);
fixPointer(node.parts, node.partsOFFSET, base);
}
});
if(CSFile_getGeometryMetas(csf))
{
for(int g = 0; g < csf->numGeometries; g++)
{
CSFMeta& meta = csf->geometryMetas[g];
fixPointer(meta.bytes, meta.bytesOFFSET, base);
}
}
if(CSFile_getNodeMetas(csf))
{
csfutils::parallel_ranges(csf->numNodes, [&](uint64_t idxBegin, uint64_t idxEnd, uint32_t threadIndex, void* userData) {
for(int i = int(idxBegin); i < int(idxEnd); i++)
{
CSFMeta& meta = csf->nodeMetas[i];
fixPointer(meta.bytes, meta.bytesOFFSET, base);
}
});
}
if(CSFile_getFileMeta(csf))
{
CSFMeta& meta = csf->fileMeta[0];
fixPointer(meta.bytes, meta.bytesOFFSET, base);
}
}
//////////////////////////////////////////////////////////////////////////
// hasContent means the pointers within the struct are loaded as well
static inline void csfPostLoadFile(CSFile* csf, bool hasContent = true)
{
if(csf->version < CADSCENEFILE_VERSION_FILEFLAGS)
{
csf->fileFlags = csf->fileFlags ? CADSCENEFILE_FLAG_UNIQUENODES : 0;
}
csf->numPointers = 0;
csf->pointers = nullptr;
}
static inline void csfPostLoadMaterial(const CSFile* csf, CSFMaterial* material, bool hasContent = true) {}
static inline void csfPostLoadGeometry(const CSFile* csf, CSFGeometry* geo, bool hasContent = true)
{
CSFGeometry_clearDeprecated(geo);
if(csf->version < CADSCENEFILE_VERSION_GEOMETRYCHANNELS)
{
CSFGeometry_setupDefaultChannels(geo);
}
}
static inline void csfPostLoadNode(const CSFile* csf, CSFNode* node, bool hasContent = true)
{
if(csf->version >= CADSCENEFILE_VERSION_PARTNODEIDX || !hasContent)
return;
// node->parts is only guaranteed to be valid if geometryIDX >= 0:
if(node->geometryIDX >= 0)
{
for(int p = 0; p < node->numParts; p++)
{
node->parts[p].nodeIDX = -1;
}
}
}
// Returns whether an array defined by `ptr` and `numElements` is contained
// within the range of bytes specified by `csf` and `csf_size`, not including
// the header.
template <class T, class CountType>
static bool CSFile_validateRange(const T* ptr, CountType numElements, const CSFile* csf, size_t csf_size)
{
if(numElements < 0)
return false;
// Always allow a range of length 0, even if ptr is not valid. This occurs on
// worldcar.csf.gz, for instance.
if(numElements == 0)
return true;
// Two casts here to ensure cast from void* to uintptr_t is valid, since only
// T* -> void* and void* -> uintptr_t are defined.
const uintptr_t ptr_as_number = reinterpret_cast<uintptr_t>(static_cast<const void*>(ptr));
const uintptr_t csf_as_number = reinterpret_cast<uintptr_t>(static_cast<const void*>(csf));
// We say that csf->pointers is the only array which can point to other values
// in the header. Otherwise, it is very easy for objects to contain pointers
// into the header - and when functions like CSFile_setupDefaultChannels
// modify these objects, they could make the header invalid!
if(ptr_as_number < (csf_as_number + CSFile_getHeaderSize(csf)))
return false;
if(ptr_as_number % alignof(T) != 0)
{
return false;
}
static_assert(std::is_same<size_t, uintptr_t>::value,
"Behavior of CSFile_validatePointer requires size_t and uintptr_t to be equivalent!");
bool overflow = false;
const size_t address_end = CSFile_checkedAdd(csf_as_number, csf_size, overflow);
const size_t array_size = CSFile_checkedMul(sizeof(T), static_cast<size_t>(numElements), overflow);
const size_t pointer_end = CSFile_checkedAdd(ptr_as_number, array_size, overflow);
if(overflow || (pointer_end > address_end))
return false;
return true;
}
// Returns whether a CSFMeta array is within bounds.
static bool CSFile_validateMetaArray(const CSFMeta* meta_ptr, int numElements, const CSFile* csf, size_t csf_size)
{
if(!CSFile_validateRange(meta_ptr, numElements, csf, csf_size))
return false;
int hadError = 0;
csfutils::parallel_ranges(
numElements,
[&](uint64_t idxBegin, uint64_t idxEnd, uint32_t threadIdx, void* userData) {
for(uint64_t m = idxBegin; m < idxEnd; m++)
{
const CSFMeta& meta = meta_ptr[m];
if(!CSFile_validateRange(meta.bytes, meta.numBytes, csf, csf_size))
{
hadError = 1;
break;
}
}
},
nullptr, 1024 * 256);
return hadError == 0;
}
// Returns whether all ranges are within bounds. This will catch if
// csf->pointers didn't include some pointers.
static bool CSFile_validateAllRanges(const CSFile* csf, size_t csf_size)
{
if(!CSFile_validateRange(csf->geometries, csf->numGeometries, csf, csf_size))
return false;
if(csf->version >= CADSCENEFILE_VERSION_GEOMETRYCHANNELS)
{
for(int g = 0; g < csf->numGeometries; g++)
{
const CSFGeometry& geo = csf->geometries[g];
if(geo.numNormalChannels < 0 || geo.numTexChannels < 0 || geo.numAuxChannels < 0 || geo.numPartChannels < 0
|| geo.numParts < 0 || geo.numVertices < 0 || geo.numIndexSolid < 0 || geo.numIndexWire < 0)
return false;
if(!CSFile_validateRange(geo.auxStorageOrder, geo.numAuxChannels, csf, csf_size))
return false;
bool overflow = false;
const size_t aux_count = CSFile_checkedMul(CSFile_checkedMul(4, geo.numVertices, overflow), geo.numAuxChannels, overflow);
if(overflow)
return false;
if(!CSFile_validateRange(geo.aux, aux_count, csf, csf_size))
return false;
if(!CSFile_validateRange(geo.perpartStorageOrder, geo.numPartChannels, csf, csf_size))
return false;
// For simplicity here, we require that
// geo.numPartChannels * sizeof(CSFGeometryPartBbox) * geo.numParts <= SIZE_MAX -
// that ensures that these functions called here don't overflow.
CSFile_checkedMul(sizeof(CSFGeometryPartBbox), CSFile_checkedMul(geo.numPartChannels, geo.numParts, overflow), overflow);
if(overflow)
return false;
const size_t perpart_size = CSFGeometry_getPerPartSize(&geo);
if(!CSFile_validateRange(geo.perpart, perpart_size, csf, csf_size))
return false;
const size_t vertex_num_elements = CSFile_checkedMul(3, geo.numVertices, overflow);
if(overflow || !CSFile_validateRange(geo.vertex, vertex_num_elements, csf, csf_size))
return false;
const size_t normal_num_elements = CSFile_checkedMul(vertex_num_elements, geo.numNormalChannels, overflow);
if(overflow || !CSFile_validateRange(geo.normal, normal_num_elements, csf, csf_size))
return false;
const size_t tex_num_elements =
CSFile_checkedMul(2, CSFile_checkedMul(geo.numVertices, geo.numTexChannels, overflow), overflow);
if(!CSFile_validateRange(geo.tex, tex_num_elements, csf, csf_size))
return false;
if(!CSFile_validateRange(geo.indexSolid, geo.numIndexSolid, csf, csf_size))
return false;
if(!CSFile_validateRange(geo.indexWire, geo.numIndexWire, csf, csf_size))
return false;
if(!CSFile_validateRange(geo.parts, geo.numParts, csf, csf_size))
return false;
}
}
if(!CSFile_validateRange(csf->materials, csf->numMaterials, csf, csf_size))
return false;
for(int m = 0; m < csf->numMaterials; m++)
{
if(!CSFile_validateRange(csf->materials[m].bytes, csf->materials[m].numBytes, csf, csf_size))
return false;
}
if(!CSFile_validateRange(csf->nodes, csf->numNodes, csf, csf_size))
return false;
int hadError = 0;
csfutils::parallel_ranges(csf->numNodes, [&](uint64_t idxBegin, uint64_t idxEnd, uint32_t threadIdx, void* userData) {
for(uint64_t n = idxBegin; n < idxEnd; n++)
{
CSFNode& node = csf->nodes[n];
if(node.geometryIDX >= 0)
{
if(node.geometryIDX >= csf->numGeometries)
{
hadError = 1;
break;
}
CSFGeometry& geo = csf->geometries[node.geometryIDX];
if(node.numParts != geo.numParts)
{
hadError = 1;
break;
}
if(!CSFile_validateRange(node.parts, node.numParts, csf, csf_size))
{
hadError = 1;
break;
}
}
if(!CSFile_validateRange(node.children, node.numChildren, csf, csf_size))
{
hadError = 1;
break;
}
}
});
if(hadError)
{
return false;
}
if(csf->version >= CADSCENEFILE_VERSION_META)
{
if(csf->nodeMetas)
{
if(!CSFile_validateMetaArray(csf->nodeMetas, csf->numNodes, csf, csf_size))
return false;
}
if(csf->geometryMetas)
{
if(!CSFile_validateMetaArray(csf->geometryMetas, csf->numGeometries, csf, csf_size))
return false;
}
if(csf->fileMeta)
{
if(!CSFile_validateMetaArray(csf->fileMeta, 1, csf, csf_size))
return false;
}
}
return true;
}
CSFAPI int CSFile_loadRaw(CSFile* outcsf, size_t size, void* dataraw, int validate)
{
char* data = (char*)dataraw;
CSFile* csf = (CSFile*)data;
if(size_t(outcsf) >= size_t(dataraw) && size_t(outcsf) < (size_t(dataraw) + size))
{
return CADSCENEFILE_ERROR_OPERATION;
}
if(size < sizeof(CSFile) || CSFile_invalidVersion(csf))
{
return CADSCENEFILE_ERROR_VERSION;
}
CSFile_versionFixHeader(csf);
{
const size_t required_minimum_size = CSFile_getRawSize(csf);
if((required_minimum_size == 0) || (size < required_minimum_size))
{
// The file was truncated, or the header pointed to data that was out of bounds.
return CADSCENEFILE_ERROR_VERSION;
}
}
// Pointers are saved relative to the start of the file. Modify them so that
// they point to locations in memory. Also check that alignments are OK to
// avoid undefined behavior. Finally, check that each element of csf->pointers
// points to csf->geometries or later - otherwise, this shift could change the
// header, or even the location of the pointers array itself!
if(validate && csf->pointersOFFSET % alignof(CSFoffset) != 0)
{
return CADSCENEFILE_ERROR_INVALID;
}
csf->pointersOFFSET += (CSFoffset)csf;
int hadError = 0;
csfutils::parallel_ranges(
csf->numPointers,
[&](uint64_t idxBegin, uint64_t idxEnd, uint32_t threadIdx, void* userData) {
for(uint64_t i = idxBegin; i < idxEnd; i++)
{
CSFoffset offsetFromStart;
// support unaligned loads
const uint8_t* pointerData = (const uint8_t*)csf->pointers;
memcpy(&offsetFromStart, pointerData + sizeof(CSFoffset) * i, sizeof(CSFoffset));
if(validate
&& ((offsetFromStart > static_cast<CSFoffset>(size) - sizeof(CSFoffset*)) //
|| (offsetFromStart < offsetof(CSFile, geometries)) //
|| (offsetFromStart % alignof(CSFoffset) != 0)))
{
hadError = 1;
break;
}
else
{
#if 0
CSFoffset* ptr = (CSFoffset*)(data + offsetFromStart);
*(ptr) += (CSFoffset)csf;
#else
// support unaligned loads
CSFoffset pointer;
memcpy(&pointer, data + offsetFromStart, sizeof(CSFoffset));
pointer += (CSFoffset)csf;
memcpy(data + offsetFromStart, &pointer, sizeof(CSFoffset));
#endif
}
}
},
nullptr, 1024 * 256);
// remove pointer information
csf->pointers = 0;
csf->numPointers = 0;
if(hadError)
{
return CADSCENEFILE_ERROR_INVALID;
}
// Check that pointers really contained all pointers in the file.
if(validate && !CSFile_validateAllRanges(csf, size))
{
return CADSCENEFILE_ERROR_INVALID;
}
int version = csf->version;
int numMaterials = csf->numMaterials;
for(int i = 0; i < numMaterials; i++)
{
csfPostLoadMaterial(csf, csf->materials + i);
}
int numGeometries = csf->numGeometries;
for(int i = 0; i < numGeometries; i++)
{
csfPostLoadGeometry(csf, csf->geometries + i);
}
int numNodes = csf->numNodes;
for(int i = 0; i < numNodes; i++)
{
csfPostLoadNode(csf, csf->nodes + i);
}
csfPostLoadFile(csf);
memset(outcsf, 0, sizeof(CSFile));
memcpy(outcsf, csf, CSFile_getHeaderSize(csf));
outcsf->fileMeta = (CSFMeta*)CSFile_getFileMeta(csf);
outcsf->geometryMetas = (CSFMeta*)CSFile_getGeometryMetas(csf);
outcsf->nodeMetas = (CSFMeta*)CSFile_getNodeMetas(csf);
return CADSCENEFILE_NOERROR;
}
CSFAPI int CSFile_loadReadOnly(CSFile** outcsf, const char* filename, CSFileMemoryPTR mem)
{
#if CSF_DISABLE_FILEMAPPING_SUPPORT
return CSFile_load(outcsf, filename, mem);
#else
csfutils::FileReadMapping file;
if(!file.open(filename))
{
return CADSCENEFILE_ERROR_NOFILE;
}
const uint8_t* base = (const uint8_t*)file.data();
// allocate the primary arrays
CSFile* csf = mem->allocT(sizeof(CSFile), (const CSFile*)nullptr);
memset(csf, 0, sizeof(CSFile));
memcpy(csf, base, CSFile_getHeaderSize((const CSFile*)base));
CSFile_versionFixHeader(csf);
csf->materials = mem->allocT(sizeof(CSFMaterial) * csf->numMaterials, (const CSFMaterial*)(base + csf->materialsOFFSET));
csf->geometries = mem->allocT(sizeof(CSFGeometry) * csf->numGeometries, (const CSFGeometry*)(base + csf->geometriesOFFSET));
csf->nodes = mem->allocT(sizeof(CSFNode) * csf->numNodes, (const CSFNode*)(base + csf->nodesOFFSET));
csf->pointers = 0;
csf->numPointers = 0;
if(CSFile_getGeometryMetas(csf))
{
csf->geometryMetas = mem->allocT(sizeof(CSFMeta) * csf->numGeometries, (const CSFMeta*)(base + csf->geometryMetasOFFSET));
}
else
{
csf->geometryMetas = nullptr;
}
if(CSFile_getNodeMetas(csf))
{
csf->nodeMetas = mem->allocT(sizeof(CSFMeta) * csf->numNodes, (const CSFMeta*)(base + csf->nodeMetasOFFSET));
}
else
{
csf->nodeMetas = nullptr;
}
if(CSFile_getFileMeta(csf))
{
csf->fileMeta = mem->allocT(sizeof(CSFMeta), (const CSFMeta*)(base + csf->fileMetaOFFSET));
}
else
{
csf->fileMeta = nullptr;
}
if(csf->version < CADSCENEFILE_VERSION_GEOMETRYCHANNELS)
{
CSFile_setupDefaultChannels(csf);
}
CSFile_fixSecondaryPointers(csf, const_cast<void*>((const void*)base));
mem->m_readMappings.push_back(std::move(file));
*outcsf = csf;
return CADSCENEFILE_NOERROR;
#endif
}
CSFAPI int CSFile_load(CSFile** outcsf, const char* filename, CSFileMemoryPTR mem)
{
CSFileHandle_s fileHandle;
int result = fileHandle.open(filename);
if(result != CADSCENEFILE_NOERROR)
{
*outcsf = 0;
return result;
}
if(mem->m_config.secondariesReadOnly)
{
fileHandle.close();
return CSFile_loadReadOnly(outcsf, filename, mem);
}
else
{
size_t dataSize = fileHandle.m_fileSize;
char* data = (char*)CSFileMemory_alloc(mem, dataSize, nullptr);
size_t readSize = fileHandle.read(data, dataSize);
if(readSize != dataSize)
{
return CADSCENEFILE_ERROR_INVALID;
}
fileHandle.close();
CSFile* newHeader = (CSFile*)CSFileMemory_alloc(mem, sizeof(CSFile), nullptr);
int err = CSFile_loadRaw(newHeader, dataSize, data, mem->m_config.validate);
if(err != CADSCENEFILE_NOERROR)
{
return err;
}
*outcsf = newHeader;
return CADSCENEFILE_NOERROR;
}
}
#if CSF_SUPPORT_GLTF2
CSFAPI int CSFile_loadGTLF(CSFile** outcsf, const char* filename, CSFileMemoryPTR mem);
#endif
CSFAPI int CSFile_loadExt(CSFile** outcsf, const char* filename, CSFileMemoryPTR mem)
{
if(!filename)
{
return CADSCENEFILE_ERROR_NOFILE;
}
size_t len = strlen(filename);
#if CSF_SUPPORT_ZLIB
if(len > 3 && strcmp(filename + len - 3, ".gz") == 0)
{
gzFile filegz = gzopen(filename, "rb");
if(!filegz)
{
*outcsf = 0;
return CADSCENEFILE_ERROR_NOFILE;
}
CSFile header = {0};
size_t sizeshould = 0;
if(!gzread(filegz, &header, (z_off_t)sizeof(header)) || (sizeshould = CSFile_getRawSize(&header)) == 0)
{
gzclose(filegz);
*outcsf = 0;
return CADSCENEFILE_ERROR_VERSION;
}
gzseek(filegz, 0, SEEK_SET);
char* data = (char*)CSFileMemory_alloc(mem, sizeshould, 0);
if(!gzread(filegz, data, (z_off_t)sizeshould))
{
gzclose(filegz);
*outcsf = 0;
return CADSCENEFILE_ERROR_VERSION;
}
gzclose(filegz);
CSFile* csf = (CSFile*)CSFileMemory_alloc(mem, sizeof(CSFile), 0);
int result = CSFile_loadRaw(csf, sizeshould, data, mem->m_config.validate);
*outcsf = result == CADSCENEFILE_NOERROR ? csf : 0;
return result;
}
else
#endif
#if CSF_SUPPORT_GLTF2
if(len > 5 && strcmp(filename + len - 5, ".gltf") == 0)
{
return CSFile_loadGTLF(outcsf, filename, mem);
}
#endif
{
return CSFile_load(outcsf, filename, mem);
}
}
//////////////////////////////////////////////////////////////////////////
// SAVING
struct OutputFILE
{
#if CSF_DISABLE_FILEMAPPING_SUPPORT
FILE* m_file;
int open(const char* filename, size_t filesize)
{
#ifdef WIN32
return fopen_s(&m_file, filename, "wb");
#else
m_file = fopen(filename, "wb");
return (m_file) != nullptr;
#endif
}
void close() { fclose(m_file); }
void write(size_t offset, const void* data, size_t dataSize)
{
xfseek(m_file, offset, SEEK_SET);
fwrite(data, dataSize, 1, m_file);
}
#else
csfutils::FileReadOverWriteMapping m_mapping;
#endif
int open(const char* filename, size_t filesize) { return m_mapping.open(filename, filesize) != true; }
void close() { m_mapping.close(); }
void write(size_t offset, const void* data, size_t dataSize)
{
memcpy(((uint8_t*)m_mapping.data()) + offset, data, dataSize);
}
};
struct OutputBuf
{
char* m_data;
size_t m_allocated;
size_t m_used;
size_t m_cur;
int open(const char* filename, size_t filesize)
{
m_allocated = filesize;
m_data = (char*)malloc(m_allocated);
m_used = 0;
m_cur = 0;
return 0;
}
void close()
{
if(m_data)
{
free(m_data);
}
m_data = 0;
m_allocated = 0;
m_used = 0;
m_cur = 0;
}
void write(size_t offset, const void* data, size_t dataSize) { memcpy(m_data + offset, data, dataSize); }
};
#if CSF_SUPPORT_ZLIB
struct OutputGZ
{
gzFile m_file;
OutputBuf m_buf;
int open(const char* filename, size_t filesize)
{
m_buf.open(filename, filesize);
m_file = gzopen(filename, "wb");
return m_file == 0;
}
void close()
{
gzwrite(m_file, m_buf.m_data, (z_off_t)m_buf.m_used);
gzclose(m_file);
m_buf.close();
}
void write(size_t offset, const void* data, size_t dataSize) { m_buf.write(offset, data, dataSize); }
};
#endif
struct CSFSerializerInfo
{
int objIndex = -1;
size_t objSize = 0;
const void* obj = nullptr;
size_t objStoreOffset = 0;
uint32_t threadIndex = 0;
};
template <class T>
struct CSFOffsetMgr
{
T& m_file;
int m_version = CADSCENEFILE_VERSION;
int m_pass = -1;
// serial file length, start after header
size_t m_current = sizeof(CSFile);
size_t m_numLocations = 0;
// patch list for pointers
struct PatchEntry
{
CSFoffset offset;
CSFoffset location;
};
std::vector<PatchEntry> m_patches;
// output pointer locations
std::vector<CSFoffset> m_locations;
// in-place fixup of objects' pointers needs temporary data
// we provide one per thread
size_t m_tempObjSizeMax = 0;
std::vector<uint8_t> m_tempObjData;
// current pass's object offsets list
std::vector<size_t>* m_objectOffsets = nullptr;
CSFOffsetMgr(T& file)
: m_file(file)
{
size_t objSize = 0;
objSize = std::max(objSize, sizeof(CSFNode));
objSize = std::max(objSize, sizeof(CSFGeometry));
objSize = std::max(objSize, sizeof(CSFMaterial));
objSize = std::max(objSize, sizeof(CSFMeta));
m_tempObjSizeMax = objSize;
m_tempObjData.resize(m_tempObjSizeMax * CSF_DEFAULT_NUM_THREADS);
}
void setPass(int pass) { m_pass = pass; }
void setObjects(std::vector<size_t>* objectOffsets) { m_objectOffsets = objectOffsets; }
size_t writePadding(size_t start, size_t padding)
{
static const uint32_t padBufferSize = 16;
static const uint8_t padBuffer[padBufferSize] = {0};
if(!padding)
return 0;
size_t paddingLeft = padding;
while(paddingLeft)
{
size_t padCurrent = paddingLeft > padBufferSize ? padBufferSize : paddingLeft;
m_file.write(start, padBuffer, padCurrent);
paddingLeft -= padCurrent;
start += padCurrent;
}
return padding;
}
size_t handleAlignment(size_t current, size_t alignment)
{
// always align to 4 bytes at least
alignment = alignment < 4 ? 4 : alignment;
size_t rest = current % alignment;
size_t padding = rest ? alignment - rest : 0;
current += padding;
return current;
}
void storeHeader(const void* data, size_t dataSize)
{
assert(dataSize == sizeof(CSFile));
m_file.write(0, data, dataSize);
}
void beginObjectReference(const CSFSerializerInfo& info)
{
assert(info.objIndex >= 0 && info.obj && info.objSize);
if(m_pass == 0)
{
// record content offset for this object
// we need this to later re-do the various stores relative
assert(info.objIndex <= m_objectOffsets->size());
m_objectOffsets->at(info.objIndex) = m_current;
}
else
{
// object pass, copy in the object to temp
memcpy(m_tempObjData.data() + m_tempObjSizeMax * info.threadIndex, info.obj, info.objSize);
}
}
void endObjectReference(const CSFSerializerInfo& info)
{
assert(info.objIndex >= 0 && info.obj && info.objSize);
if(m_pass == 0)
return;
// finish old object
m_file.write(info.objStoreOffset, m_tempObjData.data() + m_tempObjSizeMax * info.threadIndex, info.objSize);
}
size_t storeArray(size_t location, size_t dataSize, size_t alignment)
{
if(m_pass == 0)
{
size_t old = m_current;
size_t newBegin = handleAlignment(old, alignment);
m_current = newBegin;
m_current += dataSize;
// arrays stored in last element
m_objectOffsets->at(m_objectOffsets->size() - 1) = old;
// without object we also record the patch
m_patches.push_back({newBegin, location});
// and the final pointer location
m_locations.push_back(location);
return newBegin;
}
else
{
size_t old = m_objectOffsets->at(m_objectOffsets->size() - 1);
size_t newBegin = handleAlignment(old, alignment);
writePadding(old, newBegin - old);
return newBegin;
}
}
size_t storeObjectData(const CSFSerializerInfo& info, size_t location, const void* data, size_t dataSize, size_t alignment)
{
// in content pass or when no object is used
// we just dump the data to file
if(m_pass == 0)
{
size_t old = m_current;
size_t newBegin = handleAlignment(old, alignment);
m_current = newBegin;
m_current += dataSize;
// final pointer location is relative to m_storeOffset of current object
m_locations.push_back(location + info.objStoreOffset);
return newBegin;
}
else
{
// modify object's content offset in-place
size_t& current = m_objectOffsets->at(info.objIndex);
size_t newBegin = handleAlignment(current, alignment);
writePadding(current, newBegin - current);
current = newBegin;
m_file.write(current, data, dataSize);
current += dataSize;
// modify temp object
CSFoffset* localOffset = (CSFoffset*)(m_tempObjData.data() + m_tempObjSizeMax * info.threadIndex + location);
*localOffset = newBegin;
return newBegin;
}
}
size_t getFileSize()
{
// add pointer table to end
size_t current = handleAlignment(m_current, alignof(CSFoffset));
current += sizeof(CSFoffset) * m_locations.size();
return current;
}
bool finalize(size_t fileSize, size_t tableCountLocation, size_t tableLocation)
{
int num = int(m_locations.size());
if(size_t(num) != m_locations.size())
return false;
m_file.write(tableCountLocation, &num, sizeof(int));
size_t old = m_current;
size_t tableBegin = handleAlignment(m_current, alignof(CSFoffset));
m_current += writePadding(m_current, tableBegin - old);
m_file.write(tableLocation, &tableBegin, sizeof(CSFoffset));
// dump table
m_file.write(tableBegin, m_locations.data(), sizeof(CSFoffset) * m_locations.size());
m_current += sizeof(CSFoffset) * m_locations.size();
// patch a few pointers retroactively
for(ptrdiff_t i = 0; i < ptrdiff_t(m_patches.size()); i++)
{
m_file.write(m_patches[i].location, &m_patches[i].offset, sizeof(CSFoffset));
}
return m_current == fileSize;
}
};
namespace CSFSerializer {
// Class is used to handle both load and save depending on template.
// MUST not use functions or values that depend on array content.
template <typename T>
static void processGeometry(T& ser, uint32_t threadIndex, int index, const CSFGeometry* geo, CSFoffset geomOFFSET, size_t perPartSize)
{
CSFSerializerInfo info;
info.obj = geo;
info.objSize = sizeof(CSFGeometry);
info.objIndex = index;
info.objStoreOffset = geomOFFSET;
info.threadIndex = threadIndex;
ser.beginObjectReference(info);
if(geo->vertex && geo->numVertices)
{
ser.storeObjectData(info, offsetof(CSFGeometry, vertexOFFSET), geo->vertex, sizeof(float) * 3 * geo->numVertices,
alignof(float));
}
if(geo->normal && geo->numVertices)
{
ser.storeObjectData(info, offsetof(CSFGeometry, normalOFFSET), geo->normal,
sizeof(float) * 3 * geo->numVertices * geo->numNormalChannels, alignof(float));
}
if(geo->tex && geo->numVertices)
{
ser.storeObjectData(info, offsetof(CSFGeometry, texOFFSET), geo->tex,
sizeof(float) * 2 * geo->numVertices * geo->numTexChannels, alignof(float));
}
if(geo->indexSolid && geo->numIndexSolid)
{
ser.storeObjectData(info, offsetof(CSFGeometry, indexSolidOFFSET), geo->indexSolid,
sizeof(uint32_t) * geo->numIndexSolid, alignof(uint32_t));
}
if(geo->indexWire && geo->numIndexWire)
{
ser.storeObjectData(info, offsetof(CSFGeometry, indexWireOFFSET), geo->indexWire,
sizeof(uint32_t) * geo->numIndexWire, alignof(uint32_t));
}
if(geo->parts && geo->numParts)
{
ser.storeObjectData(info, offsetof(CSFGeometry, partsOFFSET), geo->parts, sizeof(CSFGeometryPart) * geo->numParts,
alignof(CSFGeometryPart));
}
if(ser.m_version >= CADSCENEFILE_VERSION_GEOMETRYCHANNELS)
{
if(geo->aux && geo->numVertices)
{
ser.storeObjectData(info, offsetof(CSFGeometry, auxOFFSET), geo->aux,
sizeof(float) * 4 * geo->numVertices * geo->numAuxChannels, alignof(float));
}
if(geo->auxStorageOrder && geo->numAuxChannels)
{
ser.storeObjectData(info, offsetof(CSFGeometry, auxStorageOrderOFFSET), geo->auxStorageOrder,
sizeof(CSFGeometryAuxChannel) * geo->numAuxChannels, alignof(CSFGeometryAuxChannel));
}
if(geo->perpartStorageOrder && geo->numPartChannels)
{
ser.storeObjectData(info, offsetof(CSFGeometry, perpartStorageOrder), geo->perpartStorageOrder,
sizeof(CSFGeometryPartChannel) * geo->numPartChannels, alignof(CSFGeometryPartChannel));
}
if(geo->perpart && geo->numPartChannels && perPartSize)
{
ser.storeObjectData(info, offsetof(CSFGeometry, perpart), geo->perpart, perPartSize, 16);
}
}
ser.endObjectReference(info);
}
template <typename T>
static void processMaterial(T& ser, uint32_t threadIndex, int index, const CSFMaterial* mat, CSFoffset matOFFSET)
{
CSFSerializerInfo info;
info.obj = mat;
info.objSize = sizeof(CSFMaterial);
info.objIndex = index;
info.objStoreOffset = matOFFSET;
info.threadIndex = threadIndex;
ser.beginObjectReference(info);
if(mat->bytes && mat->numBytes)
{
ser.storeObjectData(info, offsetof(CSFMaterial, bytesOFFSET), mat->bytes, sizeof(unsigned char) * mat->numBytes,
alignof(unsigned char));
}
ser.endObjectReference(info);
}
template <typename T>
static void processNode(T& ser, uint32_t threadIndex, int index, const CSFNode* node, CSFoffset nodeOFFSET)
{
CSFSerializerInfo info;
info.obj = node;
info.objSize = sizeof(CSFNode);
info.objIndex = index;
info.objStoreOffset = nodeOFFSET;
info.threadIndex = threadIndex;
ser.beginObjectReference(info);
if(node->parts && node->numParts)
{
ser.storeObjectData(info, offsetof(CSFNode, partsOFFSET), node->parts, sizeof(CSFNodePart) * node->numParts, alignof(CSFNodePart));
}
if(node->children && node->numChildren)
{
ser.storeObjectData(info, offsetof(CSFNode, childrenOFFSET), node->children, sizeof(int) * node->numChildren, alignof(int));
}
ser.endObjectReference(info);
}
template <typename T>
static inline void processMeta(T& ser, uint32_t threadIndex, int index, const CSFMeta* meta, CSFoffset metaOFFSET)
{
CSFSerializerInfo info;
info.obj = meta;
info.objSize = sizeof(CSFMeta);
info.objIndex = index;
info.objStoreOffset = metaOFFSET;
info.threadIndex = threadIndex;
ser.beginObjectReference(info);
if(meta->bytes && meta->numBytes)
{
ser.storeObjectData(info, offsetof(CSFMeta, bytesOFFSET), meta->bytes, sizeof(unsigned char) * meta->numBytes, 16);
}
ser.endObjectReference(info);
}
}; // namespace CSFSerializer
template <class T>
static inline void processMetaArray(CSFOffsetMgr<T>& mgr, const CSFile* csf, int numMetas, const CSFMeta* metas, size_t location, uint32_t numThreads)
{
CSFSerializerInfo dummy;
size_t metaOFFSET = mgr.storeArray(location, sizeof(CSFMeta) * numMetas, alignof(CSFMeta));
csfutils::parallel_ranges(
numMetas,
[&](uint64_t idxBegin, uint64_t idxEnd, uint32_t threadIndex, void* userData) {
for(int i = int(idxBegin); i < int(idxEnd); i++)
{
const CSFMeta* meta = metas + i;
CSFSerializer::processMeta(mgr, threadIndex, i, meta, metaOFFSET + i * sizeof(CSFMeta));
}
},
nullptr, 1024, numThreads);
}
template <class T>
static int CSFile_saveInternal(const CSFile* csf, const char* filename)
{
T file;
CSFOffsetMgr<T> mgr(file);
CSFile dump = {0};
memcpy(&dump, csf, CSFile_getHeaderSize(csf));
dump.version = std::max(csf->version, (int)CADSCENEFILE_VERSION);
dump.magic = CADSCENEFILE_MAGIC;
// initialize these to zero, their actual values will be handled later if they exist
dump.fileMeta = nullptr;
dump.geometryMetas = nullptr;
dump.nodeMetas = nullptr;
std::vector<size_t> materialOffsets(csf->numMaterials + 1);
std::vector<size_t> geometryOffsets(csf->numGeometries + 1);
std::vector<size_t> nodeOffsets(csf->numNodes + 1);
std::vector<size_t> geometryMeshletOffsets;
std::vector<size_t> geometryLodOffsets;
std::vector<size_t> fileMetaOffsets;
std::vector<size_t> geoemtryMetaOffsets;
std::vector<size_t> nodeMetaOffsets;
// two passes:
// p 0: compute all offsets and filesize
// p 1: store to file
size_t fileSize = 0;
for(int p = 0; p < 2; p++)
{
#if CSF_DISABLE_FILEMAPPING_SUPPORT
// must be serial always
uint32_t numThreads = 1;
#else
uint32_t numThreads = CSF_DEFAULT_NUM_THREADS;
#endif
// computation pass is serial
if(p == 0)
{
numThreads = 1;
}
mgr.setPass(p);
{
mgr.setObjects(&geometryOffsets);
size_t geomOFFSET =
mgr.storeArray(offsetof(CSFile, geometriesOFFSET), sizeof(CSFGeometry) * csf->numGeometries, alignof(CSFGeometry));
csfutils::parallel_ranges(
csf->numGeometries,
[&](uint64_t idxBegin, uint64_t idxEnd, uint32_t threadIndex, void* userData) {
for(int i = int(idxBegin); i < int(idxEnd); i++)
{
const CSFGeometry* geo = csf->geometries + i;
size_t perPartSize = csfGeometryPerPartSize(geo->numPartChannels, geo->perpartStorageOrder, geo->numParts);
CSFSerializer::processGeometry(mgr, threadIndex, i, geo, geomOFFSET + i * sizeof(CSFGeometry), perPartSize);
}
},
nullptr, 1024, numThreads);
// typically not worth to do threading for materials
mgr.setObjects(&materialOffsets);
size_t matOFFSET =
mgr.storeArray(offsetof(CSFile, materialsOFFSET), sizeof(CSFMaterial) * csf->numMaterials, alignof(CSFMaterial));
for(int i = 0; i < csf->numMaterials; i++)
{
const CSFMaterial* mat = csf->materials + i;
CSFSerializer::processMaterial(mgr, 0, i, mat, matOFFSET + i * sizeof(CSFMaterial));
}
mgr.setObjects(&nodeOffsets);
size_t nodeOFFSET = mgr.storeArray(offsetof(CSFile, nodesOFFSET), sizeof(CSFNode) * csf->numNodes, alignof(CSFNode));
csfutils::parallel_ranges(
csf->numNodes,
[&](uint64_t idxBegin, uint64_t idxEnd, uint32_t threadIndex, void* userData) {
for(int i = int(idxBegin); i < int(idxEnd); i++)
{
const CSFNode* node = csf->nodes + i;
CSFSerializer::processNode(mgr, threadIndex, i, node, nodeOFFSET + i * sizeof(CSFNode));
}
},
nullptr, 1024, numThreads);
}
if(dump.version >= CADSCENEFILE_VERSION_META)
{
if(csf->nodeMetas)
{
nodeMetaOffsets.resize(csf->numNodes + 1);
mgr.setObjects(&nodeMetaOffsets);
processMetaArray(mgr, csf, csf->numNodes, csf->nodeMetas, offsetof(CSFile, nodeMetasOFFSET), numThreads);
}
if(csf->geometryMetas)
{
geoemtryMetaOffsets.resize(csf->numGeometries + 1);
mgr.setObjects(&geoemtryMetaOffsets);
processMetaArray(mgr, csf, csf->numGeometries, csf->geometryMetas, offsetof(CSFile, geometryMetasOFFSET), numThreads);
}
if(csf->fileMeta)
{
fileMetaOffsets.resize(1 + 1);
mgr.setObjects(&fileMetaOffsets);
processMetaArray(mgr, csf, 1, csf->fileMeta, offsetof(CSFile, fileMetaOFFSET), 1);
}
}
// open file at end of computation pass
if(p == 0)
{
fileSize = mgr.getFileSize();
if(file.open(filename, fileSize))
{
return CADSCENEFILE_ERROR_NOFILE;
}
// dump main part as is
mgr.storeHeader(&dump, sizeof(CSFile));
}
}
bool hadError = !mgr.finalize(fileSize, offsetof(CSFile, numPointers), offsetof(CSFile, pointersOFFSET));
file.close();
return hadError ? CADSCENEFILE_ERROR_OPERATION : CADSCENEFILE_NOERROR;
}
CSFAPI int CSFile_save(const CSFile* csf, const char* filename)
{
return CSFile_saveInternal<OutputFILE>(csf, filename);
}
CSFAPI int CSFile_saveExt(CSFile* csf, const char* filename)
{
#if CSF_SUPPORT_ZLIB
size_t len = strlen(filename);
if(strcmp(filename + len - 3, ".gz") == 0)
{
return CSFile_saveInternal<OutputGZ>(csf, filename);
}
else
#endif
{
return CSFile_save(csf, filename);
}
}
//////////////////////////////////////////////////////////////////////////
struct CSFReadMapping_s
{
csfutils::FileReadMapping mapping;
};
CSFAPI CSFReadMappingPTR CSFReadMapping_new(const char* filename)
{
CSFReadMappingPTR mapping = new CSFReadMapping_s();
if(mapping->mapping.open(filename))
{
return mapping;
}
else
{
delete mapping;
return nullptr;
}
}
CSFAPI void CSFReadMapping_delete(CSFReadMappingPTR readMapping)
{
if(readMapping)
{
delete readMapping;
}
}
CSFAPI size_t CSFReadMapping_getSize(CSFReadMappingPTR readMapping)
{
return readMapping->mapping.size();
}
CSFAPI const void* CSFReadMapping_getData(CSFReadMappingPTR readMapping)
{
return readMapping->mapping.data();
}
//////////////////////////////////////////////////////////////////////////
// FILEHANDLE API
CSFAPI int CSFileHandle_open(CSFileHandlePTR* pHandle, const char* filename)
{
CSFileHandlePTR handle = new CSFileHandle_s;
int result = handle->open(filename);
if(result != CADSCENEFILE_NOERROR)
{
delete handle;
}
else
{
*pHandle = handle;
}
return result;
}
CSFAPI void CSFileHandle_close(CSFileHandlePTR handle)
{
delete handle;
}
CSFAPI CSFile* CSFileHandle_rawHeader(CSFileHandlePTR handle, CSFile* header)
{
memcpy(header, &handle->m_header, sizeof(CSFile));
return header;
}
CSFAPI CSFile* CSFileHandle_loadHeader(CSFileHandlePTR handle, CSFile* header)
{
memcpy(header, &handle->m_header, sizeof(CSFile));
header->materialsOFFSET = 0;
header->geometriesOFFSET = 0;
header->nodesOFFSET = 0;
header->geometryMetasOFFSET = 0;
header->nodeMetasOFFSET = 0;
header->fileMetaOFFSET = 0;
return header;
}
struct CSFOffsetNullify
{
// nukes all pointers to zero
CSFOffsetNullify(int version, int fileFlags)
: m_version(version)
{
}
int m_version;
int m_fileFlags;
unsigned char* m_refpointer;
void beginObjectReference(const CSFSerializerInfo& info)
{
// original the serializer was meant to read from the csf
// this operation here actually modifies the objects
m_refpointer = (unsigned char*)info.obj;
}
void endObjectReference(const CSFSerializerInfo& info) { m_refpointer = nullptr; }
size_t storeObjectData(const CSFSerializerInfo& info, size_t location, const void* data, size_t dataSize, size_t alignment)
{
void** writePointer = (void**)(m_refpointer + location);
*writePointer = nullptr;
return 0;
}
};
CSFAPI CSFile* CSFileHandle_loadBasics(CSFileHandlePTR handle, uint32_t typebitsflag, CSFileMemoryPTR mem)
{
// allocate CSFile
CSFile* csf = (CSFile*)mem->alloc(sizeof(CSFile), &handle->m_header);
int version = csf->version;
CSFOffsetNullify mgr(version, csf->fileFlags);
csf->materials = handle->allocAndReadT<CSFMaterial>(csf->materialsOFFSET, csf->numMaterials, mem,
typebitsflag & CSFILEHANDLE_CONTENT_MATERIAL);
if(csf->materials)
{
for(int i = 0; i < csf->numMaterials; i++)
{
CSFSerializer::processMaterial(mgr, 0, i, csf->materials + i, 0);
csfPostLoadMaterial(csf, csf->materials + i, false);
}
}
csf->geometries = handle->allocAndReadT<CSFGeometry>(csf->geometriesOFFSET, csf->numGeometries, mem,
typebitsflag & CSFILEHANDLE_CONTENT_GEOMETRY);
if(csf->geometries)
{
for(int i = 0; i < csf->numGeometries; i++)
{
CSFSerializer::processGeometry(mgr, 0, i, csf->geometries + i, 0, 0);
csfPostLoadGeometry(csf, csf->geometries + i, false);
}
}
csf->nodes = handle->allocAndReadT<CSFNode>(csf->nodesOFFSET, csf->numNodes, mem, typebitsflag & CSFILEHANDLE_CONTENT_NODE);
if(csf->nodes)
{
for(int i = 0; i < csf->numNodes; i++)
{
CSFSerializer::processNode(mgr, 0, i, csf->nodes + i, 0);
csfPostLoadNode(csf, csf->nodes + i, false);
}
}
csf->geometryMetas =
handle->allocAndReadT<CSFMeta>(csf->geometryMetasOFFSET, csf->numGeometries, mem,
CSFile_getGeometryMetas(csf) && typebitsflag & CSFILEHANDLE_CONTENT_GEOMETRYMETA);
if(csf->geometryMetas)
{
for(int i = 0; i < csf->numGeometries; i++)
{
CSFSerializer::processMeta(mgr, 0, i, csf->geometryMetas + i, 0);
}
}
csf->nodeMetas = handle->allocAndReadT<CSFMeta>(csf->nodeMetasOFFSET, csf->numNodes, mem,
CSFile_getNodeMetas(csf) && typebitsflag & CSFILEHANDLE_CONTENT_NODEMETA);
if(csf->nodeMetas)
{
for(int i = 0; i < csf->numNodes; i++)
{
CSFSerializer::processMeta(mgr, 0, i, csf->nodeMetas + i, 0);
}
}
csf->fileMeta = handle->allocAndReadT<CSFMeta>(csf->fileMetaOFFSET, 1, mem,
CSFile_getFileMeta(csf) && typebitsflag & CSFILEHANDLE_CONTENT_FILEMETA);
if(csf->fileMeta)
{
for(int i = 0; i < 1; i++)
{
CSFSerializer::processMeta(mgr, 0, i, csf->fileMeta + i, 0);
}
}
csfPostLoadFile(csf, false);
return csf;
}
struct CSFOffsetReader
{
int m_version;
int m_fileFlags;
CSFileHandlePTR m_handle;
CSFileMemoryPTR m_mem;
bool m_calculate;
unsigned char* m_allocation;
CSFoffset m_minOffset;
CSFoffset m_maxOffset;
unsigned char* m_refpointer = nullptr;
CSFOffsetReader(CSFileHandlePTR handle, CSFileMemoryPTR mem)
: m_handle(handle)
, m_mem(mem)
, m_calculate(true)
{
m_version = handle->m_header.version;
m_fileFlags = handle->m_header.fileFlags;
}
size_t geometrySetup(CSFGeometry* geo)
{
if(m_version >= CADSCENEFILE_VERSION_GEOMETRYCHANNELS && geo->numPartChannels)
{
// special case data structures
CSFGeometryPartChannel* channels = new CSFGeometryPartChannel[geo->numPartChannels];
m_handle->read(geo->perpartStorageOrderOFFSET, channels, sizeof(CSFGeometryPartChannel) * geo->numPartChannels);
size_t size = csfGeometryPerPartSize(geo->numPartChannels, channels, geo->numParts);
delete[] channels;
return size;
}
return 0;
}
void beginObjectReference(const CSFSerializerInfo& info)
{
// original the serializer was meant to read from the csf
// this operation here actually modifies the objects
m_refpointer = (unsigned char*)info.obj;
}
void endObjectReference(const CSFSerializerInfo& info) { m_refpointer = nullptr; }
size_t storeObjectData(const CSFSerializerInfo& info, size_t location, const void* data, size_t dataSize, size_t alignment)
{
// m_refpointer is the begin of the struct
// apply the location offset which gives us the place where the pointer/offset is stored
// offset within file
CSFoffset readOffset = *(CSFoffset*)(m_refpointer + location);
if(m_calculate)
{
// extract the file memory location of all pointers within an object
m_minOffset = std::min(m_minOffset, readOffset);
m_maxOffset = std::max(m_maxOffset, readOffset + dataSize);
}
else
{
// location of pointer, assign current allocation memory
void** writePointer = (void**)(m_refpointer + location);
// reabase the original offset relative to allocation
*writePointer = m_allocation + (readOffset - m_minOffset);
}
return 0;
}
void calculatePass()
{
m_calculate = true;
m_minOffset = ~0ull;
m_maxOffset = 0;
}
void readPass()
{
m_calculate = false;
// all data relevant to the same reference object
// is stored in a contiguous chunk of memory
size_t allocationSize = m_maxOffset - m_minOffset;
m_allocation = (unsigned char*)m_mem->alloc(allocationSize);
m_handle->read(m_minOffset, m_allocation, allocationSize);
}
};
CSFAPI void* CSFileHandle_loadElementsInto(CSFileHandlePTR handle,
CSFileHandleContentBit contentbit,
int begin,
int num,
CSFileMemoryPTR mem,
size_t pimarySize,
void* primaryArray)
{
const CSFile* csf = &handle->m_header;
int version = csf->version;
CSFOffsetReader reader(handle, mem);
switch(contentbit)
{
case CSFILEHANDLE_CONTENT_MATERIAL: {
assert(begin + num <= csf->numMaterials);
CSFoffset offset = csf->materialsOFFSET + sizeof(CSFMaterial) * begin;
CSFMaterial* arrayContent = (CSFMaterial*)primaryArray;
handle->read(offset, arrayContent, pimarySize);
for(int i = 0; i < num; i++)
{
reader.calculatePass();
CSFSerializer::processMaterial(reader, 0, i, arrayContent + i, 0);
reader.readPass();
CSFSerializer::processMaterial(reader, 0, i, arrayContent + i, 0);
csfPostLoadMaterial(csf, arrayContent + i);
}
return arrayContent;
}
case CSFILEHANDLE_CONTENT_GEOMETRY: {
assert(begin + num <= csf->numGeometries);
CSFoffset offset = csf->geometriesOFFSET + sizeof(CSFGeometry) * begin;
CSFGeometry* arrayContent = (CSFGeometry*)primaryArray;
handle->read(offset, arrayContent, pimarySize);
for(int i = 0; i < num; i++)
{
size_t perpartSize = reader.geometrySetup(arrayContent + i);
reader.calculatePass();
CSFSerializer::processGeometry(reader, 0, i, arrayContent + i, 0, perpartSize);
reader.readPass();
CSFSerializer::processGeometry(reader, 0, i, arrayContent + i, 0, perpartSize);
csfPostLoadGeometry(csf, arrayContent + i);
}
return arrayContent;
}
case CSFILEHANDLE_CONTENT_NODE: {
assert(begin + num <= csf->numNodes);
CSFoffset offset = csf->nodesOFFSET + sizeof(CSFNode) * begin;
CSFNode* arrayContent = (CSFNode*)primaryArray;
handle->read(offset, arrayContent, pimarySize);
for(int i = 0; i < num; i++)
{
reader.calculatePass();
CSFSerializer::processNode(reader, 0, i, arrayContent + i, 0);
reader.readPass();
CSFSerializer::processNode(reader, 0, i, arrayContent + i, 0);
csfPostLoadNode(csf, arrayContent + i);
}
return arrayContent;
}
case CSFILEHANDLE_CONTENT_GEOMETRYMETA: {
if(!CSFile_getGeometryMetas(csf))
return nullptr;
assert(begin + num <= csf->numGeometries);
CSFoffset offset = csf->geometryMetasOFFSET + sizeof(CSFMeta) * begin;
CSFMeta* arrayContent = (CSFMeta*)primaryArray;
handle->read(offset, arrayContent, pimarySize);
for(int i = 0; i < num; i++)
{
reader.calculatePass();
CSFSerializer::processMeta(reader, 0, i, arrayContent + i, 0);
reader.readPass();
CSFSerializer::processMeta(reader, 0, i, arrayContent + i, 0);
}
return arrayContent;
}
case CSFILEHANDLE_CONTENT_NODEMETA: {
if(!CSFile_getNodeMetas(csf))
return nullptr;
assert(begin + num <= csf->numNodes);
CSFoffset offset = csf->nodeMetasOFFSET + sizeof(CSFMeta) * begin;
CSFMeta* arrayContent = (CSFMeta*)primaryArray;
handle->read(offset, arrayContent, pimarySize);
for(int i = 0; i < num; i++)
{
reader.calculatePass();
CSFSerializer::processMeta(reader, 0, i, arrayContent + i, 0);
reader.readPass();
CSFSerializer::processMeta(reader, 0, i, arrayContent + i, 0);
}
return arrayContent;
}
case CSFILEHANDLE_CONTENT_FILEMETA: {
if(!CSFile_getFileMeta(csf))
return nullptr;
assert(begin + num <= 1);
CSFoffset offset = csf->fileMetaOFFSET + sizeof(CSFMeta) * begin;
CSFMeta* arrayContent = (CSFMeta*)primaryArray;
handle->read(offset, arrayContent, pimarySize);
for(int i = 0; i < num; i++)
{
reader.calculatePass();
CSFSerializer::processMeta(reader, 0, i, arrayContent + i, 0);
reader.readPass();
CSFSerializer::processMeta(reader, 0, i, arrayContent + i, 0);
}
return arrayContent;
}
}
return nullptr;
}
CSFAPI void* CSFileHandle_loadElements(CSFileHandlePTR handle, CSFileHandleContentBit contentbit, int begin, int num, CSFileMemoryPTR mem)
{
size_t arraySize = 0;
switch(contentbit)
{
case CSFILEHANDLE_CONTENT_MATERIAL:
arraySize = sizeof(CSFMaterial) * num;
break;
case CSFILEHANDLE_CONTENT_GEOMETRY:
arraySize = sizeof(CSFGeometry) * num;
break;
case CSFILEHANDLE_CONTENT_NODE:
arraySize = sizeof(CSFNode) * num;
break;
case CSFILEHANDLE_CONTENT_GEOMETRYMETA:
arraySize = sizeof(CSFMeta) * num;
break;
case CSFILEHANDLE_CONTENT_NODEMETA:
arraySize = sizeof(CSFMeta) * num;
break;
case CSFILEHANDLE_CONTENT_FILEMETA:
arraySize = sizeof(CSFMeta) * num;
break;
default:
return nullptr;
}
void* arrayContent = mem->alloc(arraySize);
return CSFileHandle_loadElementsInto(handle, contentbit, begin, num, mem, arraySize, arrayContent);
}
//////////////////////////////////////////////////////////////////////////
#if !CSF_DISABLE_FILEMAPPING_SUPPORT
#if defined(LINUX)
#include <errno.h>
#include <unistd.h>
#endif
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
inline DWORD HIDWORD(size_t x)
{
return (DWORD)(x >> 32);
}
inline DWORD LODWORD(size_t x)
{
return (DWORD)x;
}
#endif
namespace csfutils {
bool FileMapping::open(const char* fileName, MappingType mappingType, size_t fileSize)
{
if(!g_pageSize)
{
#if defined(_WIN32)
SYSTEM_INFO si;
GetSystemInfo(&si);
g_pageSize = (size_t)si.dwAllocationGranularity;
#elif defined(LINUX)
g_pageSize = (size_t)getpagesize();
#endif
}
m_mappingType = mappingType;
if(mappingType == MAPPING_READOVERWRITE)
{
assert(fileSize);
m_fileSize = fileSize;
m_mappingSize = ((fileSize + g_pageSize - 1) / g_pageSize) * g_pageSize;
// check if the current process is allowed to save a file of that size
#if defined(_WIN32)
TCHAR dir[MAX_PATH + 1];
BOOL success = FALSE;
ULARGE_INTEGER numFreeBytes;
DWORD length = GetVolumePathName(fileName, dir, MAX_PATH + 1);
if(length > 0)
{
success = GetDiskFreeSpaceEx(dir, NULL, NULL, &numFreeBytes);
}
m_isValid = (!!success) && (m_mappingSize <= numFreeBytes.QuadPart);
#elif defined(LINUX)
struct rlimit rlim;
getrlimit(RLIMIT_FSIZE, &rlim);
m_isValid = (m_mappingSize <= rlim.rlim_cur);
#endif
if(!m_isValid)
{
return false;
}
}
#if defined(_WIN32)
m_win32.file = mappingType == MAPPING_READONLY ?
CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL) :
CreateFile(fileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
m_isValid = (m_win32.file != INVALID_HANDLE_VALUE);
if(m_isValid)
{
if(mappingType == MAPPING_READONLY)
{
DWORD sizeHi = 0;
DWORD sizeLo = GetFileSize(m_win32.file, &sizeHi);
m_mappingSize = (static_cast<size_t>(sizeHi) << 32) | sizeLo;
m_fileSize = m_mappingSize;
}
m_win32.fileMapping = CreateFileMapping(m_win32.file, NULL, mappingType == MAPPING_READONLY ? PAGE_READONLY : PAGE_READWRITE,
HIDWORD(m_mappingSize), LODWORD(m_mappingSize), NULL);
m_isValid = (m_win32.fileMapping != NULL);
if(m_isValid)
{
m_mappingPtr = MapViewOfFile(m_win32.fileMapping, mappingType == MAPPING_READONLY ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS,
HIDWORD(0), LODWORD(0), (SIZE_T)m_mappingSize);
if(!m_mappingPtr)
{
#if 0
DWORD err = GetLastError();
#endif
CloseHandle(m_win32.file);
m_isValid = false;
}
}
else
{
CloseHandle(m_win32.file);
}
}
#elif defined(LINUX)
m_unix.file = mappingType == MAPPING_READONLY ? ::open(fileName, O_RDONLY) : ::open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0666);
m_isValid = (m_unix.file != -1);
if(m_isValid)
{
if(mappingType == MAPPING_READONLY)
{
struct stat s;
fstat(m_unix.file, &s);
m_mappingSize = s.st_size;
}
else
{
// make file large enough to hold the complete scene
lseek(m_unix.file, m_mappingSize - 1, SEEK_SET);
write(m_unix.file, "", 1);
lseek(m_unix.file, 0, SEEK_SET);
}
m_fileSize = m_mappingSize;
m_mappingPtr = mmap(0, m_mappingSize, mappingType == MAPPING_READONLY ? PROT_READ : (PROT_READ | PROT_WRITE),
MAP_SHARED, m_unix.file, 0);
if(m_mappingPtr == MAP_FAILED)
{
::close(m_unix.file);
m_unix.file = -1;
m_isValid = false;
}
}
#endif
return m_isValid;
}
void FileMapping::close()
{
if(m_isValid)
{
#if defined(_WIN32)
assert((m_win32.file != INVALID_HANDLE_VALUE) && (m_win32.fileMapping != NULL));
UnmapViewOfFile(m_mappingPtr);
CloseHandle(m_win32.fileMapping);
if(m_mappingType == MAPPING_READOVERWRITE)
{
// truncate file to minimum size
// To work with 64-bit file pointers, you can declare a LONG, treat it as the upper half
// of the 64-bit file pointer, and pass its address in lpDistanceToMoveHigh. This means
// you have to treat two different variables as a logical unit, which is error-prone.
// The problems can be ameliorated by using the LARGE_INTEGER structure to create a 64-bit
// value and passing the two 32-bit values by means of the appropriate elements of the union.
// (see msdn documentation on SetFilePointer)
LARGE_INTEGER li;
li.QuadPart = (__int64)m_fileSize;
SetFilePointer(m_win32.file, li.LowPart, &li.HighPart, FILE_BEGIN);
SetEndOfFile(m_win32.file);
}
CloseHandle(m_win32.file);
m_mappingPtr = nullptr;
m_win32.fileMapping = nullptr;
m_win32.file = nullptr;
#elif defined(LINUX)
assert(m_unix.file != -1);
munmap(m_mappingPtr, m_mappingSize);
if(m_mappingType == MAPPING_READOVERWRITE)
{
::ftruncate(m_unix.file, m_fileSize);
}
::close(m_unix.file);
m_mappingPtr = nullptr;
m_unix.file = -1;
#endif
m_isValid = false;
}
}
size_t FileMapping::g_pageSize = 0;
} // namespace csfutils
#else
namespace csfutils {
bool FileMapping::open(const char* fileName, MappingType mappingType, size_t fileSize)
{
return false;
}
void FileMapping::close() {}
}; // namespace csfutils
#endif
#if CSF_SUPPORT_GLTF2
#include "cgltf.h"
#include <glm/glm.hpp>
#include <unordered_map>
void CSFile_countGLTFNodes(CSFile* csf, const cgltf_data* gltfModel, const cgltf_node* node)
{
csf->numNodes++;
for(cgltf_size i = 0; i < node->children_count; i++)
{
CSFile_countGLTFNodes(csf, gltfModel, node->children[i]);
}
}
int CSFile_addGLTFNode(CSFile* csf, const cgltf_data* gltfModel, const uint32_t* meshGeometries, CSFileMemoryPTR mem, const cgltf_node* node)
{
int idx = csf->numNodes++;
CSFNode& csfnode = csf->nodes[idx];
CSFMatrix_identity(csfnode.worldTM);
CSFMatrix_identity(csfnode.objectTM);
if(node->has_matrix)
{
for(int i = 0; i < 16; i++)
{
csfnode.objectTM[i] = (float)node->matrix[i];
}
}
else
{
glm::vec3 translation = {0, 0, 0};
glm::quat rotation = {0, 0, 0, 0};
glm::vec3 scale = {1, 1, 1};
if(node->has_translation)
{
translation.x = static_cast<float>(node->translation[0]);
translation.y = static_cast<float>(node->translation[1]);
translation.z = static_cast<float>(node->translation[2]);
}
if(node->has_rotation)
{
rotation.x = static_cast<float>(node->rotation[0]);
rotation.y = static_cast<float>(node->rotation[1]);
rotation.z = static_cast<float>(node->rotation[2]);
rotation.w = static_cast<float>(node->rotation[3]);
}
if(node->has_scale)
{
scale.x = static_cast<float>(node->scale[0]);
scale.y = static_cast<float>(node->scale[1]);
scale.z = static_cast<float>(node->scale[2]);
}
glm::mat4 mtranslation, mscale, mrot;
mtranslation = glm::translate(glm::mat4(1), translation);
mscale = glm::scale(glm::mat4(1), scale);
mrot = glm::mat4_cast(rotation);
glm::mat4 matrix = mtranslation * mrot * mscale;
float* mat_array = glm::value_ptr(matrix);
for(int i = 0; i < 16; i++)
{
csfnode.objectTM[i] = mat_array[i];
}
}
// setup geometry
if(node->mesh)
{
size_t meshIndex = node->mesh - gltfModel->meshes;
csfnode.geometryIDX = meshGeometries[meshIndex];
const cgltf_mesh& mesh = gltfModel->meshes[meshIndex];
csfnode.numParts = csf->geometries[csfnode.geometryIDX].numParts;
csfnode.parts = (CSFNodePart*)CSFileMemory_alloc(mem, sizeof(CSFNodePart) * csfnode.numParts, nullptr);
uint32_t p = 0;
for(cgltf_size i = 0; i < mesh.primitives_count; i++)
{
const cgltf_primitive& primitive = mesh.primitives[i];
if(primitive.type != cgltf_primitive_type_triangles)
continue;
CSFNodePart& csfpart = csfnode.parts[p++];
csfpart.active = 1;
csfpart.materialIDX = primitive.material ? int(primitive.material - gltfModel->materials) : 0;
csfpart.nodeIDX = -1;
}
}
else
{
csfnode.geometryIDX = -1;
}
csfnode.numChildren = (int)node->children_count;
csfnode.children = (int*)CSFileMemory_alloc(mem, sizeof(int) * csfnode.numChildren, nullptr);
for(cgltf_size i = 0; i < node->children_count; i++)
{
csfnode.children[i] = CSFile_addGLTFNode(csf, gltfModel, meshGeometries, mem, node->children[i]);
}
return idx;
}
//-----------------------------------------------------------------------------
// MurmurHash2A, by Austin Appleby
// This is a variant of MurmurHash2 modified to use the Merkle-Damgard
// construction. Bulk speed should be identical to Murmur2, small-key speed
// will be 10%-20% slower due to the added overhead at the end of the hash.
// This variant fixes a minor issue where null keys were more likely to
// collide with each other than expected, and also makes the algorithm
// more amenable to incremental implementations. All other caveats from
// MurmurHash2 still apply.
#define mmix(h, k) \
{ \
k *= m; \
k ^= k >> r; \
k *= m; \
h *= m; \
h ^= k; \
}
static unsigned int strMurmurHash2A(const void* key, size_t len, unsigned int seed)
{
const unsigned int m = 0x5bd1e995;
const int r = 24;
unsigned int l = (unsigned int)len;
const unsigned char* data = (const unsigned char*)key;
unsigned int h = seed;
unsigned int t = 0;
while(len >= 4)
{
unsigned int k = *(unsigned int*)data;
mmix(h, k);
data += 4;
len -= 4;
}
switch(len)
{
case 3:
t ^= data[2] << 16;
case 2:
t ^= data[1] << 8;
case 1:
t ^= data[0];
};
mmix(h, t);
mmix(h, l);
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return h;
}
#undef mmix
struct GLTFGeometryInfo
{
uint32_t numVertices = 0;
uint32_t numNormals = 0;
uint32_t numTexcoords = 0;
uint32_t numIndices = 0;
uint32_t numParts = 0;
uint32_t hashIndex = 0;
uint32_t hashVertex = 0;
uint32_t hashNormal = 0;
uint32_t hashTexcoord = 0;
uint32_t hashLightVertex = 0;
uint32_t hashLightNormal = 0;
uint32_t hashLightTexcoord = 0;
bool isEqualHash(const GLTFGeometryInfo& other)
{
return hashIndex == other.hashIndex && hashVertex == other.hashVertex && hashNormal == other.hashNormal
&& hashTexcoord == other.hashTexcoord;
}
bool isEqualLight(const GLTFGeometryInfo& other)
{
return numVertices == other.numVertices && numNormals == other.numNormals && numIndices == other.numIndices
&& numParts == other.numParts && hashLightVertex == other.hashLightVertex
&& hashLightNormal == other.hashLightNormal && hashLightTexcoord == other.hashLightTexcoord;
}
void setup(const cgltf_data* gltfModel, const cgltf_mesh& mesh)
{
hashVertex = 0;
hashNormal = 0;
hashTexcoord = 0;
hashIndex = 0;
hashLightVertex = 0;
hashLightNormal = 0;
hashLightTexcoord = 0;
for(cgltf_size i = 0; i < mesh.primitives_count; i++)
{
const cgltf_primitive& primitive = mesh.primitives[i];
if(primitive.type != cgltf_primitive_type_triangles)
continue;
for(cgltf_size a = 0; a < primitive.attributes_count; a++)
{
const cgltf_accessor* accessor = primitive.attributes[a].data;
const cgltf_buffer_view* view = accessor->buffer_view;
const uint8_t* data = reinterpret_cast<const uint8_t*>(view->buffer->data);
data += accessor->offset + view->offset;
switch(primitive.attributes[a].type)
{
case cgltf_attribute_type_position:
numVertices += static_cast<uint32_t>(accessor->count);
hashLightVertex = strMurmurHash2A(data, accessor->stride, hashLightVertex);
break;
case cgltf_attribute_type_normal:
numNormals += static_cast<uint32_t>(accessor->count);
hashLightNormal = strMurmurHash2A(data, accessor->stride, hashLightNormal);
break;
case cgltf_attribute_type_texcoord:
if(primitive.attributes[a].index == 0)
{
numTexcoords += static_cast<uint32_t>(accessor->count);
hashLightTexcoord = strMurmurHash2A(data, accessor->stride, hashLightTexcoord);
}
break;
default:
break; // No matching csf attribute
}
}
numIndices += static_cast<uint32_t>(primitive.indices->count);
numParts++;
}
}
bool hasHash() const { return hashIndex != 0 || hashVertex != 0 || hashNormal != 0; }
void setupHash(const cgltf_data* gltfModel, const cgltf_mesh& mesh)
{
for(cgltf_size i = 0; i < mesh.primitives_count; i++)
{
const cgltf_primitive& primitive = mesh.primitives[i];
if(primitive.type != cgltf_primitive_type_triangles)
continue;
for(cgltf_size a = 0; a < primitive.attributes_count; a++)
{
const cgltf_accessor* accessor = primitive.attributes[a].data;
const cgltf_buffer_view* view = accessor->buffer_view;
const uint8_t* data = reinterpret_cast<const uint8_t*>(view->buffer->data);
data += accessor->offset + view->offset;
switch(primitive.attributes[a].type)
{
case cgltf_attribute_type_position:
hashVertex = strMurmurHash2A(data, accessor->stride * accessor->count, hashVertex);
break;
case cgltf_attribute_type_normal:
hashNormal = strMurmurHash2A(data, accessor->stride * accessor->count, hashNormal);
break;
case cgltf_attribute_type_texcoord:
if(primitive.attributes[a].index == 0)
{
hashTexcoord = strMurmurHash2A(data, accessor->stride * accessor->count, hashTexcoord);
}
break;
default:
break; // No matching csf attribute
}
}
{
const cgltf_accessor* accessor = primitive.indices;
const cgltf_buffer_view* view = accessor->buffer_view;
const uint8_t* data = reinterpret_cast<const uint8_t*>(view->buffer->data);
data += accessor->offset + view->offset;
hashIndex = strMurmurHash2A(data, accessor->stride * accessor->count, hashIndex);
}
}
}
};
static inline void setupCSFMaterialTexture(CSFMaterialGLTF2Texture& csftex, const cgltf_texture_view& tex)
{
if(!tex.texture)
return;
const char* uri = tex.texture->image->uri;
if(uri)
{
strncpy(csftex.name, uri, sizeof(csftex.name));
}
}
struct MappingList
{
std::unordered_map<std::string, CSFReadMapping> maps;
};
cgltf_result csf_cgltf_read(const struct cgltf_memory_options* memory_options,
const struct cgltf_file_options* file_options,
const char* path,
cgltf_size* size,
void** data)
{
MappingList* mappings = (MappingList*)file_options->user_data;
std::string pathStr(path);
auto it = mappings->maps.find(pathStr);
if(it != mappings->maps.end())
{
*data = const_cast<void*>(CSFReadMapping_getData(it->second.mapping));
*size = CSFReadMapping_getSize(it->second.mapping);
return cgltf_result_success;
}
CSFReadMapping entry;
entry.mapping = CSFReadMapping_new(path);
if(entry.mapping)
{
*data = const_cast<void*>(CSFReadMapping_getData(entry.mapping));
*size = CSFReadMapping_getSize(entry.mapping);
mappings->maps.insert({pathStr, std::move(entry)});
return cgltf_result_success;
}
return cgltf_result_io_error;
}
void csf_cgltf_release(const struct cgltf_memory_options* memory_options, const struct cgltf_file_options* file_options, void* data)
{
// let MappingList destructor handle it
}
CSFAPI int CSFile_loadGTLF(CSFile** outcsf, const char* filename, CSFileMemoryPTR mem)
{
if(!filename)
{
return CADSCENEFILE_ERROR_NOFILE;
}
int findUniqueGeometries = mem->m_config.gltfFindUniqueGeometries;
cgltf_options gltfOptions = {};
cgltf_data* gltfModel;
MappingList mappings;
gltfOptions.file.read = csf_cgltf_read;
gltfOptions.file.release = csf_cgltf_release;
gltfOptions.file.user_data = &mappings;
*outcsf = NULL;
cgltf_result result = cgltf_parse_file(&gltfOptions, filename, &gltfModel);
if(result != cgltf_result_success)
{
printf("ERR: cgltf_parse_file: %d\n", result);
return CADSCENEFILE_ERROR_OPERATION;
}
result = cgltf_load_buffers(&gltfOptions, gltfModel, filename);
if(result != cgltf_result_success)
{
printf("ERR: cgltf_load_buffers: %d\n", result);
cgltf_free(gltfModel);
return CADSCENEFILE_ERROR_OPERATION;
}
const cgltf_scene* scene = gltfModel->scene ? gltfModel->scene : &gltfModel->scenes[0];
if(!scene)
{
printf("ERR: cgltf: no scene\n");
cgltf_free(gltfModel);
return CADSCENEFILE_ERROR_OPERATION;
}
CSFile* csf = (CSFile*)CSFileMemory_alloc(mem, sizeof(CSFile), NULL);
memset(csf, 0, sizeof(CSFile));
csf->version = CADSCENEFILE_VERSION;
csf->fileFlags = 0;
csf->fileFlags |= CADSCENEFILE_FLAG_UNIQUENODES;
csf->numMaterials = (int)gltfModel->materials_count;
csf->numNodes = (int)gltfModel->nodes_count;
csf->materials = (CSFMaterial*)CSFileMemory_alloc(mem, sizeof(CSFMaterial) * csf->numMaterials, NULL);
memset(csf->materials, 0, sizeof(CSFMaterial) * csf->numMaterials);
// create materials
for(cgltf_size materialIdx = 0; materialIdx < gltfModel->materials_count; materialIdx++)
{
const cgltf_material& mat = gltfModel->materials[materialIdx];
CSFMaterial& csfmat = csf->materials[materialIdx];
if(mat.has_pbr_metallic_roughness)
{
csfmat.color[0] = mat.pbr_metallic_roughness.base_color_factor[0];
csfmat.color[1] = mat.pbr_metallic_roughness.base_color_factor[1];
csfmat.color[2] = mat.pbr_metallic_roughness.base_color_factor[2];
csfmat.color[3] = mat.pbr_metallic_roughness.base_color_factor[3];
}
else if(mat.has_pbr_specular_glossiness)
{
csfmat.color[0] = mat.pbr_specular_glossiness.diffuse_factor[0];
csfmat.color[1] = mat.pbr_specular_glossiness.diffuse_factor[1];
csfmat.color[2] = mat.pbr_specular_glossiness.diffuse_factor[2];
csfmat.color[3] = mat.pbr_specular_glossiness.diffuse_factor[3];
}
else
{
csfmat.color[0] = 1.0f;
csfmat.color[1] = 1.0f;
csfmat.color[2] = 1.0f;
csfmat.color[3] = 1.0f;
}
if(mat.name)
{
strncpy(csfmat.name, mat.name, sizeof(csfmat.name));
}
else
{
strncpy(csfmat.name, "undefined", sizeof(csfmat.name));
}
csfmat.bytes = nullptr;
csfmat.numBytes = 0;
csfmat.type = 0;
CSFMaterialGLTF2Meta csfmatgltf = {{CSFGUID_MATERIAL_GLTF2, sizeof(CSFMaterialGLTF2Meta)}};
csfmatgltf.shadingModel = -1;
csfmatgltf.emissiveFactor[0] = mat.emissive_factor[0];
csfmatgltf.emissiveFactor[1] = mat.emissive_factor[1];
csfmatgltf.emissiveFactor[2] = mat.emissive_factor[2];
csfmatgltf.doubleSided = mat.double_sided ? 1 : 0;
csfmatgltf.alphaCutoff = mat.alpha_cutoff;
csfmatgltf.alphaMode = mat.alpha_mode;
setupCSFMaterialTexture(csfmatgltf.emissiveTexture, mat.emissive_texture);
setupCSFMaterialTexture(csfmatgltf.normalTexture, mat.normal_texture);
setupCSFMaterialTexture(csfmatgltf.occlusionTexture, mat.occlusion_texture);
if(mat.has_pbr_metallic_roughness)
{
csfmatgltf.shadingModel = mat.unlit ? -1 : 0;
csfmatgltf.baseColorFactor[0] = mat.pbr_metallic_roughness.base_color_factor[0];
csfmatgltf.baseColorFactor[1] = mat.pbr_metallic_roughness.base_color_factor[1];
csfmatgltf.baseColorFactor[2] = mat.pbr_metallic_roughness.base_color_factor[2];
csfmatgltf.baseColorFactor[3] = mat.pbr_metallic_roughness.base_color_factor[3];
csfmatgltf.roughnessFactor = mat.pbr_metallic_roughness.roughness_factor;
csfmatgltf.metallicFactor = mat.pbr_metallic_roughness.metallic_factor;
setupCSFMaterialTexture(csfmatgltf.baseColorTexture, mat.pbr_metallic_roughness.base_color_texture);
setupCSFMaterialTexture(csfmatgltf.metallicRoughnessTexture, mat.pbr_metallic_roughness.metallic_roughness_texture);
}
else if(mat.has_pbr_specular_glossiness)
{
csfmatgltf.shadingModel = 1;
csfmatgltf.diffuseFactor[0] = mat.pbr_specular_glossiness.diffuse_factor[0];
csfmatgltf.diffuseFactor[1] = mat.pbr_specular_glossiness.diffuse_factor[1];
csfmatgltf.diffuseFactor[2] = mat.pbr_specular_glossiness.diffuse_factor[2];
csfmatgltf.diffuseFactor[3] = mat.pbr_specular_glossiness.diffuse_factor[3];
csfmatgltf.glossinessFactor = mat.pbr_specular_glossiness.glossiness_factor;
csfmatgltf.specularFactor[0] = mat.pbr_specular_glossiness.specular_factor[0];
csfmatgltf.specularFactor[1] = mat.pbr_specular_glossiness.specular_factor[1];
csfmatgltf.specularFactor[2] = mat.pbr_specular_glossiness.specular_factor[2];
setupCSFMaterialTexture(csfmatgltf.diffuseTexture, mat.pbr_specular_glossiness.diffuse_texture);
setupCSFMaterialTexture(csfmatgltf.specularGlossinessTexture, mat.pbr_specular_glossiness.specular_glossiness_texture);
}
csfmat.numBytes = sizeof(csfmatgltf);
csfmat.bytes = (unsigned char*)CSFileMemory_alloc(mem, sizeof(csfmatgltf), &csfmatgltf);
}
// find unique geometries
// many gltf files make improper use of geometry instancing
std::vector<uint32_t> meshGeometries;
std::vector<uint32_t> geometryMeshes;
meshGeometries.reserve(gltfModel->meshes_count);
geometryMeshes.reserve(gltfModel->meshes_count);
if(findUniqueGeometries)
{
// use some hashing based comparisons to avoid deep comparisons
std::vector<GLTFGeometryInfo> geometryInfos;
geometryInfos.reserve(gltfModel->meshes_count);
uint32_t meshIdx = 0;
for(cgltf_size m = 0; m < gltfModel->meshes_count; m++)
{
const cgltf_mesh& mesh = gltfModel->meshes[m];
GLTFGeometryInfo geoInfo;
geoInfo.setup(gltfModel, mesh);
// compare against existing hashes
uint32_t found = ~0;
for(uint32_t i = 0; i < (uint32_t)geometryInfos.size(); i++)
{
if(geoInfo.isEqualLight(geometryInfos[i]))
{
if(!geometryInfos[i].hasHash())
{
geometryInfos[i].setupHash(gltfModel, gltfModel->meshes[geometryMeshes[i]]);
}
geoInfo.setupHash(gltfModel, mesh);
if(geoInfo.isEqualHash(geometryInfos[i]))
{
found = i;
break;
}
}
}
if(found != ~uint32_t(0))
{
meshGeometries.push_back(found);
}
else
{
meshGeometries.push_back((uint32_t)geometryInfos.size());
geometryInfos.push_back(geoInfo);
geometryMeshes.push_back(uint32_t(meshIdx));
}
meshIdx++;
}
}
else
{
// 1:1 Mesh to CSFGeometry
for(cgltf_size meshIdx = 0; meshIdx < gltfModel->meshes_count; meshIdx++)
{
meshGeometries.push_back(uint32_t(meshIdx));
geometryMeshes.push_back(uint32_t(meshIdx));
}
}
csf->numGeometries = (int)geometryMeshes.size();
csf->geometries = (CSFGeometry*)CSFileMemory_alloc(mem, sizeof(CSFGeometry) * csf->numGeometries, NULL);
memset(csf->geometries, 0, sizeof(CSFGeometry) * csf->numGeometries);
// create geometries
#pragma omp parallel for
for(int outIdx = 0; outIdx < csf->numGeometries; outIdx++)
{
const cgltf_mesh& mesh = gltfModel->meshes[geometryMeshes[outIdx]];
CSFGeometry& csfgeom = csf->geometries[outIdx];
// count pass
uint32_t vertexTotCount = 0;
uint32_t indexTotCount = 0;
uint32_t partsTotCount = 0;
bool hasNormals = false;
bool hasTexcoords = false;
for(cgltf_size p = 0; p < mesh.primitives_count; p++)
{
const cgltf_primitive& primitive = mesh.primitives[p];
if(primitive.type != cgltf_primitive_type_triangles)
continue;
for(cgltf_size a = 0; a < primitive.attributes_count; a++)
{
const cgltf_accessor* accessor = primitive.attributes[a].data;
switch(primitive.attributes[a].type)
{
case cgltf_attribute_type_position:
vertexTotCount += uint32_t(accessor->count);
break;
case cgltf_attribute_type_normal:
hasNormals = true;
break;
case cgltf_attribute_type_texcoord:
if(primitive.attributes[a].index == 0)
{
hasTexcoords = true;
}
break;
default:
break; // No matching csf attribute
}
}
indexTotCount += uint32_t(primitive.indices->count);
partsTotCount++;
}
// allocate all data
csfgeom.numVertices = vertexTotCount;
csfgeom.numParts = partsTotCount;
csfgeom.vertex = (float*)CSFileMemory_alloc(mem, sizeof(float) * 3 * vertexTotCount, nullptr);
if(hasNormals)
{
csfgeom.normal = (float*)CSFileMemory_alloc(mem, sizeof(float) * 3 * vertexTotCount, nullptr);
}
if(hasTexcoords)
{
csfgeom.tex = (float*)CSFileMemory_alloc(mem, sizeof(float) * 2 * vertexTotCount, nullptr);
}
csfgeom.indexSolid = (uint32_t*)CSFileMemory_alloc(mem, sizeof(uint32_t) * indexTotCount, nullptr);
csfgeom.parts = (CSFGeometryPart*)CSFileMemory_alloc(mem, sizeof(CSFGeometryPart) * partsTotCount, nullptr);
// fill pass
indexTotCount = 0;
vertexTotCount = 0;
partsTotCount = 0;
for(cgltf_size p = 0; p < mesh.primitives_count; p++)
{
const cgltf_primitive& primitive = mesh.primitives[p];
if(primitive.type != cgltf_primitive_type_triangles)
continue;
CSFGeometryPart& csfpart = csfgeom.parts[partsTotCount++];
uint32_t vertexCount = 0;
for(cgltf_size a = 0; a < primitive.attributes_count; a++)
{
const cgltf_accessor* accessor = primitive.attributes[a].data;
const cgltf_buffer_view* view = accessor->buffer_view;
const uint8_t* data = reinterpret_cast<const uint8_t*>(view->buffer->data);
data += accessor->offset + view->offset;
switch(primitive.attributes[a].type)
{
case cgltf_attribute_type_position:
vertexCount += uint32_t(accessor->count);
for(cgltf_size i = 0; i < accessor->count; i++)
{
const float* vec = (const float*)(data + i * accessor->stride);
csfgeom.vertex[(vertexTotCount + i) * 3 + 0] = vec[0];
csfgeom.vertex[(vertexTotCount + i) * 3 + 1] = vec[1];
csfgeom.vertex[(vertexTotCount + i) * 3 + 2] = vec[2];
}
break;
case cgltf_attribute_type_normal:
for(cgltf_size i = 0; i < accessor->count; i++)
{
const float* vec = (const float*)(data + i * accessor->stride);
csfgeom.normal[(vertexTotCount + i) * 3 + 0] = vec[0];
csfgeom.normal[(vertexTotCount + i) * 3 + 1] = vec[1];
csfgeom.normal[(vertexTotCount + i) * 3 + 2] = vec[2];
}
hasNormals = true;
break;
case cgltf_attribute_type_texcoord:
if(primitive.attributes[a].index == 0)
{
for(cgltf_size i = 0; i < accessor->count; i++)
{
cgltf_accessor_read_float(accessor, i, csfgeom.tex + (i + vertexTotCount) * 2, 2);
}
}
break;
default:
break; // No matching csf attribute
}
}
{
const cgltf_accessor* accessor = primitive.indices;
const cgltf_buffer_view* view = accessor->buffer_view;
const uint8_t* data = reinterpret_cast<const uint8_t*>(view->buffer->data);
data += accessor->offset + view->offset;
uint32_t indexBegin = indexTotCount;
switch(accessor->component_type)
{
case cgltf_component_type_r_16:
for(cgltf_size i = 0; i < accessor->count; i++)
{
const uint8_t* in = data + (i * accessor->stride);
csfgeom.indexSolid[indexTotCount++] = *((const int16_t*)in) + vertexTotCount;
}
break;
case cgltf_component_type_r_16u:
for(cgltf_size i = 0; i < accessor->count; i++)
{
const uint8_t* in = data + (i * accessor->stride);
csfgeom.indexSolid[indexTotCount++] = *((const uint16_t*)in) + vertexTotCount;
}
break;
case cgltf_component_type_r_32u:
for(cgltf_size i = 0; i < accessor->count; i++)
{
const uint8_t* in = data + (i * accessor->stride);
csfgeom.indexSolid[indexTotCount++] = *((const uint32_t*)in) + vertexTotCount;
}
break;
case cgltf_component_type_r_8:
for(cgltf_size i = 0; i < accessor->count; i++)
{
const uint8_t* in = data + (i * accessor->stride);
csfgeom.indexSolid[indexTotCount++] = *((const int8_t*)in) + vertexTotCount;
}
break;
case cgltf_component_type_r_8u:
for(cgltf_size i = 0; i < accessor->count; i++)
{
const uint8_t* in = data + (i * accessor->stride);
csfgeom.indexSolid[indexTotCount++] = *((const uint8_t*)in) + vertexTotCount;
}
break;
default:
assert(0);
break;
}
csfpart.numIndexSolid = indexTotCount - indexBegin;
}
vertexTotCount += vertexCount;
csfpart.numIndexWire = 0;
csfpart._deprecated = 0;
}
csfgeom.numIndexSolid = (int)indexTotCount;
CSFGeometry_setupDefaultChannels(&csfgeom);
}
// create flattened nodes
csf->numNodes = 1; // reserve for root
csf->rootIDX = 0;
for(size_t i = 0; i < scene->nodes_count; i++)
{
CSFile_countGLTFNodes(csf, gltfModel, scene->nodes[i]);
}
csf->nodes = (CSFNode*)CSFileMemory_alloc(mem, sizeof(CSFNode) * csf->numNodes, nullptr);
memset(csf->nodes, 0, sizeof(CSFNode) * csf->numNodes);
csf->numNodes = 1;
// root setup
csf->nodes[0].geometryIDX = -1;
csf->nodes[0].numChildren = (int)scene->nodes_count;
csf->nodes[0].children = (int*)CSFileMemory_alloc(mem, sizeof(int) * scene->nodes_count, nullptr);
CSFMatrix_identity(csf->nodes[0].worldTM);
CSFMatrix_identity(csf->nodes[0].objectTM);
for(size_t i = 0; i < scene->nodes_count; i++)
{
csf->nodes[0].children[i] = CSFile_addGLTFNode(csf, gltfModel, meshGeometries.data(), mem, scene->nodes[i]);
}
CSFile_transform(csf);
cgltf_free(gltfModel);
*outcsf = csf;
return CADSCENEFILE_NOERROR;
}
#endif