overhauled saving/loading graphs, made joints global IDs, fixed some quirk with subsequent graphs loading, fixed quirks with async graph loading, some other things

This commit is contained in:
ecker 2026-06-18 20:42:39 -05:00
parent 941ad2ead9
commit aa2740979c
25 changed files with 939 additions and 817 deletions

View File

@ -3,10 +3,10 @@
"scenes": {
"start": "StartMenu",
"lights": { "enabled": true,
"lightmaps": true,
"lightmaps": false,
"max": 32,
"shadows": {
"enabled": false,
"enabled": true,
"update": 4,
"max": 16,
"samples": 1
@ -426,7 +426,7 @@
},
"loader": {
"assert": true,
"async": false
"async": true
},
"hooks": {
"defer lazy calls": true

View File

@ -4,14 +4,14 @@
"ignore": false,
"import": "./craetureModel.json",
"assets": [
"./scripts/craeture.lua"
// "./scripts/craeture.lua"
],
"behaviors": [
"CraetureBehavior",
"AudioEmitterBehavior"
],
"transform": {
"position": [ -7, -3.5, -44 ],
"position": [ -36.8997, 4.49179, 16.0879 ],
//"position": [ 0, 1.5, 21 ],
//"position": [ 16.3489, 1.37972, -68.1571 ],
//"scale": [ 0.09, 0.09, 0.09 ]
@ -32,7 +32,7 @@
}
},
"physics": {
"ragdoll": true
"ragdoll": false
}
}
/*

View File

@ -55,7 +55,7 @@
"skinned": true
},
"animations": {
// "animation": "idle"
"animation": "idle_wank"
}
}
}

View File

@ -78,7 +78,7 @@
"stream": {
"tag": "worldspawn",
"player": "info_player_spawn",
"enabled": "auto",
"enabled": false, // "auto",
"radius": 16,
"every": 1
}

View File

@ -1,7 +1,7 @@
{
"type": "Object",
"name": "Player: Model",
"ignore": true,
"ignore": false,
"import": "/model.json",
"assets": [
// "/player/pbear.glb"
@ -49,7 +49,7 @@
},
"animations": {
"animation": "wank",
"speed": 8.0
"speed": 1.0
}
}
}

View File

@ -1,7 +1,7 @@
{
"import": "./base_sourceengine.json",
"assets": [
// { "filename": "./maps/cs_office.bsp" }
{ "filename": "./maps/cs_office/graph.json" }
{ "filename": "./maps/cs_office.bsp" }
// { "filename": "./maps/cs_office/graph.json" }
]
}

View File

@ -3,6 +3,7 @@
"assets": [
// { "filename": "./maps/mcdonalds-mds.bsp" }
{ "filename": "./maps/mcdonalds-mds/graph.json" },
{ "filename": "ent://burger.json", "delay": 4 }
{ "filename": "ent://burger.json", "delay": 1 },
{ "filename": "ent://craeture.json", "delay": 2 }
]
}

View File

@ -19,43 +19,42 @@ uvec4 uvec2_16x4( uvec2 i ) {
return converted;
}
layout (binding = 0) uniform UBO {
layout (push_constant) uniform SkinningPush {
uint jointID;
uint padding1;
uint padding2;
uint padding3;
} ubo;
uint vertexOffset;
} push;
layout (std140, binding = 1) readonly buffer Joints {
layout (std140, binding = 0) readonly buffer Joints {
mat4 joints[];
};
layout (binding = 2) readonly buffer VertexInputPosition {
/*vec3 verticesInPos[];*/
layout (binding = 1) readonly buffer VertexInputPosition {
float verticesInPos[];
};
layout (binding = 3) readonly buffer VertexInputJoints {
layout (binding = 2) readonly buffer VertexInputJoints {
uvec2 verticesInJoints[];
};
layout (binding = 4) readonly buffer VertexInputWeights {
layout (binding = 3) readonly buffer VertexInputWeights {
vec4 verticesInWeights[];
};
layout (binding = 5) buffer VertexOutputPosition {
/*vec3 verticesOutPos[];*/
layout (binding = 4) buffer VertexOutputPosition {
float verticesOutPos[];
};
void main() {
const uint i = gl_GlobalInvocationID.x;
if ( i * 3 >= verticesInPos.length() || i * 3 >= verticesOutPos.length() ) return;
const vec3 inPos = vec3( verticesInPos[i * 3 + 0], verticesInPos[i * 3 + 1], verticesInPos[i * 3 + 2] );
const uvec4 inJoints = uvec2_16x4(verticesInJoints[i]);
const vec4 inWeights = verticesInWeights[i];
const mat4 skinned = inWeights.x * joints[ubo.jointID + int(inJoints.x)] + inWeights.y * joints[ubo.jointID + int(inJoints.y)] + inWeights.z * joints[ubo.jointID + int(inJoints.z)] + inWeights.w * joints[ubo.jointID + int(inJoints.w)];
const mat4 skinned = inWeights.x * joints[push.jointID + int(inJoints.x)]
+ inWeights.y * joints[push.jointID + int(inJoints.y)]
+ inWeights.z * joints[push.jointID + int(inJoints.z)]
+ inWeights.w * joints[push.jointID + int(inJoints.w)];
const vec3 outPos = vec3(skinned * vec4(inPos, 1));
verticesOutPos[i * 3 + 0] = outPos[0];

View File

@ -1,50 +0,0 @@
#version 450
#pragma shader_stage(compute)
layout (local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
#define COMPUTE 1
#include "../../common/macros.h"
#include "../../common/structs.h"
uvec4 uvec2_16x4( uvec2 i ) {
uvec4 converted;
converted.x = (i.x >> 0) & 0xFFFF;
converted.y = (i.x >> 16) & 0xFFFF;
converted.z = (i.y >> 0) & 0xFFFF;
converted.w = (i.y >> 16) & 0xFFFF;
return converted;
}
layout (binding = 0) uniform UBO {
uint jointID;
uint padding1;
uint padding2;
uint padding3;
} ubo;
layout (std140, binding = 1) readonly buffer Joints {
mat4 joints[];
};
layout (binding = 2) readonly buffer VertexInput {
Vertex verticesIn[];
};
layout (binding = 3) buffer VertexOutput {
Vertex verticesOut[];
};
void main() {
const uint i = gl_GlobalInvocationID.x;
if ( i >= verticesIn.length() || i >= verticesOut.length() ) return;
const vec3 inPos = verticesIn[i].position;
const uvec4 inJoints = uvec2_16x4(verticesIn[i].joints);
const vec4 inWeights = verticesIn[i].weights;
const mat4 skinned = inWeights.x * joints[ubo.jointID + int(inJoints.x)] + inWeights.y * joints[ubo.jointID + int(inJoints.y)] + inWeights.z * joints[ubo.jointID + int(inJoints.z)] + inWeights.w * joints[ubo.jointID + int(inJoints.w)];
verticesOut[i].position = vec3(skinned * vec4(verticesIn[i].position,1));
}

View File

@ -33,6 +33,7 @@ namespace uf {
extern UF_API uf::stl::unordered_map<uf::stl::string, uf::asset::userdata_t> map;
extern UF_API Job::container_t jobs;
extern UF_API Job::container_t finishedJobs;
extern UF_API uf::Serializer metadata;
// extern UF_API uf::Serializer map;

View File

@ -75,6 +75,13 @@ namespace pod {
} stream;
} settings;
struct StreamRegistry {
uf::stl::unordered_map<uf::stl::string, pod::AnimationStream> animations;
uf::stl::unordered_map<uf::stl::string, pod::SkinStream> skins;
uf::stl::unordered_map<uf::stl::string, pod::MeshStream> meshes;
uf::stl::unordered_map<uf::stl::string, pod::ImageStream> images;
} streams;
// Local storage, used for save/load
struct Storage {
enum StorageType : uint32_t {
@ -137,7 +144,7 @@ namespace uf {
namespace graph {
extern UF_API size_t initialBufferElements;
extern UF_API uint32_t storageMode;
extern UF_API pod::Graph::Storage storage;
extern UF_API pod::Graph::Storage globalStorage;
}
}

View File

@ -113,4 +113,32 @@ namespace pod {
uf::Image data;
uf::renderer::Texture2D handle;
};
}
namespace pod {
struct StreamRegion {
uf::stl::string filename = "";
size_t offset = 0;
size_t length = 0;
};
struct AnimationStream {
struct SamplerStream {
pod::StreamRegion inputs;
pod::StreamRegion outputs;
};
uf::stl::vector<SamplerStream> samplers;
};
struct MeshStream {
uf::stl::vector<pod::StreamRegion> buffers;
};
struct SkinStream {
pod::StreamRegion inverseBindMatrices;
};
struct ImageStream {
StreamRegion buffer;
};
}

View File

@ -58,6 +58,15 @@ namespace ext {
template<typename T> inline T as(const T& fallback) const;
Value& reserve( size_t );
inline bool isTable() const;
inline bool isObject() const;
inline bool isArray() const;
inline bool isNull() const;
inline Value& array();
inline Value& object();
inline Value& null();
};
inline bool isTable( const Value& v ) { return v.is_array() || v.is_object(); }
@ -74,4 +83,9 @@ namespace ext {
}
}
bool ext::json::Value::isTable() const { return ext::json::isTable( *this ); }
bool ext::json::Value::isObject() const { return ext::json::isObject( *this ); }
bool ext::json::Value::isArray() const { return ext::json::isArray( *this ); }
bool ext::json::Value::isNull() const { return ext::json::isNull( *this ); }
#include "nlohmann.inl"

View File

@ -51,6 +51,7 @@ template<> inline bool ext::json::Value::is<uint64_t>(bool strict) const { retur
template<> inline bool ext::json::Value::is<float>(bool strict) const { return strict ? is_number_float() : is_number(); }
template<> inline bool ext::json::Value::is<double>(bool strict) const { return strict ? is_number_float() : is_number(); }
template<> inline bool ext::json::Value::is<uf::stl::string>(bool strict) const { return is_string(); }
template<> inline bool ext::json::Value::is<uf::stl::vector<uf::stl::string>>(bool strict) const { return is_array(); }
// template<> template<typename T, size_t N> inline bool ext::json::Value::is<pod::Vector<T,N>>(bool strict) const { return is_array() && size() == N; }
// template<> inline bool ext::json::Value::is<std::string>(bool strict) const { return is_string(); }

View File

@ -46,5 +46,7 @@ namespace ext {
// dumps to disk
bool UF_API save( const pod::Dtex&, const uf::stl::string&, bool = false );
// dumps to buffer
bool UF_API save( const pod::Dtex&, uf::stl::vector<uint8_t>& );
}
}

View File

@ -21,12 +21,14 @@ namespace pod {
namespace uf {
namespace image {
bool UF_API open( pod::Image&, const uf::stl::string&, bool = true );
bool UF_API open( pod::Image&, const uf::stl::vector<uint8_t>&, const uf::stl::string&, bool = true );
void UF_API clear( pod::Image& );
void UF_API load( pod::Image&, const pod::Image::pixel_t::type_t* pointer, const pod::Vector2ui& size, size_t bpp, size_t channels, bool flip = false );
void UF_API load( pod::Image&, const pod::Image::container_t& container, const pod::Vector2ui& size, size_t bpp, size_t channels, bool flip = false );
bool UF_API save( const pod::Image&, const uf::stl::string& filename, bool flip = false );
void UF_API save( const pod::Image&, uf::stl::vector<uint8_t>&, bool flip = false );
void UF_API save( const pod::Image&, std::ostream& stream );
pod::Image::pixel_t UF_API at( pod::Image&, const pod::Vector2ui& at );
@ -57,6 +59,7 @@ namespace uf {
Image& operator=( Image&& ) noexcept = default;
bool open( const uf::stl::string& filename, bool = true ); // from file
bool open( const uf::stl::vector<uint8_t>& buffer, const uf::stl::string&, bool = true ); // from buffer
void open( const std::istream& stream ); // from stream
void move( Image::container_t&& move, const pod::Vector2ui& size ); // move from vector of pixels
void move( uf::Image&& ); // move from image object
@ -95,6 +98,7 @@ namespace uf {
void flip();
void padToPowerOfTwo();
bool save( const uf::stl::string& filename, bool flip = false ) const; // to file
bool save( uf::stl::vector<uint8_t>&, bool flip = false ) const; // to buffer
void save( std::ostream& stream ) const; // to stream
void convert( const uf::stl::string&, const uf::stl::string& = "rgba" );
Image overlay(const Image& top, const pod::Vector2ui& corner = {} ) const; // Merges one image on top of another

View File

@ -98,13 +98,19 @@ uf::stl::vector<T> uf::stl::KeyMap<T,Key>::flattenByIndex() const {
template<typename T, typename Key>
void uf::stl::KeyMap<T,Key>::merge( KeyMap<T, Key>&& other ) {
this->reserve( this->keys.size() + other.keys.size() );
this->reserve( this->keys.size() + other.keys.size() );
for ( auto& key : other.keys ) {
(*this)[key] = std::move( other.map[key] );
}
for ( auto& key : other.keys ) {
// Bypass custom operator[] and force a direct map assignment
if ( this->map.count(key) == 0 ) {
this->indices[key] = this->keys.size();
this->keys.emplace_back(key);
}
// Explicitly copy to prevent any weird std::move state corruption
this->map[key] = other.map.at(key);
}
other.clear();
other.clear();
}
template<typename T, typename Key>

View File

@ -209,8 +209,6 @@ namespace uf {
uf::stl::vector<buffer_t> buffers;
// crunge, but it's better this way for streaming in mesh data
uf::stl::vector<uf::stl::string> buffer_paths;
// mega cringe, but i'd like to have a way to cache it
uf::stl::vector<uf::Mesh::View> buffer_views;
protected:

View File

@ -53,19 +53,31 @@ namespace {
bool uf::asset::assertionLoad = true;
bool uf::asset::asyncQueue = true;
uf::asset::Job::container_t uf::asset::jobs;
uf::asset::Job::container_t uf::asset::finishedJobs;
uf::stl::unordered_map<uf::stl::string, uf::asset::userdata_t> uf::asset::map;
uf::Serializer uf::asset::metadata;
void uf::asset::processQueue() {
if ( uf::asset::jobs.empty() ) return;
if ( uf::asset::jobs.empty() && uf::asset::finishedJobs.empty() ) return;
STATIC_THREAD_LOCAL(uf::asset::Job::container_t, jobs);
uf::asset::Job::container_t finishedJobs;
mutex.lock();
std::swap( jobs, uf::asset::jobs );
std::swap( finishedJobs, uf::asset::finishedJobs );
mutex.unlock();
bool async = false; // uf::asset::asyncQueue; // a bit buggy
bool async = uf::asset::asyncQueue; // a bit buggy
auto tasks = uf::thread::schedule(async ? uf::thread::asyncThreadName : uf::thread::mainThreadName, !true);
if ( !finishedJobs.empty() ) {
tasks.queue([jobs = std::move(finishedJobs)]() {
for ( auto& job : jobs ) {
uf::hooks.call( job.callback, job.payload );
}
});
}
for ( auto& job : jobs ) tasks.queue([=]{
auto callback = job.callback;
auto type = job.type;
@ -76,7 +88,9 @@ void uf::asset::processQueue() {
uf::stl::string filename = type == "cache" ? uf::asset::cache(payload) : uf::asset::load(payload);
if ( callback != "" && filename != "" ) {
uf::hooks.call(callback, payload);
mutex.lock();
uf::asset::finishedJobs.emplace_back( job );
mutex.unlock();
}
});

View File

@ -16,58 +16,58 @@
#include <uf/engine/ext.h>
namespace {
uf::stl::string keyedID( size_t id ) {
return ::fmt::format("{}", id);
}
// lazy load animations if requested
void loadAnimation( pod::Graph& graph, const uf::stl::string& name ) {
auto& storage = uf::graph::getStorage( graph );
if ( storage.animations.map.count(name) == 0 ) return;
if ( graph.streams.animations.count(name) == 0 ) return;
auto& animation = storage.animations.map[name];
auto& animStream = graph.streams.animations[name];
//UF_ASSERT( animation.path != "" );
if ( animation.path == "" ) {
return;
for ( size_t i = 0; i < animation.samplers.size(); ++i ) {
auto& sampler = animation.samplers[i];
auto& stream = animStream.samplers[i];
if ( !sampler.inputs.empty() ) continue;
uf::stl::vector<uint8_t> ioBuf;
if ( stream.inputs.length > 0 ) {
if ( uf::io::readAsBuffer(ioBuf, stream.inputs.filename, stream.inputs.offset, stream.inputs.length) ) {
sampler.inputs.resize( stream.inputs.length / sizeof(float) );
memcpy( sampler.inputs.data(), ioBuf.data(), stream.inputs.length );
}
}
if ( stream.outputs.length > 0 ) {
if ( uf::io::readAsBuffer(ioBuf, stream.outputs.filename, stream.outputs.offset, stream.outputs.length) ) {
sampler.outputs.resize( stream.outputs.length / sizeof(pod::Vector4f) );
memcpy( sampler.outputs.data(), ioBuf.data(), stream.outputs.length );
}
}
}
uf::Serializer json;
json.readFromFile( animation.path );
animation.name = json["name"].as(animation.name);
animation.start = json["start"].as(animation.start);
animation.end = json["end"].as(animation.end);
if ( animation.samplers.empty() ) ext::json::forEach( json["samplers"], [&]( ext::json::Value& value ){
auto& sampler = animation.samplers.emplace_back();
sampler.interpolator = value["interpolator"].as(sampler.interpolator);
sampler.inputs.reserve( value["inputs"].size() );
ext::json::forEach( value["inputs"], [&]( ext::json::Value& input ){
sampler.inputs.emplace_back( input.as<float>() );
});
sampler.outputs.reserve( value["outputs"].size() );
ext::json::forEach( value["outputs"], [&]( ext::json::Value& output ){
sampler.outputs.emplace_back( uf::vector::decode( output, pod::Vector4f{} ) );
});
});
if ( animation.channels.empty() ) ext::json::forEach( json["channels"], [&]( ext::json::Value& value ){
auto& channel = animation.channels.emplace_back();
channel.path = value["path"].as(channel.path);
channel.node = value["node"].as(channel.node);
channel.sampler = value["sampler"].as(channel.sampler);
});
}
void unloadAnimation( pod::Graph& graph, const uf::stl::string& name ) {
auto& storage = uf::graph::getStorage( graph );
if ( storage.animations.map.count(name) == 0 ) return;
auto& animation = storage.animations.map[name];
animation.samplers.clear();
animation.channels.clear();
#if UF_ENV_DREAMCAST
animation.samplers.shrink_to_fit();
animation.channels.shrink_to_fit();
#endif
for ( auto& sampler : animation.samplers ) {
sampler.inputs.clear();
sampler.outputs.clear();
#if UF_ENV_DREAMCAST
sampler.inputs.shrink_to_fit();
sampler.outputs.shrink_to_fit();
#endif
}
}
pod::Matrix4f localMatrix( const pod::Graph& graph, int32_t index ) {
@ -121,6 +121,9 @@ void uf::graph::override( pod::Graph& graph ) {
// load animation data
// if ( animation.channels.empty() || animation.samplers.empty() ) ::loadAnimation( graph, name );
if ( !animation.samplers.empty() && animation.samplers[0].inputs.empty() ) {
::loadAnimation( graph, name );
}
for ( auto& channel : animation.channels ) {
auto& override = graph.settings.animations.override.map[channel.node];
@ -148,9 +151,16 @@ void uf::graph::animate( pod::Graph& graph, const uf::stl::string& _name, float
if ( key != "" ) key += ":";
uf::stl::string name = key + _name;
if ( storage.animations.map.count( name ) == 0 ) ::loadAnimation( graph, name );
//UF_MSG_DEBUG("name={}, count={}", name, storage.animations.map.count( name ) );
if ( storage.animations.map.count( name ) == 0 ) {
::loadAnimation( graph, name );
}
if ( storage.animations.map.count( name ) > 0 ) {
auto& animation = storage.animations.map[name];
if ( !animation.samplers.empty() && animation.samplers[0].inputs.empty() ) {
::loadAnimation( graph, name );
}
// if already playing, ignore it
if ( !graph.sequence.empty() && graph.sequence.front() == name ) return;
if ( immediate ) {
@ -170,6 +180,7 @@ void uf::graph::animate( pod::Graph& graph, const uf::stl::string& _name, float
void uf::graph::updateAnimation( pod::Graph& graph, float delta ) {
// update our instances
auto& storage = uf::graph::getStorage( graph );
// no skins
if ( !(graph.metadata["renderer"]["skinned"].as<bool>()) ) {
@ -248,9 +259,10 @@ void uf::graph::updateAnimation( pod::Graph& graph, pod::Node& node ) {
invArmatureMatrix = uf::matrix::inverse( uf::transform::model( armTf ) );
}
auto& name = graph.skins[node.skin];
auto& skin = storage.skins[name];
auto& joints = storage.joints[name];
auto& skinName = graph.skins[node.skin];
auto& skin = storage.skins[skinName];
auto objectKeyName = ::keyedID(node.object);
auto& joints = storage.joints[objectKeyName];
joints.resize( skin.joints.size() );
for ( size_t i = 0; i < skin.joints.size(); ++i ) {
auto nodeID = skin.joints[i];

View File

@ -25,106 +25,97 @@
#endif
namespace {
uf::Image decodeImage( ext::json::Value& json, pod::Graph& graph ) {
uf::Image decodeImage( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& imageName ) {
uf::Image image;
uf::stl::string filename = "";
size_t offset = 0, length = 0;
uf::stl::string formatHint = "";
#if UF_ENV_DREAMCAST
if (json["dtex"].isObject()) {
filename = json["dtex"]["filename"].as<uf::stl::string>();
offset = json["dtex"]["offset"].as<size_t>();
length = json["dtex"]["length"].as<size_t>();
formatHint = "dtex";
} else
#endif
if ( json["filename"].is<uf::stl::string>() ) {
const uf::stl::string directory = uf::io::directory( graph.name );
const uf::stl::string filename = uf::io::filename( json["filename"].as<uf::stl::string>() );
const uf::stl::string name = directory + "/" + filename;
if ( graph.settings.stream.textures ) {
image.setFilename(name);
} else {
image.open(name, false);
}
filename = json["filename"].as<uf::stl::string>();
offset = json["offset"].as<size_t>(0);
length = json["length"].as<size_t>(0);
formatHint = uf::io::extension(filename);
} else {
auto size = uf::vector::decode( json["size"], pod::Vector2ui{} );
size_t bpp = json["bpp"].as<size_t>();
size_t channels = json["channels"].as<size_t>();
auto pixels = uf::base64::decode( json["data"].as<uf::stl::string>() );
image.loadFromBuffer( &pixels[0], size, bpp, channels, true );
return image;
}
uf::stl::string fullPath = uf::io::directory( graph.name ) + "/" + filename;
if ( graph.settings.stream.textures ) {
auto& storage = uf::graph::getStorage(graph);
graph.streams.images[imageName] = { fullPath, offset, length };
image.setFilename(fullPath);
} else {
uf::stl::vector<uint8_t> buffer;
if (length > 0) {
uf::io::readAsBuffer(buffer, fullPath, offset, length);
} else {
uf::io::readAsBuffer(buffer, fullPath);
}
uf::image::open( image, buffer, formatHint, false );
image.setFilename(fullPath);
}
return image;
}
pod::Texture decodeTexture( ext::json::Value& json, pod::Graph& graph ) {
pod::Texture texture;
pod::Animation decodeAnimation( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& animName, const uf::stl::vector<uint8_t>& megaBuffer ) {
pod::Animation animation = {};
animation.name = json["name"].as(animation.name);
animation.start = json["start"].as<float>(0.0f);
animation.end = json["end"].as<float>(1.0f);
texture.index = json["index"].as(texture.index);
texture.sampler = json["sampler"].as(texture.sampler);
texture.remap = json["remap"].as(texture.remap);
texture.blend = json["blend"].as(texture.blend);
texture.lerp = uf::vector::decode( json["lerp"], pod::Vector4f{} );
return texture;
}
uf::stl::string binPath = "";
if (json["buffer"].is<uf::stl::string>()) {
binPath = uf::io::directory(graph.name) + "/" + json["buffer"].as<uf::stl::string>();
}
uf::renderer::Sampler decodeSampler( ext::json::Value& json, pod::Graph& graph ) {
uf::renderer::Sampler sampler;
auto& storage = uf::graph::getStorage(graph);
auto& animStream = graph.streams.animations[animName];
sampler.descriptor.filter.min = (uf::renderer::enums::Filter::type_t) json["min"].as<size_t>();
sampler.descriptor.filter.mag = (uf::renderer::enums::Filter::type_t) json["mag"].as<size_t>();
sampler.descriptor.addressMode.u = (uf::renderer::enums::AddressMode::type_t) json["u"].as<size_t>();
sampler.descriptor.addressMode.v = (uf::renderer::enums::AddressMode::type_t) json["v"].as<size_t>();
sampler.descriptor.addressMode.w = sampler.descriptor.addressMode.v;
#if UF_ENV_DREAMCAST
sampler.descriptor.filter.min = uf::renderer::enums::Filter::NEAREST;
sampler.descriptor.filter.mag = uf::renderer::enums::Filter::NEAREST;
#endif
return sampler;
}
pod::Material decodeMaterial( ext::json::Value& json, pod::Graph& graph ) {
pod::Material material;
material.colorBase = uf::vector::decode( json["base"], material.colorBase );
material.colorEmissive = uf::vector::decode( json["emissive"], material.colorEmissive );
material.factorMetallic = json["fMetallic"].as(material.factorMetallic);
material.factorRoughness = json["fRoughness"].as(material.factorRoughness);
material.factorOcclusion = json["fOcclusion"].as(material.factorOcclusion);
material.factorAlphaCutoff = json["fAlphaCutoff"].as(material.factorAlphaCutoff);
material.indexAlbedo = json["iAlbedo"].as(material.indexAlbedo);
material.indexNormal = json["iNormal"].as(material.indexNormal);
material.indexEmissive = json["iEmissive"].as(material.indexEmissive);
material.indexOcclusion = json["iOcclusion"].as(material.indexOcclusion);
material.indexMetallicRoughness = json["iMetallicRoughness"].as(material.indexMetallicRoughness);
material.indexCubemap = json["iCubemap"].as(material.indexCubemap);
material.modeCull = json["modeCull"].as(material.modeCull);
material.modeAlpha = json["modeAlpha"].as(material.modeAlpha);
return material;
}
pod::Light decodeLight( ext::json::Value& json, pod::Graph& graph ) {
pod::Light light;
light.color = uf::vector::decode( json["color"], light.color );
light.intensity = json["intensity"].as(light.intensity);
light.range = json["range"].as(light.range);
return light;
}
pod::Animation decodeAnimation( ext::json::Value& json, pod::Graph& graph ) {
pod::Animation animation;
animation.name = json["name"].as(animation.name);
animation.start = json["start"].as(animation.start);
animation.end = json["end"].as(animation.end);
ext::json::forEach( json["samplers"], [&]( ext::json::Value& value ){
ext::json::forEach( json["samplers"], [&]( ext::json::Value& value ){
auto& sampler = animation.samplers.emplace_back();
sampler.interpolator = value["interpolator"].as(sampler.interpolator);
sampler.inputs.reserve( value["inputs"].size() );
ext::json::forEach( value["inputs"], [&]( ext::json::Value& input ){
sampler.inputs.emplace_back( input.as<float>() );
});
sampler.outputs.reserve( value["outputs"].size() );
ext::json::forEach( value["outputs"], [&]( ext::json::Value& output ){
sampler.outputs.emplace_back( uf::vector::decode( output, pod::Vector4f{} ) );
});
size_t inputsCount = value["inputs"]["count"].as<size_t>();
size_t inputsOffset = value["inputs"]["offset"].as<size_t>();
size_t inputsLen = value["inputs"]["length"].as<size_t>();
size_t outputsCount = value["outputs"]["count"].as<size_t>();
size_t outputsOffset = value["outputs"]["offset"].as<size_t>();
size_t outputsLen = value["outputs"]["length"].as<size_t>();
if ( graph.settings.stream.animations ) {
pod::AnimationStream::SamplerStream sStream;
sStream.inputs = { binPath, inputsOffset, inputsLen };
sStream.outputs = { binPath, outputsOffset, outputsLen };
animStream.samplers.emplace_back(sStream);
} else {
if (inputsLen > 0 && !megaBuffer.empty()) {
sampler.inputs.resize(inputsCount);
memcpy(sampler.inputs.data(), megaBuffer.data() + inputsOffset, inputsLen);
}
if (outputsLen > 0 && !megaBuffer.empty()) {
sampler.outputs.resize(outputsCount);
memcpy(sampler.outputs.data(), megaBuffer.data() + outputsOffset, outputsLen);
}
}
});
ext::json::forEach( json["channels"], [&]( ext::json::Value& value ){
@ -133,94 +124,44 @@ namespace {
channel.node = value["node"].as(channel.node);
channel.sampler = value["sampler"].as(channel.sampler);
});
return animation;
}
pod::Skin decodeSkin( ext::json::Value& json, pod::Graph& graph ) {
pod::Skin decodeSkin( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& skinName, const uf::stl::vector<uint8_t>& megaBuffer ) {
pod::Skin skin;
skin.name = json["name"].as(skin.name);
skin.joints.reserve( json["joints"].size() );
ext::json::forEach( json["joints"], [&]( ext::json::Value& value ){
skin.joints.emplace_back( value.as<int32_t>() );
});
skin.inverseBindMatrices.reserve( json["inverseBindMatrices"].size() );
ext::json::forEach( json["inverseBindMatrices"], [&]( ext::json::Value& value ){
skin.inverseBindMatrices.emplace_back( uf::matrix::decode( value, pod::Matrix4f{} ) );
});
if (json["inverseBindMatrices"].isObject()) {
auto& invJson = json["inverseBindMatrices"];
size_t count = invJson["count"].as<size_t>();
size_t offset = invJson["offset"].as<size_t>();
size_t length = invJson["length"].as<size_t>();
uf::stl::string binPath = uf::io::directory(graph.name) + "/" + invJson["buffer"].as<uf::stl::string>();
auto& storage = uf::graph::getStorage(graph);
auto& skinStream = graph.streams.skins[skinName];
if ( graph.settings.stream.enabled ) {
skinStream.inverseBindMatrices = { binPath, offset, length };
} else {
if (length > 0 && !megaBuffer.empty()) {
skin.inverseBindMatrices.resize(count);
memcpy(skin.inverseBindMatrices.data(), megaBuffer.data() + offset, length);
}
}
}
return skin;
}
pod::Instance decodeInstance( ext::json::Value& json, pod::Graph& graph ) {
pod::Instance instance;
//instance.model = uf::matrix::decode( json["model"], instance.model );
//instance.color = uf::vector::decode( json["color"], instance.color );
instance.materialID = json["materialID"].as( instance.materialID );
instance.primitiveID = json["primitiveID"].as( instance.primitiveID );
instance.meshID = json["meshID"].as( instance.meshID );
instance.lightmapID = json["lightmapID"].as( instance.lightmapID );
instance.cubemapID = json["cubemapID"].as( -1 /*instance.cubemapID*/ );
instance.auxID = json["auxID"].as( instance.auxID );
instance.objectID = json["objectID"].as( instance.objectID );
instance.bounds.min = uf::vector::decode( json["bounds"]["min"], instance.bounds.min );
instance.bounds.max = uf::vector::decode( json["bounds"]["max"], instance.bounds.max );
return instance;
}
pod::DrawCommand decodeDrawCommand( ext::json::Value& json, pod::Graph& graph ) {
pod::DrawCommand drawCommand;
drawCommand.indices = json["indices"].as( drawCommand.indices );
drawCommand.instances = json["instances"].as( drawCommand.instances );
drawCommand.indexID = json["indexID"].as( drawCommand.indexID );
drawCommand.vertexID = json["vertexID"].as( drawCommand.vertexID );
drawCommand.instanceID = json["instanceID"].as( drawCommand.instanceID );
drawCommand.auxID = json["auxID"].as( drawCommand.auxID );
drawCommand.materialID = json["materialID"].as( drawCommand.materialID );
drawCommand.vertices = json["vertices"].as( drawCommand.vertices );
return drawCommand;
}
pod::LODMetadata decodeLODMetadata( ext::json::Value& json, pod::Graph& graph ) {
pod::LODMetadata lodMetadata;
ext::json::forEach( json, [&]( size_t i, ext::json::Value& value ){
lodMetadata.levels[i].indices = value["indices"].as( lodMetadata.levels[i].indices );
lodMetadata.levels[i].indexID = value["indexID"].as( lodMetadata.levels[i].indexID );
lodMetadata.levels[i].vertices = value["vertices"].as( lodMetadata.levels[i].vertices );
lodMetadata.levels[i].vertexID = value["vertexID"].as( lodMetadata.levels[i].vertexID );
});
return lodMetadata;
}
pod::Primitive decodePrimitive( ext::json::Value& json, pod::Graph& graph ) {
pod::Primitive prim;
prim.instance = decodeInstance( json["instance"], graph );
prim.drawCommand = decodeDrawCommand( json["drawCommand"], graph );
prim.lod = decodeLODMetadata( json["lod"], graph );
return prim;
}
uf::stl::vector<pod::Primitive> decodePrimitives( ext::json::Value& json, pod::Graph& graph ) {
uf::stl::vector<pod::Primitive> primitives;
auto name = json["name"].as<uf::stl::string>();
ext::json::forEach( json["primitives"], [&]( ext::json::Value& value ){
primitives.emplace_back( decodePrimitive( value, graph ) );
});
return primitives;
}
uf::stl::vector<pod::DrawCommand> decodeDrawCommands( ext::json::Value& json, pod::Graph& graph ) {
uf::stl::vector<pod::DrawCommand> drawCommands;
auto name = json["name"].as<uf::stl::string>();
ext::json::forEach( json["drawCommands"], [&]( ext::json::Value& value ){
drawCommands.emplace_back( decodeDrawCommand( value, graph ) );
});
return drawCommands;
}
uf::Mesh decodeMesh( ext::json::Value& json, pod::Graph& graph ) {
uf::Mesh decodeMesh( ext::json::Value& json, pod::Graph& graph, const uf::stl::string& meshName, const uf::stl::vector<uint8_t>& megaBuffer ) {
uf::Mesh mesh;
#define DESERIALIZE_MESH(N) {\
@ -249,38 +190,67 @@ namespace {
DESERIALIZE_MESH(index);
DESERIALIZE_MESH(instance);
DESERIALIZE_MESH(indirect);
#undef DESERIALIZE_MESH
auto& storage = uf::graph::getStorage(graph);
auto& meshStream = graph.streams.meshes[meshName];
mesh.buffers.reserve( json["buffers"].size() );
mesh.buffer_paths.reserve( json["buffers"].size() );
uf::stl::vector<pod::StreamRegion> localRegions;
localRegions.reserve( json["buffers"].size() );
ext::json::forEach( json["buffers"], [&]( ext::json::Value& value ){
const uf::stl::string filename = value.as<uf::stl::string>();
const uf::stl::string directory = uf::io::directory( graph.name );
// uf::io::readAsBuffer( directory + "/" + filename )
#if !UF_GRAPH_EXTENDED
mesh.buffers.emplace_back(uf::io::readAsBuffer( directory + "/" + filename ));
#else
uf::stl::string filename;
size_t offset = 0, length = 0;
if (value.isObject()) {
filename = value["filename"].as<uf::stl::string>();
offset = value["offset"].as<size_t>();
length = value["length"].as<size_t>();
} else {
filename = value.as<uf::stl::string>();
}
uf::stl::string fullPath = uf::io::directory( graph.name ) + "/" + filename;
pod::StreamRegion region = { fullPath, offset, length };
if ( graph.settings.stream.enabled ) {
mesh.buffers.emplace_back();
mesh.buffer_paths.emplace_back(directory + "/" + filename);
meshStream.buffers.push_back(region);
} else {
mesh.buffers.emplace_back(uf::io::readAsBuffer( directory + "/" + filename ));
uf::stl::vector<uint8_t> buf;
if (length > 0 && !megaBuffer.empty()) {
buf.assign(megaBuffer.begin() + offset, megaBuffer.begin() + offset + length);
} else if (length > 0) {
uf::io::readAsBuffer(buf, fullPath, offset, length);
} else {
uf::io::readAsBuffer(buf, fullPath);
}
mesh.buffers.emplace_back(std::move(buf));
localRegions.push_back(region);
}
#endif
});
// load non vertex/index buffers
auto getRegion = [&](size_t bufferIdx) -> pod::StreamRegion {
if ( graph.settings.stream.enabled ) return meshStream.buffers[bufferIdx];
return localRegions[bufferIdx];
};
for ( size_t i = 0; i < mesh.instance.attributes.size(); ++i ) {
auto& attribute = mesh.instance.attributes[i];
if ( !mesh.buffers[attribute.buffer].empty() ) continue;
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );
auto& attr = mesh.instance.attributes[i];
if ( !mesh.buffers[attr.buffer].empty() ) continue;
auto region = getRegion(attr.buffer);
uf::io::readAsBuffer(mesh.buffers[attr.buffer], region.filename, region.offset, region.length);
}
for ( size_t i = 0; i < mesh.indirect.attributes.size(); ++i ) {
auto& attribute = mesh.indirect.attributes[i];
if ( !mesh.buffers[attribute.buffer].empty() ) continue;
mesh.buffers[attribute.buffer] = uf::io::readAsBuffer( mesh.buffer_paths[attribute.buffer] );
auto& attr = mesh.indirect.attributes[i];
if ( !mesh.buffers[attr.buffer].empty() ) continue;
auto region = getRegion(attr.buffer);
uf::io::readAsBuffer(mesh.buffers[attr.buffer], region.filename, region.offset, region.length);
}
#if UF_ENV_DREAMCAST
#if UF_ENV_DREAMCAST
// remove extraneous buffers
// if ( graph.metadata["renderer"]["separate"].as<bool>() )
{
@ -291,7 +261,6 @@ namespace {
auto& attribute = mesh.vertex.attributes[i];
if ( std::find( attributesKept.begin(), attributesKept.end(), attribute.descriptor.name ) != attributesKept.end() ) continue;
remove.insert(remove.begin(), i);
UF_MSG_DEBUG("Removing mesh attribute: {}", attribute.descriptor.name);
}
for ( auto& i : remove ) {
mesh.buffers[mesh.vertex.attributes[i].buffer].clear();
@ -323,7 +292,6 @@ namespace {
}
mesh.updateDescriptor();
return mesh;
}
@ -361,20 +329,9 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
uf::Serializer serializer;
UF_DEBUG_TIMER_MULTITRACE_START("Reading {}", filename);
serializer.readFromFile( filename );
// load metadata
graph.name = filename; //serializer["name"].as<uf::stl::string>();
// graph.metadata = metadata; // serializer["metadata"];
// UF_MSG_DEBUG("A: {}", serializer["metadata"].dump(1, '\t'));
// UF_MSG_DEBUG("B: {}", metadata.dump(1, '\t'));
#if 0
graph.metadata = serializer["metadata"];
graph.metadata.merge( metadata, false );
#else
graph.name = filename;
graph.metadata = metadata;
graph.metadata.merge( serializer["metadata"], true );
#endif
// UF_MSG_DEBUG("C: {}", graph.metadata.dump(1, '\t'));
#if UF_GRAPH_LOAD_MULTITHREAD
auto tasks = uf::thread::schedule(true);
@ -440,185 +397,222 @@ void uf::graph::load( pod::Graph& graph, const uf::stl::string& filename, const
if ( key != "" ) key += ":";
tasks.queue([&]{
// load images
UF_DEBUG_TIMER_MULTITRACE("Reading material information...");
auto& node = serializer["materials"];
if (node.isObject() && node["buffer"].is<uf::stl::string>()) {
uf::stl::string binName = node["buffer"].as<uf::stl::string>();
uf::stl::vector<uint8_t> ioBuf;
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
pod::Material* rawMaterials = reinterpret_cast<pod::Material*>(ioBuf.data());
auto names = node["names"].as<uf::stl::vector<uf::stl::string>>();
graph.materials.reserve(names.size());
for (size_t i = 0; i < names.size(); ++i) {
auto name = key + names[i];
storage.materials[name] = rawMaterials[i];
graph.materials.emplace_back(name);
}
}
}
UF_DEBUG_TIMER_MULTITRACE("Read material information");
});
tasks.queue([&]{
UF_DEBUG_TIMER_MULTITRACE("Reading texture information...");
auto& node = serializer["textures"];
if (node.isObject() && node["buffer"].is<uf::stl::string>()) {
uf::stl::string binName = node["buffer"].as<uf::stl::string>();
uf::stl::vector<uint8_t> ioBuf;
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
pod::Texture* rawTextures = reinterpret_cast<pod::Texture*>(ioBuf.data());
auto names = node["names"].as<uf::stl::vector<uf::stl::string>>();
graph.textures.reserve(names.size());
for (size_t i = 0; i < names.size(); ++i) {
auto name = key + names[i];
storage.textures[name] = rawTextures[i];
graph.textures.emplace_back(name);
}
}
}
UF_DEBUG_TIMER_MULTITRACE("Read texture information");
});
tasks.queue([&]{
UF_DEBUG_TIMER_MULTITRACE("Reading sampler information...");
auto& node = serializer["samplers"];
if (node.isObject() && node["buffer"].is<uf::stl::string>()) {
uf::stl::string binName = node["buffer"].as<uf::stl::string>();
uf::stl::vector<uint8_t> ioBuf;
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
uf::renderer::Sampler* rawSamplers = reinterpret_cast<uf::renderer::Sampler*>(ioBuf.data());
auto names = node["names"].as<uf::stl::vector<uf::stl::string>>();
graph.samplers.reserve(names.size());
for (size_t i = 0; i < names.size(); ++i) {
auto name = key + names[i];
storage.samplers[name] = rawSamplers[i];
graph.samplers.emplace_back(name);
}
}
}
UF_DEBUG_TIMER_MULTITRACE("Read sampler information");
});
tasks.queue([&]{
UF_DEBUG_TIMER_MULTITRACE("Reading lighting information...");
auto& node = serializer["lights"];
if (node.isObject() && node["buffer"].is<uf::stl::string>()) {
uf::stl::string binName = node["buffer"].as<uf::stl::string>();
uf::stl::vector<uint8_t> ioBuf;
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
pod::Light* rawLights = reinterpret_cast<pod::Light*>(ioBuf.data());
auto names = node["names"].as<uf::stl::vector<uf::stl::string>>();
graph.lights.reserve(names.size());
for (size_t i = 0; i < names.size(); ++i) {
auto name = key + names[i];
graph.lights[name] = rawLights[i];
}
}
}
UF_DEBUG_TIMER_MULTITRACE("Read lighting information");
});
tasks.queue([&]{
UF_DEBUG_TIMER_MULTITRACE("Reading primitives...");
graph.primitives.reserve( serializer["primitives"].size() );
ext::json::forEach( serializer["primitives"], [&]( ext::json::Value& value ){
uf::stl::string binName = "primitives.bin";
uf::stl::vector<uint8_t> ioBuf;
pod::Primitive* allPrimitives = nullptr;
if (uf::io::readAsBuffer(ioBuf, directory + binName)) {
allPrimitives = reinterpret_cast<pod::Primitive*>(ioBuf.data());
}
auto& primNode = serializer["primitives"];
graph.primitives.reserve( primNode.size() );
ext::json::forEach( primNode, [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
// UF_MSG_DEBUG("{}", name);
storage.primitives[name] = decodePrimitives( value, graph );
graph.primitives.emplace_back(name);
bool hasOffset = !value["offset"].isNull();
if (allPrimitives && hasOffset) {
size_t count = value["count"].as<size_t>();
size_t offsetBytes = value["offset"].as<size_t>();
size_t startIndex = offsetBytes / sizeof(pod::Primitive);
storage.primitives[name].assign(&allPrimitives[startIndex], &allPrimitives[startIndex + count]);
} else {
UF_MSG_WARNING("Primitive '{}' missing binary data. Buffer Loaded: {} | Offset in JSON: {}",
name, (allPrimitives != nullptr), hasOffset);
}
});
UF_DEBUG_TIMER_MULTITRACE("Read primitives.");
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
});
tasks.queue([&]{
// load mesh information
UF_DEBUG_TIMER_MULTITRACE("Reading meshes...");
graph.meshes.reserve( serializer["meshes"].size() );
ext::json::forEach( serializer["meshes"], [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
// UF_MSG_DEBUG("{}", name);
storage.meshes[name] = decodeMesh( value, graph );
graph.meshes.emplace_back(name);
});
UF_DEBUG_TIMER_MULTITRACE("Read meshes");
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
});
tasks.queue([&]{
// load images
UF_DEBUG_TIMER_MULTITRACE("Reading images...");
graph.images.reserve( serializer["images"].size() );
ext::json::forEach( serializer["images"], [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
// UF_MSG_DEBUG("{}", name);
storage.images[name] = {
.data = decodeImage( value, graph ),
.data = decodeImage( value, graph, name ),
};
graph.images.emplace_back(name);
});
UF_DEBUG_TIMER_MULTITRACE("Read images");
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
#if 0 && !UF_ENV_DREAMCAST
if ( !ext::json::isNull( serializer["atlas"] ) ) {
UF_DEBUG_TIMER_MULTITRACE("Reading atlas...");
auto& image = graph.atlas.getAtlas();
auto& value = serializer["atlas"];
if ( value.is<uf::stl::string>() ) {
uf::stl::string filename = directory + "/" + value.as<uf::stl::string>();
UF_DEBUG_TIMER_MULTITRACE("Reading atlas " << filename);
image.open(filename, false);
} else {
decode( value, image, graph );
});
tasks.queue([&]{
UF_DEBUG_TIMER_MULTITRACE("Reading meshes...");
graph.meshes.reserve( serializer["meshes"].size() );
uf::stl::vector<uint8_t> megaBuffer;
bool bufferAttempted = false;
ext::json::forEach( serializer["meshes"], [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
if (!bufferAttempted && value["buffers"].size() > 0 && value["buffers"][0].isObject()) {
uf::stl::string binName = value["buffers"][0]["filename"].as<uf::stl::string>();
uf::io::readAsBuffer(megaBuffer, directory + binName);
bufferAttempted = true;
}
}
#endif
});
tasks.queue([&]{
// load texture information
UF_DEBUG_TIMER_MULTITRACE("Reading texture information...");
graph.textures.reserve( serializer["textures"].size() );
ext::json::forEach( serializer["textures"], [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
// UF_MSG_DEBUG("{}", name);
storage.textures[name] = decodeTexture( value, graph );
graph.textures.emplace_back(name);
storage.meshes[name] = decodeMesh( value, graph, name, megaBuffer );
graph.meshes.emplace_back(name);
});
UF_DEBUG_TIMER_MULTITRACE("Read texture information");
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
UF_DEBUG_TIMER_MULTITRACE("Read meshes");
});
tasks.queue([&]{
// load sampler information
UF_DEBUG_TIMER_MULTITRACE("Reading sampler information...");
graph.samplers.reserve( serializer["samplers"].size() );
ext::json::forEach( serializer["samplers"], [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
// UF_MSG_DEBUG("{}", name);
storage.samplers[name] = decodeSampler( value, graph );
graph.samplers.emplace_back(name);
});
UF_DEBUG_TIMER_MULTITRACE("Read sampler information");
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
});
tasks.queue([&]{
// load material information
UF_DEBUG_TIMER_MULTITRACE("Reading material information...");
graph.materials.reserve( serializer["materials"].size() );
ext::json::forEach( serializer["materials"], [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
// UF_MSG_DEBUG("{}", name);
storage.materials[name] = decodeMaterial( value, graph );
graph.materials.emplace_back(name);
});
UF_DEBUG_TIMER_MULTITRACE("Read material information");
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
});
tasks.queue([&]{
// load light information
UF_DEBUG_TIMER_MULTITRACE("Reading lighting information...");
graph.lights.reserve( serializer["lights"].size() );
ext::json::forEach( serializer["lights"], [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
// UF_MSG_DEBUG("{}", name);
graph.lights[name] = decodeLight( value, graph );
});
UF_DEBUG_TIMER_MULTITRACE("Read lighting information");
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
});
#if 1
tasks.queue([&]{
// load animation information
UF_DEBUG_TIMER_MULTITRACE("Reading animation information...");
storage.animations.map.reserve( serializer["animations"].size() );
ext::json::forEach( serializer["animations"], [&]( ext::json::Value& value ){
if ( value.is<uf::stl::string>() ) {
auto path = directory + "/" + value.as<uf::stl::string>();
auto& animNode = serializer["animations"];
uf::stl::vector<uint8_t> megaBuffer;
bool bufferAttempted = false;
if (animNode.isObject()) {
storage.animations.map.reserve( animNode.size() );
ext::json::forEach( animNode, [&]( const uf::stl::string& rawName, ext::json::Value& value ){
auto name = key + rawName;
if (!bufferAttempted && value["buffer"].is<uf::stl::string>()) {
uf::stl::string binName = value["buffer"].as<uf::stl::string>();
uf::io::readAsBuffer(megaBuffer, directory + binName);
bufferAttempted = true;
}
storage.animations[name] = decodeAnimation( value, graph, name, megaBuffer );
graph.animations.emplace_back(name);
});
}
else if (animNode.isArray()) {
storage.animations.map.reserve( animNode.size() );
ext::json::forEach( animNode, [&]( ext::json::Value& value ){
uf::stl::string path = directory + "/" + value.as<uf::stl::string>();
uf::Serializer json;
json.readFromFile( path );
auto name = key + json["name"].as<uf::stl::string>();
if ( graph.settings.stream.animations ) {
storage.animations[name].path = path;
} else {
storage.animations[name] = decodeAnimation( json, graph );
}
graph.animations.emplace_back(name);
} else {
// UF_MSG_DEBUG("{}", name);
if ( value["filename"].is<uf::stl::string>() ) {
auto path = directory + "/" + value.as<uf::stl::string>();
uf::Serializer json;
json.readFromFile( path );
auto name = key + json["name"].as<uf::stl::string>();
if ( graph.settings.stream.animations ) {
storage.animations[name].path = path;
} else {
storage.animations[name] = decodeAnimation( json, graph );
}
graph.animations.emplace_back(name);
} else {
auto name = key + value["name"].as<uf::stl::string>();
storage.animations[name] = decodeAnimation( value, graph );
graph.animations.emplace_back(name);
}
}
});
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
storage.animations[name] = decodeAnimation( json, graph, name, megaBuffer );
graph.animations.emplace_back(name);
});
}
UF_DEBUG_TIMER_MULTITRACE("Read animations");
});
tasks.queue([&]{
// load skin information
UF_DEBUG_TIMER_MULTITRACE("Reading skinning information...");
graph.skins.reserve( serializer["skins"].size() );
uf::stl::vector<uint8_t> megaBuffer;
bool bufferAttempted = false;
ext::json::forEach( serializer["skins"], [&]( ext::json::Value& value ){
auto name = key + value["name"].as<uf::stl::string>();
// UF_MSG_DEBUG("{}", name);
storage.skins[name] = decodeSkin( value, graph );
if (!bufferAttempted && value["inverseBindMatrices"].isObject()) {
uf::stl::string binName = value["inverseBindMatrices"]["buffer"].as<uf::stl::string>();
uf::io::readAsBuffer(megaBuffer, directory + binName);
bufferAttempted = true;
}
storage.skins[name] = decodeSkin( value, graph, name, megaBuffer );
graph.skins.emplace_back(name);
});
#if UF_ENV_DREAMCAST
DC_STATS();
#endif
UF_DEBUG_TIMER_MULTITRACE("Read skins");
});
#endif
tasks.queue([&]{
// load node information
UF_DEBUG_TIMER_MULTITRACE("Reading nodes...");
graph.nodes.reserve( serializer["nodes"].size() );
ext::json::forEach( serializer["nodes"], [&]( ext::json::Value& value ){

View File

@ -20,145 +20,77 @@ namespace {
uf::stl::string conversion = "";
};
uf::Serializer encode( const uf::Image& image, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
json["size"] = uf::vector::encode( image.getDimensions() );
json["bpp"] = image.getBpp() / image.getChannels();
json["channels"] = image.getChannels();
json["data"] = uf::base64::encode( image.getPixels() );
return json;
}
uf::Serializer encode( const pod::Texture& texture, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
json["index"] = texture.index;
json["sampler"] = texture.sampler;
json["remap"] = texture.remap;
json["blend"] = texture.blend;
json["lerp"] = uf::vector::encode( texture.lerp, settings );
return json;
}
uf::Serializer encode( const uf::renderer::Sampler& sampler, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
json["min"] = sampler.descriptor.filter.min;
json["mag"] = sampler.descriptor.filter.mag;
json["u"] = sampler.descriptor.addressMode.u;
json["v"] = sampler.descriptor.addressMode.v;
return json;
}
uf::Serializer encode( const pod::Material& material, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
json["base"] = uf::vector::encode( material.colorBase, settings );
json["emissive"] = uf::vector::encode( material.colorEmissive, settings );
json["fMetallic"] = material.factorMetallic;
json["fRoughness"] = material.factorRoughness;
json["fOcclusion"] = material.factorOcclusion;
json["fAlphaCutoff"] = material.factorAlphaCutoff;
if ( material.indexAlbedo >= 0 ) json["iAlbedo"] = material.indexAlbedo;
if ( material.indexNormal >= 0 ) json["iNormal"] = material.indexNormal;
if ( material.indexEmissive >= 0 ) json["iEmissive"] = material.indexEmissive;
if ( material.indexOcclusion >= 0 ) json["iOcclusion"] = material.indexOcclusion;
if ( material.indexMetallicRoughness >= 0 ) json["iMetallicRoughness"] = material.indexMetallicRoughness;
if ( material.indexCubemap >= 0 ) json["iCubemap"] = material.indexCubemap;
json["modeCull"] = material.modeCull;
json["modeAlpha"] = material.modeAlpha;
return json;
}
uf::Serializer encode( const pod::Light& light, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
json["color"] = uf::vector::encode( light.color, settings );
json["intensity"] = light.intensity;
json["range"] = light.range;
return json;
}
uf::Serializer encode( const pod::Animation& animation, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer encode( const pod::Animation& animation, const EncodingSettings& settings, const pod::Graph& graph, uf::stl::vector<uint8_t>& outBuffer, const uf::stl::string& binFilename ) {
uf::Serializer json;
json["name"] = animation.name;
json["start"] = animation.start;
json["end"] = animation.end;
auto appendToBuffer = [&](const void* data, size_t size) -> size_t {
size_t offset = outBuffer.size();
if (size > 0) {
const uint8_t* bytes = static_cast<const uint8_t*>(data);
outBuffer.insert(outBuffer.end(), bytes, bytes + size);
}
return offset;
};
ext::json::reserve( json["samplers"], animation.samplers.size() );
auto& samplers = json["samplers"];
for ( auto& sampler : animation.samplers ) {
auto& json = samplers.emplace_back();
json["interpolator"] = sampler.interpolator;
for ( auto& input : sampler.inputs ) {
json["inputs"].emplace_back(input);
}
for ( auto& output : sampler.outputs ) {
json["outputs"].emplace_back(uf::vector::encode( output, settings ));
}
auto& sJson = samplers.emplace_back();
sJson["interpolator"] = sampler.interpolator;
size_t inputsSize = sampler.inputs.size() * sizeof(float);
sJson["inputs"]["count"] = sampler.inputs.size();
sJson["inputs"]["offset"] = appendToBuffer(sampler.inputs.data(), inputsSize);
sJson["inputs"]["length"] = inputsSize;
size_t outputsSize = sampler.outputs.size() * sizeof(pod::Vector4f);
sJson["outputs"]["count"] = sampler.outputs.size();
sJson["outputs"]["offset"] = appendToBuffer(sampler.outputs.data(), outputsSize);
sJson["outputs"]["length"] = outputsSize;
}
json["buffer"] = binFilename;
ext::json::reserve( json["channels"], animation.channels.size() );
auto& channels = json["channels"];
for ( auto& channel : animation.channels ) {
auto& json = channels.emplace_back();
json["path"] = channel.path;
json["node"] = channel.node;
json["sampler"] = channel.sampler;
auto& cJson = channels.emplace_back();
cJson["path"] = channel.path;
cJson["node"] = channel.node;
cJson["sampler"] = channel.sampler;
}
return json;
}
uf::Serializer encode( const pod::Skin& skin, const EncodingSettings& settings, const pod::Graph& graph ) {
}
uf::Serializer encode( const pod::Skin& skin, const EncodingSettings& settings, const pod::Graph& graph, uf::stl::vector<uint8_t>& outBuffer, const uf::stl::string& binFilename ) {
uf::Serializer json;
json["name"] = skin.name;
ext::json::reserve( json["joints"], skin.joints.size() );
for ( auto& joint : skin.joints ) json["joints"].emplace_back( joint );
ext::json::reserve( json["inverseBindMatrices"], skin.inverseBindMatrices.size() );
for ( auto& inverseBindMatrix : skin.inverseBindMatrices )
json["inverseBindMatrices"].emplace_back( uf::matrix::encode(inverseBindMatrix, settings) );
return json;
}
uf::Serializer encode( const pod::Instance& instance, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
// json["model"] = uf::matrix::encode( instance.model, settings );
// json["color"] = uf::vector::encode( instance.color, settings );
json["materialID"] = instance.materialID;
json["primitiveID"] = instance.primitiveID;
json["meshID"] = instance.meshID;
json["lightmapID"] = instance.lightmapID;
json["cubemapID"] = instance.cubemapID;
json["objectID"] = instance.objectID;
json["auxID"] = instance.auxID;
json["bounds"]["min"] = uf::vector::encode( instance.bounds.min, settings );
json["bounds"]["max"] = uf::vector::encode( instance.bounds.max, settings );
return json;
}
uf::Serializer encode( const pod::DrawCommand& drawCommand, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
json["indices"] = drawCommand.indices;
json["instances"] = drawCommand.instances;
json["indexID"] = drawCommand.indexID;
json["vertexID"] = drawCommand.vertexID;
json["instanceID"] = drawCommand.instanceID;
json["auxID"] = drawCommand.auxID;
json["materialID"] = drawCommand.materialID;
json["vertices"] = drawCommand.vertices;
return json;
}
uf::Serializer encode( const pod::LODMetadata& lodMetadata, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
ext::json::reserve( json, 4 );
for ( size_t i = 0; i < 4; ++i ) {
auto& value = json.emplace_back();
value["indices"] = lodMetadata.levels[i].indices;
value["indexID"] = lodMetadata.levels[i].indexID;
value["vertexID"] = lodMetadata.levels[i].vertexID;
value["vertices"] = lodMetadata.levels[i].vertices;
for ( auto& joint : skin.joints ) {
json["joints"].emplace_back( joint );
}
return json;
}
uf::Serializer encode( const pod::Primitive& primitive, const EncodingSettings& settings, const pod::Graph& graph ) {
uf::Serializer json;
json["drawCommand"] = encode( primitive.drawCommand, settings, graph );
json["instance"] = encode( primitive.instance, settings, graph );
json["lod"] = encode( primitive.lod, settings, graph );
return json;
}
size_t matricesSize = skin.inverseBindMatrices.size() * sizeof(pod::Matrix4f);
size_t offset = outBuffer.size();
if ( matricesSize > 0 ) {
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(skin.inverseBindMatrices.data());
outBuffer.insert(outBuffer.end(), bytes, bytes + matricesSize);
}
json["inverseBindMatrices"]["buffer"] = binFilename;
json["inverseBindMatrices"]["count"] = skin.inverseBindMatrices.size();
json["inverseBindMatrices"]["offset"] = offset;
json["inverseBindMatrices"]["length"] = matricesSize;
return json;
}
uf::Mesh reencode( const uf::Mesh& _mesh, const uf::stl::string& conversion ) {
uf::Mesh mesh = _mesh.copy();
if ( conversion != "" ) {
@ -177,9 +109,9 @@ namespace {
return mesh;
}
uf::Serializer encode( const uf::Mesh& mesh, const EncodingSettings& settings, const pod::Graph& graph, bool checkForConversions = true ) {
uf::Serializer encode( const uf::Mesh& mesh, const EncodingSettings& settings, const pod::Graph& graph, uf::stl::vector<uint8_t>& outBuffer, const uf::stl::string& binFilename, bool checkForConversions = true ) {
if ( checkForConversions && settings.conversion != "" ) {
// return encode( reencode( mesh, settings.conversion ), settings, graph, false );
// return encode( reencode( mesh, settings.conversion ), settings, graph, outBuffer, binFilename, false );
}
uf::Serializer json;
@ -212,10 +144,19 @@ namespace {
ext::json::reserve( json["buffers"], mesh.buffers.size() );
for ( auto i = 0; i < mesh.buffers.size(); ++i ) {
const uf::stl::string filename = ::fmt::format("{}.buffer.{}.{}", settings.filename, i, ( settings.compression == "none" ? "bin" : settings.compression ) );
uf::io::write( filename, mesh.buffers[i] );
json["buffers"].emplace_back(uf::io::filename( filename ));
size_t offset = outBuffer.size();
size_t length = mesh.buffers[i].size();
outBuffer.insert(outBuffer.end(), mesh.buffers[i].begin(), mesh.buffers[i].end());
auto& bufJson = json["buffers"].emplace_back();
bufJson["filename"] = binFilename;
bufJson["offset"] = offset;
bufJson["length"] = length;
}
#undef SERIALIZE_MESH
return json;
}
uf::Serializer encode( const pod::Node& node, const EncodingSettings& settings, const pod::Graph& graph ) {
@ -273,164 +214,229 @@ uf::stl::string uf::graph::save( const pod::Graph& graph, const uf::stl::string&
auto& storage = uf::graph::getStorage( graph );
tasks.queue([&]{
uf::stl::vector<pod::Material> flatMaterials;
flatMaterials.reserve(graph.materials.size());
for ( auto& name : graph.materials ) {
flatMaterials.push_back(storage.materials.map.at(name));
}
if (!flatMaterials.empty()) {
size_t length = flatMaterials.size() * sizeof(pod::Material);
uf::stl::string binName = "materials.bin";
uf::io::write(directory + "/" + binName, flatMaterials.data(), length);
serializer["materials"]["names"] = graph.materials;
serializer["materials"]["buffer"] = binName;
serializer["materials"]["count"] = flatMaterials.size();
serializer["materials"]["length"] = length;
}
});
tasks.queue([&]{
uf::stl::vector<pod::Texture> flatTextures;
flatTextures.reserve(graph.textures.size());
for ( auto& name : graph.textures ) {
flatTextures.push_back(storage.textures.map.at(name));
}
if (!flatTextures.empty()) {
size_t length = flatTextures.size() * sizeof(pod::Texture);
uf::stl::string binName = "textures.bin";
uf::io::write(directory + "/" + binName, flatTextures.data(), length);
serializer["textures"]["names"] = graph.textures;
serializer["textures"]["buffer"] = binName;
serializer["textures"]["count"] = flatTextures.size();
serializer["textures"]["length"] = length;
}
});
tasks.queue([&]{
uf::stl::vector<uf::renderer::Sampler> flatSamplers;
flatSamplers.reserve(graph.samplers.size());
for ( auto& name : graph.samplers ) {
flatSamplers.push_back(storage.samplers.map.at(name));
}
if (!flatSamplers.empty()) {
size_t length = flatSamplers.size() * sizeof(uf::renderer::Sampler);
uf::stl::string binName = "samplers.bin";
uf::io::write(directory + "/" + binName, flatSamplers.data(), length);
serializer["samplers"]["names"] = graph.samplers;
serializer["samplers"]["buffer"] = binName;
serializer["samplers"]["count"] = flatSamplers.size();
serializer["samplers"]["length"] = length;
}
});
tasks.queue([&]{
uf::stl::vector<pod::Light> flatLights;
uf::stl::vector<uf::stl::string> lightNames;
flatLights.reserve(graph.lights.size());
lightNames.reserve(graph.lights.size());
for ( auto& pair : graph.lights ) {
lightNames.push_back(pair.first);
flatLights.push_back(pair.second);
}
if (!flatLights.empty()) {
size_t length = flatLights.size() * sizeof(pod::Light);
uf::stl::string binName = "lights.bin";
uf::io::write(directory + "/" + binName, flatLights.data(), length);
serializer["lights"]["names"] = lightNames;
serializer["lights"]["buffer"] = binName;
serializer["lights"]["count"] = flatLights.size();
serializer["lights"]["length"] = length;
}
});
tasks.queue([&]{
uf::stl::vector<pod::Primitive> flatPrimitives;
size_t totalPrimitives = 0;
for ( auto& name : graph.primitives ) {
totalPrimitives += storage.primitives.map.at(name).size();
}
flatPrimitives.reserve(totalPrimitives);
ext::json::reserve( serializer["primitives"], graph.primitives.size() );
for ( size_t i = 0; i < graph.primitives.size(); ++i ) {
auto& name = graph.primitives[i];
auto& primitives = /*graph.storage*/storage.primitives.map.at(name);
auto& primArray = storage.primitives.map.at(name);
size_t byteOffset = flatPrimitives.size() * sizeof(pod::Primitive);
size_t byteLength = primArray.size() * sizeof(pod::Primitive);
flatPrimitives.insert(flatPrimitives.end(), primArray.begin(), primArray.end());
auto& json = serializer["primitives"].emplace_back();
json["name"] = name;
// ext::json::reserve( json["primitives"], primitives.size() );
for ( auto& primitive : primitives ) {
json["primitives"].emplace_back( encode( primitive, settings, graph ) );
}
json["count"] = primArray.size();
json["offset"] = byteOffset;
json["length"] = byteLength;
}
if ( !flatPrimitives.empty() ) {
uf::stl::string binName = "primitives.bin";
uf::io::write(directory + "/" + binName, flatPrimitives.data(), flatPrimitives.size() * sizeof(pod::Primitive));
serializer["metadata"]["buffers"]["primitives"] = binName;
}
});
tasks.queue([&]{
// store mesh information
ext::json::reserve( serializer["meshes"], graph.meshes.size() );
if ( !settings.combined ) {
::EncodingSettings s = settings;
for ( size_t i = 0; i < graph.meshes.size(); ++i ) {
auto& name = graph.meshes[i];
auto& mesh = /*graph.storage*/storage.meshes.map.at(name);
if ( !s.encodeBuffers ) {
s.filename = ::fmt::format("{}/mesh.{}.json", directory, i );
encode(mesh, s, graph).writeToFile(s.filename);
uf::Serializer json;
json["name"] = name;
json["filename"] = uf::io::filename(s.filename);
serializer["meshes"].emplace_back( json );
} else {
s.filename = ::fmt::format("{}/mesh.{}", directory, i );
auto json = encode(mesh, s, graph);
json["name"] = name;
serializer["meshes"].emplace_back(json);
}
}
} else {
for ( auto& name : graph.meshes ) {
auto& mesh = /*graph.storage*/storage.meshes.map.at(name);
auto json = encode(mesh, settings, graph);
json["name"] = name;
serializer["meshes"].emplace_back(json);
}
uf::stl::vector<uint8_t> meshesBuffer;
uf::stl::string binName = "meshes." + (settings.compression == "none" ? "bin" : settings.compression);
for ( auto& name : graph.meshes ) {
auto& mesh = storage.meshes.map.at(name);
auto json = encode(mesh, settings, graph, meshesBuffer, binName);
json["name"] = name;
serializer["meshes"].emplace_back(json);
}
if ( !meshesBuffer.empty() ) {
uf::io::write(directory + "/" + binName, meshesBuffer);
}
});
#if 0
tasks.queue([&]{
if ( uf::graphs::storage.atlases[graph.atlas].generated() ) {
auto atlasName = filename + "/" + "atlas";
auto& atlas = /*graph.storage*/storage.atlases[atlasName];
auto& image = atlas.getAtlas();
if ( !settings.combined ) {
image.save(directory + "/atlas.png");
serializer["atlas"] = "atlas.png";
} else {
serializer["atlas"] = encode(image, settings, graph);
}
ext::json::reserve( serializer["animations"], graph.animations.size() );
uf::stl::vector<uint8_t> animsBuffer;
uf::stl::string binName = "animations.bin";
for ( auto& name : graph.animations ) {
auto& animation = storage.animations.map.at(name);
serializer["animations"][name] = encode(animation, settings, graph, animsBuffer, binName);
}
if ( !animsBuffer.empty() ) {
uf::io::write(directory + "/" + binName, animsBuffer);
}
});
#endif
tasks.queue([&]{
ext::json::reserve( serializer["skins"], graph.skins.size() );
uf::stl::vector<uint8_t> skinsBuffer;
uf::stl::string binName = "skins.bin";
for ( auto& name : graph.skins ) {
auto& skin = storage.skins.map.at(name);
serializer["skins"].emplace_back( encode(skin, settings, graph, skinsBuffer, binName) );
}
if ( !skinsBuffer.empty() ) {
uf::io::write(directory + "/" + binName, skinsBuffer);
}
});
tasks.queue([&]{
ext::json::reserve( serializer["images"], graph.images.size() );
if ( !settings.combined ) {
for ( size_t i = 0; i < graph.images.size(); ++i ) {
auto& name = graph.images[i];
auto& image = /*graph.storage*/storage.images.map.at(name).data;
uf::stl::vector<uint8_t> imagesBuffer;
uf::stl::vector<uint8_t> dtexBuffer;
uf::stl::string binName = "images.bin";
uf::stl::string dtexBinName = "images.dtex.bin";
for ( size_t i = 0; i < graph.images.size(); ++i ) {
auto& name = graph.images[i];
auto& image = storage.images.map.at(name).data;
uf::Serializer json;
json["name"] = name;
if ( !settings.combined ) {
uf::stl::string f = ::fmt::format("image.{}.png", i );
image.save(::fmt::format("{}/{}", directory, f));
// export DC's .dtex
#if UF_USE_DC_TEXCONV
// to-do: properly scale per my script
auto converted = image.scale( {32, 32}, "nearest" );
auto dtex = ext::texconv::convert( converted );
ext::texconv::save( dtex, ::fmt::format("{}/image.{}", directory, i) );
#endif
uf::Serializer json;
json["name"] = name;
json["filename"] = f;
serializer["images"].emplace_back( json );
}
} else {
for ( auto& name : graph.images ) {
auto& image = /*graph.storage*/storage.images.map.at(name).data;
auto json = encode(image, settings, graph);
json["name"] = name;
serializer["images"].emplace_back( json );
} else {
uf::stl::vector<uint8_t> pngBytes;
image.save( pngBytes );
size_t offset = imagesBuffer.size();
size_t length = pngBytes.size();
imagesBuffer.insert(imagesBuffer.end(), pngBytes.begin(), pngBytes.end());
json["filename"] = binName;
json["offset"] = offset;
json["length"] = length;
}
#if UF_USE_DC_TEXCONV
auto converted = image.scale( {32, 32}, "nearest" );
uf::stl::vector<uint8_t> dtexBytes;
auto dtex = ext::texconv::convert( converted );
ext::texconv::save( dtex, dtexBytes );
size_t dtexOffset = dtexBuffer.size();
size_t dtexLength = dtexBytes.size();
dtexBuffer.insert(dtexBuffer.end(), dtexBytes.begin(), dtexBytes.end());
json["dtex"]["filename"] = dtexBinName;
json["dtex"]["offset"] = dtexOffset;
json["dtex"]["length"] = dtexLength;
#endif
serializer["images"].emplace_back( json );
}
});
tasks.queue([&]{
// store texture information
ext::json::reserve( serializer["textures"], graph.textures.size() );
for ( auto& name : graph.textures ) {
auto& texture = /*graph.storage*/storage.textures.map.at(name);
auto json = encode(texture, settings, graph);
json["name"] = name;
serializer["textures"].emplace_back(json);
if ( settings.combined && !imagesBuffer.empty() ) {
uf::io::write(directory + "/" + binName, imagesBuffer);
}
});
tasks.queue([&]{
// store sampler information
ext::json::reserve( serializer["samplers"], graph.samplers.size() );
for ( auto& name : graph.samplers ) {
auto& sampler = /*graph.storage*/storage.samplers.map.at(name);
auto json = encode(sampler, settings, graph);
json["name"] = name;
serializer["samplers"].emplace_back(json);
#if UF_USE_DC_TEXCONV
if ( !dtexBuffer.empty() ) {
uf::io::write(directory + "/" + dtexBinName, dtexBuffer);
}
#endif
});
tasks.queue([&]{
// store material information
ext::json::reserve( serializer["materials"], graph.materials.size() );
for ( auto& name : graph.materials ) {
auto& material = /*graph.storage*/storage.materials.map.at(name);
auto json = encode(material, settings, graph);
json["name"] = name;
serializer["materials"].emplace_back(json);
}
});
tasks.queue([&]{
// store light information
ext::json::reserve( serializer["lights"], graph.lights.size() );
for ( auto pair : graph.lights ) {
auto& name = pair.first;
auto& light = pair.second;
auto json = encode(light, settings, graph);
json["name"] = name;
serializer["lights"].emplace_back(json);
}
});
tasks.queue([&]{
// store animation information
ext::json::reserve( serializer["animations"], graph.animations.size() );
if ( !settings.combined ) {
for ( auto i = 0; i < graph.animations.size(); ++i ) {
auto& name = graph.animations[i];
uf::stl::string f = ::fmt::format( "animation.{}.json", i );
auto& animation = /*graph.storage*/storage.animations.map.at(name);
encode(animation, settings, graph).writeToFile(directory+"/"+f);
serializer["animations"].emplace_back(f);
}
} else {
for ( auto& name : graph.animations ) {
auto& animation = /*graph.storage*/storage.animations.map.at(name);
serializer["animations"][name] = encode(animation, settings, graph);
}
}
});
tasks.queue([&]{
// store skin information
ext::json::reserve( serializer["skins"], graph.skins.size() );
for ( auto& name : graph.skins ) {
auto& skin = /*graph.storage*/storage.skins.map.at(name);
serializer["skins"].emplace_back( encode(skin, settings, graph) );
}
});
tasks.queue([&]{
// store node information
ext::json::reserve( serializer["nodes"], graph.nodes.size() );
for ( auto& node : graph.nodes ) serializer["nodes"].emplace_back( encode(node, settings, graph) );
serializer["root"] = encode(graph.root, settings, graph);

View File

@ -50,6 +50,14 @@ namespace {
}
return instanceID;
}
size_t allocateJointID( pod::Graph::Storage& storage, const uf::stl::string& name ) {
size_t jointID = 0;
for ( auto& key : storage.joints.keys ) {
if ( key == name ) break;
jointID += storage.joints.map[key].size();
}
return jointID;
}
// removes non-uniform aliased buffers
void resetBuffers( uf::renderer::Shader& shader ) {
@ -68,7 +76,7 @@ namespace {
}
}
void bindShaders( pod::Graph& graph, uf::Object& entity, uf::Mesh& mesh ) {
void bindShaders( pod::Graph& graph, uf::Object& entity, uf::Mesh& mesh, uf::stl::vector<pod::Primitive>& primitives ) {
auto& scene = uf::scene::getCurrentScene();
auto& sceneTextures = scene.getComponent<pod::SceneTextures>();
auto& sceneMetadataJson = scene.getComponent<uf::Serializer>();
@ -278,7 +286,7 @@ namespace {
uint32_t jointID;
};
uf::stl::string compShaderFilename = graphMetadataJson["shaders"]["skinning"]["compute"].as<uf::stl::string>("/graph/skinning/skinning.deinterleaved.comp.spv"); {
uf::stl::string compShaderFilename = graphMetadataJson["shaders"]["skinning"]["compute"].as<uf::stl::string>("/graph/skinning/skinning.comp.spv"); {
compShaderFilename = entity.resolveURI( compShaderFilename, root );
}
@ -328,13 +336,14 @@ namespace {
auto& shader = graphic.material.getShader("compute", "skinning");
struct {
struct SkinningPush {
uint32_t jointID;
} uniforms = {
.jointID = 0
};
shader.updateBuffer( (const void*) &uniforms, sizeof(uniforms), shader.getUniformBuffer("UBO") );
auto& pushConstant = shader.pushConstants.front().get<SkinningPush>();
pushConstant = {
.jointID = (uint32_t) primitives.front().instance.jointID
};
// bind buffers
::resetBuffers( shader );
@ -514,7 +523,7 @@ namespace {
size_t uf::graph::initialBufferElements = 1024;
uint32_t uf::graph::storageMode = pod::Graph::Storage::SCENE;
pod::Graph::Storage uf::graph::storage;
pod::Graph::Storage uf::graph::globalStorage;
UF_VERTEX_DESCRIPTOR(uf::graph::mesh::Base,
UF_VERTEX_DESCRIPTION(uf::graph::mesh::Base, R32G32B32_SFLOAT, position)
@ -641,7 +650,7 @@ pod::Graph::Storage& uf::graph::getStorage( pod::Graph& graph ) {
}
case pod::Graph::Storage::GLOBAL:
default: {
return uf::graph::storage;
return uf::graph::globalStorage;
}
}
}
@ -674,7 +683,7 @@ pod::Graph::Storage& uf::graph::getStorage( uf::Object& object ) {
}
case pod::Graph::Storage::GLOBAL:
default: {
return uf::graph::storage;
return uf::graph::globalStorage;
}
}
}
@ -751,7 +760,7 @@ void uf::graph::initializeGraphics( pod::Graph& graph, uf::Object& entity, uf::M
::bindTextures( graph, graphic );
::bindShaders( graph, entity, mesh );
::bindShaders( graph, entity, mesh, primitives );
::bindBuffers( graph, graphic, mesh );
::bindAddresses( graph, graphic, mesh, primitives );
@ -1166,17 +1175,6 @@ void uf::graph::process( pod::Graph& graph ) {
auto& needle = graph.textures[instance.cubemapID];
instance.cubemapID = indices[needle];
}
// remap a skinID as an actual jointID
if ( 0 <= instance.jointID && instance.jointID < graph.skins.size() ) {
auto& name = graph.skins[instance.jointID];
instance.jointID = 0;
for ( auto key : storage.joints.keys ) {
if ( key == name ) break;
auto& joints = storage.joints[key];
instance.jointID += joints.size();
}
}
}
for ( auto& instance : storage.instances.map[name] ) {
@ -1198,15 +1196,6 @@ void uf::graph::process( pod::Graph& graph ) {
auto& needle = graph.textures[instance.cubemapID];
instance.cubemapID = indices[needle];
}
if ( 0 <= instance.jointID && instance.jointID < graph.skins.size() ) {
auto& skinName = graph.skins[instance.jointID];
instance.jointID = 0;
for ( auto key : storage.joints.keys ) {
if ( key == skinName ) break;
instance.jointID += storage.joints[key].size();
}
}
}
}
/*
@ -1506,15 +1495,17 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
//
if ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) {
{
node.object = ::allocateObjectID( storage );
auto objectKeyName = ::keyedID( node.object );
node.object = ::allocateObjectID( storage );
auto objectKeyName = ::keyedID( node.object );
storage.entities[objectKeyName] = &entity;
storage.objects[objectKeyName] = pod::Instance::Object{
.model = model,
.previous = model,
};
storage.entities[objectKeyName] = &entity;
storage.objects[objectKeyName] = pod::Instance::Object{
.model = model,
.previous = model,
};
if ( node.skin >= 0 && node.skin < graph.skins.size() ) {
storage.joints[objectKeyName] = {};
}
auto& mesh = storage.meshes.map[graph.meshes[node.mesh]];
@ -1527,7 +1518,11 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
for ( auto drawID = 0; drawID < primitives.size(); ++drawID ) {
pod::Instance newInstance = primitives[drawID].instance;
newInstance.objectID = node.object;
newInstance.jointID = graphMetadataJson["renderer"]["skinned"].as<bool>() ? 0 : -1;
if ( node.skin >= 0 && node.skin < graph.skins.size() ) {
newInstance.jointID = ::allocateJointID( storage, ::keyedID(node.object) );
} else {
newInstance.jointID = -1;
}
bounds.min = uf::vector::min( bounds.min, newInstance.bounds.min );
bounds.max = uf::vector::max( bounds.max, newInstance.bounds.max );
@ -1535,9 +1530,12 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
grouped.emplace_back(newInstance);
}
bool isFirstInstance = ( grouped.size() == primitives.size() );
#if !UF_GRAPH_EXTENDED
if ( graphMetadataJson["renderer"]["render"].as<bool>() && isFirstInstance ) {
bool isFirstInstance = ( grouped.size() == primitives.size() );
bool isSkinned = graphMetadataJson["renderer"]["skinned"].as<bool>();
bool shouldInitializeRender = graphMetadataJson["renderer"]["render"].as<bool>();
if ( shouldInitializeRender && (isFirstInstance || isSkinned) ) {
uf::graph::initializeGraphics( graph, entity, mesh, primitives );
}
#endif
@ -1644,7 +1642,7 @@ void uf::graph::tick() {
// tick only one graph if scene/global
switch ( uf::graph::storageMode ) {
case pod::Graph::Storage::GLOBAL: {
auto& storage = uf::graph::storage;
auto& storage = uf::graph::globalStorage;
storage.shouldRebind = uf::graph::tick( storage );
return;
} break;
@ -1689,7 +1687,7 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
auto& entity = *storage.entities.map[key];
auto& object = storage.objects.map[key];
if ( entity.isValid() ) {
if ( entity.hasComponent<pod::Transform<>>() ) {
auto& metadata = entity.getComponent<uf::ObjectBehavior::Metadata>();
auto& transform = entity.getComponent<pod::Transform<>>();
@ -1746,14 +1744,21 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
#if UF_USE_VULKAN
if ( commands && !grouped.empty() ) {
auto objectKeyName = ::keyedID(grouped.front().objectID);
if ( storage.entities.map.count(objectKeyName) > 0 ) {
uf::stl::unordered_set<uf::Object*> updatedGraphics;
for ( auto& instance : grouped ) {
auto objectKeyName = ::keyedID(instance.objectID);
if ( storage.entities.map.count(objectKeyName) == 0 ) continue;
auto& entity = *storage.entities.map[objectKeyName];
if ( entity.hasComponent<uf::renderer::Graphic>() ) {
auto& graphic = entity.getComponent<uf::renderer::Graphic>();
auto& attr = mesh.indirect.attributes.front();
graphic.updateBuffer( (const void*) attr.pointer, attr.length, graphic.metadata.buffers["indirect["+attr.descriptor.name+"]"] );
}
if ( !entity.hasComponent<uf::renderer::Graphic>() ) continue;
if ( updatedGraphics.find(&entity) != updatedGraphics.end() ) continue;
auto& graphic = entity.getComponent<uf::renderer::Graphic>();
auto& attr = mesh.indirect.attributes.front();
graphic.updateBuffer( (const void*) attr.pointer, attr.length, graphic.metadata.buffers["indirect["+attr.descriptor.name+"]"] );
updatedGraphics.insert(&entity);
}
}
#endif
@ -1802,7 +1807,7 @@ bool uf::graph::tick( pod::Graph::Storage& storage ) {
}
void uf::graph::aggregate() {
return uf::graph::aggregate( uf::scene::getCurrentScene(), uf::graph::storage );
return uf::graph::aggregate( uf::scene::getCurrentScene(), uf::graph::globalStorage );
}
void uf::graph::aggregate( uf::Object& object, pod::Graph::Storage& storage ) {
STATIC_THREAD_LOCAL(uf::stl::vector<pod::Instance>, instances);
@ -1891,7 +1896,7 @@ void uf::graph::render() {
// render only one graph if scene/global
switch ( uf::graph::storageMode ) {
case pod::Graph::Storage::GLOBAL: {
auto& storage = uf::graph::storage;
auto& storage = uf::graph::globalStorage;
uf::graph::render( storage );
return;
} break;
@ -2037,7 +2042,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
bool meshUpdated = false;
auto model = uf::transform::model( transform );
auto& mesh = storage.meshes.map[graph.meshes[node.mesh]];
auto meshName = graph.meshes[node.mesh];
auto& mesh = storage.meshes.map[meshName];
auto& meshStream = graph.streams.meshes[meshName];
auto& primitives = storage.primitives.map[graph.primitives[node.mesh]];
float radius = graph.settings.stream.radius;
@ -2054,7 +2062,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
radius = 0;
}
if ( mesh.buffer_paths.empty() ) {
if ( meshStream.buffers.empty() ) {
radius = 0;
}
@ -2207,7 +2215,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
if ( ranges.count(attribute.buffer) <= 0 ) { \
mesh.buffers[attribute.buffer].clear();\
} else {\
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], mesh.buffer_paths[attribute.buffer], ranges[attribute.buffer] );\
auto& region = meshStream.buffers[attribute.buffer];\
auto adjustedRanges = ranges[attribute.buffer];\
for (auto& r : adjustedRanges) r.start += region.offset;\
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], region.filename, adjustedRanges );\
}\
}
@ -2249,8 +2260,9 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
#define STREAM_MESH_DATA( N ) \
for ( auto& attribute : mesh.N.attributes ) {\
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;\
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], mesh.buffer_paths[attribute.buffer] );\
if ( !mesh.buffers[attribute.buffer].empty() || meshStream.buffers.empty() ) continue;\
auto& region = meshStream.buffers[attribute.buffer];\
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], region.filename, region.offset, region.length );\
}
STREAM_MESH_DATA( index );
@ -2297,7 +2309,21 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
if ( visible && (!texture.generated() || texture.aliased) ) {
// load image
if ( image.getPixels().empty() ) image.open(image.getFilename(), false);
if ( image.getPixels().empty() ) {
auto& imgStream = graph.streams.images[key];
uf::stl::vector<uint8_t> buf;
if (imgStream.buffer.length > 0) {
uf::io::readAsBuffer(buf, imgStream.buffer.filename, imgStream.buffer.offset, imgStream.buffer.length);
} else {
uf::io::readAsBuffer(buf, imgStream.buffer.filename);
}
uf::stl::string formatHint = uf::io::extension(image.getFilename());
if (imgStream.buffer.filename.find(".dtex") != uf::stl::string::npos) formatHint = "dtex";
uf::image::open(image, buf, formatHint, false);
}
auto filter = uf::renderer::enums::Filter::LINEAR;
auto tag = ext::json::find( key, graphMetadataJson["tags"] );
@ -2345,9 +2371,10 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
// load mesh if not already loaded
#define LOAD_MESH_DATA( N ) \
for ( auto& attribute : mesh.N.attributes ) {\
if ( !mesh.buffers[attribute.buffer].empty() || mesh.buffer_paths.empty() ) continue;\
if ( !mesh.buffers[attribute.buffer].empty() || meshStream.buffers.empty() ) continue;\
meshUpdated = true;\
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], mesh.buffer_paths[attribute.buffer] );\
auto& region = meshStream.buffers[attribute.buffer];\
uf::io::readAsBuffer( mesh.buffers[attribute.buffer], region.filename, region.offset, region.length );\
}
LOAD_MESH_DATA( index );
@ -2385,14 +2412,19 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
uf::renderer::states::rebuild = true;
#endif
storage.stale = true; // force rebuffering the draw commands
storage.stale = true;
bool graphicOwner = graphMetadataJson["renderer"]["render"].as<bool>();
bool isSkinned = graphMetadataJson["renderer"]["skinned"].as<bool>();
if ( graphicOwner ) {
auto objectKeyName = ::keyedID(storage.instances.map[graph.primitives[node.mesh]].front().objectID);
graphicOwner = storage.entities[objectKeyName] == &entity;
if ( isSkinned ) {
graphicOwner = true;
} else {
auto objectKeyName = ::keyedID(storage.instances.map[graph.primitives[node.mesh]].front().objectID);
graphicOwner = storage.entities[objectKeyName] == &entity;
}
}
// update graphic
if ( graphicOwner ) {
bool exists = entity.hasComponent<uf::renderer::Graphic>();
if ( exists ) {
@ -2448,7 +2480,16 @@ void uf::graph::reload( pod::Graph& graph ) {
// ::combineMesh( graph );
}
void uf::graph::reload() {
storage.stale = true;
switch ( uf::graph::storageMode ) {
case pod::Graph::Storage::SCENE: {
auto& storage = uf::scene::getCurrentScene().getComponent<pod::Graph::Storage>();
storage.stale = true;
}
case pod::Graph::Storage::GLOBAL:
default: {
uf::graph::globalStorage.stale = true;
}
}
}
void uf::graph::update( pod::Graph& graph ) {
@ -2475,7 +2516,7 @@ void uf::graph::update( pod::Graph& graph, float delta ) {
::bindBuffers( graph, graphic, mesh );
::bindAddresses( graph, graphic, mesh, primitives );
}
storage.shouldRebind = false;
//storage.shouldRebind = false;
}
// get last update time
@ -2495,6 +2536,7 @@ uf::stl::string uf::graph::getMaterialName( pod::Graph& graph, size_t materialID
return storage.materials.keys[materialID];
}
pod::Material uf::graph::getMaterial( pod::Graph& graph, size_t materialID ) {
auto& storage = uf::graph::getStorage( graph );
auto key = uf::graph::getMaterialName( graph, materialID );
return storage.materials.map[key];
}

View File

@ -145,6 +145,20 @@ bool UF_API ext::texconv::save( const pod::Dtex& dtex, const uf::stl::string& fi
return true;
}
bool UF_API ext::texconv::save( const pod::Dtex& dtex, uf::stl::vector<uint8_t>& buffer ) {
size_t totalSize = dtex.imageData.size() + dtex.paletteData.size();
buffer.reserve(buffer.size() + totalSize);
if ( !dtex.imageData.empty() ) {
buffer.insert( buffer.end(), dtex.imageData.begin(), dtex.imageData.end() );
}
if ( !dtex.paletteData.empty() ) {
buffer.insert( buffer.end(), dtex.paletteData.begin(), dtex.paletteData.end() );
}
return true;
}
// maintains original main()
bool ext::texconv::convert( const pod::TextureOptions& opts ) {

View File

@ -102,32 +102,45 @@ bool uf::image::open( pod::Image& image, const uf::stl::string& filename, bool f
if ( !uf::io::readAsBuffer( buffer, filename ) ) {
return false;
}
auto extension = uf::io::extension( filename );
#if UF_USE_OPENGL_GLDC
auto extension = uf::io::extension( filename );
if ( extension != "dtex" ) UF_MSG_WARNING("non-dtex loading is highly discouraged on this platform: {}", filename);
if ( extension != "dtex" ) {
UF_MSG_WARNING("non-dtex loading is highly discouraged on this platform: {}", filename);
}
#endif
image.filename = filename;
return uf::image::open(image, buffer, extension, flip);
}
bool uf::image::open( pod::Image& image, const uf::stl::vector<uint8_t>& buffer, const uf::stl::string& formatHint, bool flip ) {
image.pixels.clear();
int width = 0, height = 0, channelsDud = 0, bpp = 8, channels = 4;
#if UF_USE_OPENGL_GLDC
if ( extension == "dtex" ) {
if ( formatHint == "dtex" ) {
struct {
char id[4]; // 'DTEX'
uint16_t width;
uint16_t height;
uint32_t type;
uint32_t size;
uint16_t width;
uint16_t height;
uint32_t type;
uint32_t size;
} header;
uf::stl::vector<uint8_t> buffer;
if ( !uf::io::readAsBuffer( buffer, filename ) ) UF_EXCEPTION("IO error: could not read DTEX: {}", filename);
if ( buffer.size() < sizeof(header) ) UF_EXCEPTION("IO error: DTEX file is too small to contain a header: {}", filename);
if ( buffer.size() < sizeof(header) ) {
UF_EXCEPTION("IO error: DTEX buffer is too small to contain a header");
return false;
}
memcpy(&header, buffer.data(), sizeof(header));
if ( buffer.size() < sizeof(header) + header.size ) UF_EXCEPTION("IO error: DTEX file is truncated or corrupted: {}", filename);
if ( buffer.size() < sizeof(header) + header.size ) {
UF_EXCEPTION("IO error: DTEX buffer is truncated or corrupted");
return false;
}
image.pixels.resize(header.size);
memcpy(image.pixels.data(), buffer.data() + sizeof(header), header.size);
memcpy( image.pixels.data(), buffer.data() + sizeof(header), header.size );
// palette data: buffer.data() + sizeof(header) + header.size
bool twiddled = (header.type & (1 << 26)) < 1;
bool compressed = (header.type & (1 << 30)) > 0;
@ -137,8 +150,6 @@ bool uf::image::open( pod::Image& image, const uf::stl::string& filename, bool f
width = header.width;
height = header.height;
uint32_t expected = 2 * header.width * header.height;
uint32_t ratio = (uint32_t) (((float) expected) / ((float) header.size));
bpp = 4;
image.format = format;
if ( compressed ) {
@ -147,25 +158,33 @@ bool uf::image::open( pod::Image& image, const uf::stl::string& filename, bool f
case 1: image.format = mipmapped ? GL_COMPRESSED_RGB_565_VQ_MIPMAP_TWID_KOS : GL_COMPRESSED_RGB_565_VQ_TWID_KOS; channels = 3; break;
case 0: image.format = mipmapped ? GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_TWID_KOS : GL_COMPRESSED_ARGB_1555_VQ_TWID_KOS; break;
case 2: image.format = mipmapped ? GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_TWID_KOS : GL_COMPRESSED_ARGB_4444_VQ_TWID_KOS; break;
default: UF_EXCEPTION("Image error: invalid texture format: {}", filename); return false;
default: UF_EXCEPTION("Image error: invalid texture format"); return false;
}
} else {
switch ( format ) {
case 1: image.format = mipmapped ? GL_COMPRESSED_RGB_565_VQ_MIPMAP_KOS : GL_COMPRESSED_RGB_565_VQ_KOS; channels = 3; break;
case 0: image.format = mipmapped ? GL_COMPRESSED_ARGB_1555_VQ_MIPMAP_KOS : GL_COMPRESSED_ARGB_1555_VQ_KOS; break;
case 2: image.format = mipmapped ? GL_COMPRESSED_ARGB_4444_VQ_MIPMAP_KOS : GL_COMPRESSED_ARGB_4444_VQ_KOS; break;
default: UF_EXCEPTION("Image error: invalid texture format: {}", filename); return false;
default: UF_EXCEPTION("Image error: invalid texture format"); return false;
}
}
} else { UF_EXCEPTION("Image error: not a compressed texture: {}", filename); return false; }
} else
} else { UF_EXCEPTION("Image error: not a compressed texture"); return false; }
} else
#endif
{
stbi_set_flip_vertically_on_load(flip);
stbi_set_flip_vertically_on_load( flip );
uint8_t* stbi_pixels = stbi_load_from_memory( buffer.data(), buffer.size(), &width, &height, &channelsDud, STBI_rgb_alpha );
if ( !stbi_pixels ) {
UF_EXCEPTION("Image error: stb_image failed to decode buffer");
return false;
}
size_t len = width * height * channels;
image.pixels.resize( len );
memcpy( &image.pixels[0], stbi_pixels, len );
memcpy( image.pixels.data(), stbi_pixels, len );
stbi_image_free(stbi_pixels);
}
image.size.x = width;
@ -205,27 +224,29 @@ void uf::image::load( pod::Image& image, const pod::Image::container_t& containe
if ( flip ) uf::image::flip( image );
}
bool uf::image::save( const pod::Image& image, const uf::stl::string& filename, bool flip ) {
if ( image.pixels.empty() ) return false;
uf::stl::vector<uint8_t> buffer;
uf::image::save( image, buffer, flip );
if ( buffer.empty() ) return false;
return uf::vfs::write( filename, buffer.data(), buffer.size() ) > 0;
}
void uf::image::save( const pod::Image& image, uf::stl::vector<uint8_t>& buffer, bool flip ) {
buffer.clear();
if ( image.pixels.empty() ) return;
uint w = image.size.x;
uint h = image.size.y;
auto* pixels = &image.pixels[0];
uf::stl::string extension = uf::io::extension(filename);
uf::stl::string extension = image.filename.empty() ? "png" : uf::io::extension( image.filename );
stbi_flip_vertically_on_write(flip);
uf::stl::vector<uint8_t> buffer;
if ( extension == "png" ) {
stbi_write_png_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, pixels, w * image.channels);
} else if ( extension == "jpg" || extension == "jpeg" ) {
stbi_write_jpg_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, pixels, 90); // 90 is quality
stbi_write_jpg_to_func(stbi_buffer_write_func, &buffer, w, h, image.channels, pixels, 90); // quality
} else {
UF_MSG_ERROR("Unsupported image save format: {}", extension);
return false;
}
if ( buffer.empty() ) return false;
return uf::vfs::write( filename, buffer.data(), buffer.size() ) > 0;
}
void uf::image::save( const pod::Image& image, std::ostream& stream ) {
@ -464,6 +485,9 @@ void uf::Image::setFilename( const uf::stl::string& filename ) {
bool uf::Image::open( const uf::stl::string& filename, bool flip ) {
return uf::image::open( *this, filename, flip );
}
bool uf::Image::open( const uf::stl::vector<uint8_t>& buffer, const uf::stl::string& formatHint, bool flip ) {
return uf::image::open( *this, buffer, formatHint, flip );
}
void uf::Image::loadFromBuffer( const pod::Image::pixel_t::type_t* pointer, const pod::Vector2ui& size, size_t bpp, size_t channels, bool flip ) {
return uf::image::load( *this, pointer, size, bpp, channels, flip );
}
@ -558,6 +582,11 @@ pod::Image::pixel_t uf::Image::at( const pod::Vector2ui& at ) {
bool uf::Image::save( const uf::stl::string& filename, bool flip ) const {
return uf::image::save( *this, filename, flip );
}
// to buffer
bool uf::Image::save( uf::stl::vector<uint8_t>& buffer, bool flip ) const {
uf::image::save( *this, buffer, flip );
return true;
}
// to stream
void uf::Image::save( std::ostream& stream ) const {
return uf::image::save( *this, stream );