diff --git a/bin/data/config.json b/bin/data/config.json index 957efb4e..aa46cdca 100644 --- a/bin/data/config.json +++ b/bin/data/config.json @@ -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 diff --git a/bin/data/entities/craeture.json b/bin/data/entities/craeture.json index 0831d4ec..1dbce95e 100644 --- a/bin/data/entities/craeture.json +++ b/bin/data/entities/craeture.json @@ -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 } } /* diff --git a/bin/data/entities/craetureModel.json b/bin/data/entities/craetureModel.json index 7886e477..91e0a169 100644 --- a/bin/data/entities/craetureModel.json +++ b/bin/data/entities/craetureModel.json @@ -55,7 +55,7 @@ "skinned": true }, "animations": { - // "animation": "idle" + "animation": "idle_wank" } } } diff --git a/bin/data/entities/model.json b/bin/data/entities/model.json index 5ca5fff2..3225add6 100644 --- a/bin/data/entities/model.json +++ b/bin/data/entities/model.json @@ -78,7 +78,7 @@ "stream": { "tag": "worldspawn", "player": "info_player_spawn", - "enabled": "auto", + "enabled": false, // "auto", "radius": 16, "every": 1 } diff --git a/bin/data/entities/playerModel.json b/bin/data/entities/playerModel.json index a29aa9be..5467bb32 100644 --- a/bin/data/entities/playerModel.json +++ b/bin/data/entities/playerModel.json @@ -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 } } } diff --git a/bin/data/scenes/sourceengine/cs_office.json b/bin/data/scenes/sourceengine/cs_office.json index 398df6e8..af8b3c8d 100644 --- a/bin/data/scenes/sourceengine/cs_office.json +++ b/bin/data/scenes/sourceengine/cs_office.json @@ -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" } ] } \ No newline at end of file diff --git a/bin/data/scenes/sourceengine/mds_mcdonalds.json b/bin/data/scenes/sourceengine/mds_mcdonalds.json index 39f9e552..45290690 100644 --- a/bin/data/scenes/sourceengine/mds_mcdonalds.json +++ b/bin/data/scenes/sourceengine/mds_mcdonalds.json @@ -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 } ] } \ No newline at end of file diff --git a/bin/data/shaders/graph/skinning/skinning.deinterleaved.comp.glsl b/bin/data/shaders/graph/skinning/skinning.comp.glsl similarity index 60% rename from bin/data/shaders/graph/skinning/skinning.deinterleaved.comp.glsl rename to bin/data/shaders/graph/skinning/skinning.comp.glsl index a8237fe0..b1eeb078 100644 --- a/bin/data/shaders/graph/skinning/skinning.deinterleaved.comp.glsl +++ b/bin/data/shaders/graph/skinning/skinning.comp.glsl @@ -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]; diff --git a/bin/data/shaders/graph/skinning/skinning.interleaved.comp.glsl b/bin/data/shaders/graph/skinning/skinning.interleaved.comp.glsl deleted file mode 100644 index 04692fbd..00000000 --- a/bin/data/shaders/graph/skinning/skinning.interleaved.comp.glsl +++ /dev/null @@ -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)); -} \ No newline at end of file diff --git a/engine/inc/uf/engine/asset/asset.h b/engine/inc/uf/engine/asset/asset.h index 3e404661..1c8ded77 100644 --- a/engine/inc/uf/engine/asset/asset.h +++ b/engine/inc/uf/engine/asset/asset.h @@ -33,6 +33,7 @@ namespace uf { extern UF_API uf::stl::unordered_map 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; diff --git a/engine/inc/uf/engine/graph/graph.h b/engine/inc/uf/engine/graph/graph.h index d4fa1865..8deef739 100644 --- a/engine/inc/uf/engine/graph/graph.h +++ b/engine/inc/uf/engine/graph/graph.h @@ -75,6 +75,13 @@ namespace pod { } stream; } settings; + struct StreamRegistry { + uf::stl::unordered_map animations; + uf::stl::unordered_map skins; + uf::stl::unordered_map meshes; + uf::stl::unordered_map 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; } } diff --git a/engine/inc/uf/engine/graph/pod.inl b/engine/inc/uf/engine/graph/pod.inl index e113d160..f5428c97 100644 --- a/engine/inc/uf/engine/graph/pod.inl +++ b/engine/inc/uf/engine/graph/pod.inl @@ -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 samplers; + }; + + struct MeshStream { + uf::stl::vector buffers; + }; + + struct SkinStream { + pod::StreamRegion inverseBindMatrices; + }; + + struct ImageStream { + StreamRegion buffer; + }; } \ No newline at end of file diff --git a/engine/inc/uf/ext/json/nlohmann.h b/engine/inc/uf/ext/json/nlohmann.h index 0de60c1b..8468f09b 100644 --- a/engine/inc/uf/ext/json/nlohmann.h +++ b/engine/inc/uf/ext/json/nlohmann.h @@ -58,6 +58,15 @@ namespace ext { template 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" \ No newline at end of file diff --git a/engine/inc/uf/ext/json/nlohmann.inl b/engine/inc/uf/ext/json/nlohmann.inl index 3a3836eb..8a278afb 100644 --- a/engine/inc/uf/ext/json/nlohmann.inl +++ b/engine/inc/uf/ext/json/nlohmann.inl @@ -51,6 +51,7 @@ template<> inline bool ext::json::Value::is(bool strict) const { retur template<> inline bool ext::json::Value::is(bool strict) const { return strict ? is_number_float() : is_number(); } template<> inline bool ext::json::Value::is(bool strict) const { return strict ? is_number_float() : is_number(); } template<> inline bool ext::json::Value::is(bool strict) const { return is_string(); } +template<> inline bool ext::json::Value::is>(bool strict) const { return is_array(); } // template<> template inline bool ext::json::Value::is>(bool strict) const { return is_array() && size() == N; } // template<> inline bool ext::json::Value::is(bool strict) const { return is_string(); } diff --git a/engine/inc/uf/ext/texconv/texconv.h b/engine/inc/uf/ext/texconv/texconv.h index a0e66054..2bbeb732 100644 --- a/engine/inc/uf/ext/texconv/texconv.h +++ b/engine/inc/uf/ext/texconv/texconv.h @@ -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& ); } } \ No newline at end of file diff --git a/engine/inc/uf/utils/image/image.h b/engine/inc/uf/utils/image/image.h index f9990f84..9935a968 100644 --- a/engine/inc/uf/utils/image/image.h +++ b/engine/inc/uf/utils/image/image.h @@ -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&, 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&, 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& 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&, 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 diff --git a/engine/inc/uf/utils/memory/key_map.h b/engine/inc/uf/utils/memory/key_map.h index d52137f1..53c0bb1a 100644 --- a/engine/inc/uf/utils/memory/key_map.h +++ b/engine/inc/uf/utils/memory/key_map.h @@ -98,13 +98,19 @@ uf::stl::vector uf::stl::KeyMap::flattenByIndex() const { template void uf::stl::KeyMap::merge( KeyMap&& 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 diff --git a/engine/inc/uf/utils/mesh/mesh.h b/engine/inc/uf/utils/mesh/mesh.h index dbaf2270..47b676af 100644 --- a/engine/inc/uf/utils/mesh/mesh.h +++ b/engine/inc/uf/utils/mesh/mesh.h @@ -209,8 +209,6 @@ namespace uf { uf::stl::vector buffers; - // crunge, but it's better this way for streaming in mesh data - uf::stl::vector buffer_paths; // mega cringe, but i'd like to have a way to cache it uf::stl::vector buffer_views; protected: diff --git a/engine/src/engine/asset/asset.cpp b/engine/src/engine/asset/asset.cpp index a3a75daa..8ca3c900 100644 --- a/engine/src/engine/asset/asset.cpp +++ b/engine/src/engine/asset/asset.cpp @@ -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::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(); } }); diff --git a/engine/src/engine/graph/animation.cpp b/engine/src/engine/graph/animation.cpp index a8ed441d..7ff76e96 100644 --- a/engine/src/engine/graph/animation.cpp +++ b/engine/src/engine/graph/animation.cpp @@ -16,58 +16,58 @@ #include 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 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() ); - }); - - 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()) ) { @@ -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]; diff --git a/engine/src/engine/graph/decode.cpp b/engine/src/engine/graph/decode.cpp index a577f4a1..88773181 100644 --- a/engine/src/engine/graph/decode.cpp +++ b/engine/src/engine/graph/decode.cpp @@ -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(); + offset = json["dtex"]["offset"].as(); + length = json["dtex"]["length"].as(); + formatHint = "dtex"; + } else + #endif if ( json["filename"].is() ) { - const uf::stl::string directory = uf::io::directory( graph.name ); - const uf::stl::string filename = uf::io::filename( json["filename"].as() ); - const uf::stl::string name = directory + "/" + filename; - if ( graph.settings.stream.textures ) { - image.setFilename(name); - } else { - image.open(name, false); - } + filename = json["filename"].as(); + offset = json["offset"].as(0); + length = json["length"].as(0); + formatHint = uf::io::extension(filename); } else { auto size = uf::vector::decode( json["size"], pod::Vector2ui{} ); size_t bpp = json["bpp"].as(); size_t channels = json["channels"].as(); auto pixels = uf::base64::decode( json["data"].as() ); 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 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& megaBuffer ) { + pod::Animation animation = {}; + animation.name = json["name"].as(animation.name); + animation.start = json["start"].as(0.0f); + animation.end = json["end"].as(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()) { + binPath = uf::io::directory(graph.name) + "/" + json["buffer"].as(); + } - 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(); - sampler.descriptor.filter.mag = (uf::renderer::enums::Filter::type_t) json["mag"].as(); - sampler.descriptor.addressMode.u = (uf::renderer::enums::AddressMode::type_t) json["u"].as(); - sampler.descriptor.addressMode.v = (uf::renderer::enums::AddressMode::type_t) json["v"].as(); - 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() ); - }); - - 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 inputsOffset = value["inputs"]["offset"].as(); + size_t inputsLen = value["inputs"]["length"].as(); + + size_t outputsCount = value["outputs"]["count"].as(); + size_t outputsOffset = value["outputs"]["offset"].as(); + size_t outputsLen = value["outputs"]["length"].as(); + + 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& 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() ); }); - - 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 offset = invJson["offset"].as(); + size_t length = invJson["length"].as(); + uf::stl::string binPath = uf::io::directory(graph.name) + "/" + invJson["buffer"].as(); + + 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 decodePrimitives( ext::json::Value& json, pod::Graph& graph ) { - uf::stl::vector primitives; - auto name = json["name"].as(); - ext::json::forEach( json["primitives"], [&]( ext::json::Value& value ){ - primitives.emplace_back( decodePrimitive( value, graph ) ); - }); - return primitives; - } - uf::stl::vector decodeDrawCommands( ext::json::Value& json, pod::Graph& graph ) { - uf::stl::vector drawCommands; - auto name = json["name"].as(); - 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& 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 localRegions; + localRegions.reserve( json["buffers"].size() ); + ext::json::forEach( json["buffers"], [&]( ext::json::Value& value ){ - const uf::stl::string filename = value.as(); - 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(); + offset = value["offset"].as(); + length = value["length"].as(); + } else { + filename = value.as(); + } + + 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 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() ) { @@ -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(); -// 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 binName = node["buffer"].as(); + uf::stl::vector ioBuf; + + if (uf::io::readAsBuffer(ioBuf, directory + binName)) { + pod::Material* rawMaterials = reinterpret_cast(ioBuf.data()); + auto names = node["names"].as>(); + + 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 binName = node["buffer"].as(); + uf::stl::vector ioBuf; + + if (uf::io::readAsBuffer(ioBuf, directory + binName)) { + pod::Texture* rawTextures = reinterpret_cast(ioBuf.data()); + auto names = node["names"].as>(); + + 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 binName = node["buffer"].as(); + uf::stl::vector ioBuf; + + if (uf::io::readAsBuffer(ioBuf, directory + binName)) { + uf::renderer::Sampler* rawSamplers = reinterpret_cast(ioBuf.data()); + auto names = node["names"].as>(); + + 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 binName = node["buffer"].as(); + uf::stl::vector ioBuf; + + if (uf::io::readAsBuffer(ioBuf, directory + binName)) { + pod::Light* rawLights = reinterpret_cast(ioBuf.data()); + auto names = node["names"].as>(); + + 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 ioBuf; + pod::Primitive* allPrimitives = nullptr; + + if (uf::io::readAsBuffer(ioBuf, directory + binName)) { + allPrimitives = reinterpret_cast(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_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 offsetBytes = value["offset"].as(); + 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_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_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 filename = directory + "/" + value.as(); - 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 megaBuffer; + bool bufferAttempted = false; + + ext::json::forEach( serializer["meshes"], [&]( ext::json::Value& value ){ + auto name = key + value["name"].as(); + + if (!bufferAttempted && value["buffers"].size() > 0 && value["buffers"][0].isObject()) { + uf::stl::string binName = value["buffers"][0]["filename"].as(); + 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_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_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_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_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() ) { - auto path = directory + "/" + value.as(); + auto& animNode = serializer["animations"]; + + uf::stl::vector 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 binName = value["buffer"].as(); + 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::Serializer json; json.readFromFile( path ); auto name = key + json["name"].as(); - 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() ) { - auto path = directory + "/" + value.as(); - uf::Serializer json; - json.readFromFile( path ); - auto name = key + json["name"].as(); - 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(); - 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 megaBuffer; + bool bufferAttempted = false; + ext::json::forEach( serializer["skins"], [&]( ext::json::Value& value ){ auto name = key + value["name"].as(); - // UF_MSG_DEBUG("{}", name); - storage.skins[name] = decodeSkin( value, graph ); + + if (!bufferAttempted && value["inverseBindMatrices"].isObject()) { + uf::stl::string binName = value["inverseBindMatrices"]["buffer"].as(); + 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 ){ diff --git a/engine/src/engine/graph/encode.cpp b/engine/src/engine/graph/encode.cpp index 92359759..a3d8f329 100644 --- a/engine/src/engine/graph/encode.cpp +++ b/engine/src/engine/graph/encode.cpp @@ -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& 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(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& 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(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& 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 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 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 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 flatLights; + uf::stl::vector 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 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 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 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 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 imagesBuffer; + uf::stl::vector 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 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 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); diff --git a/engine/src/engine/graph/graph.cpp b/engine/src/engine/graph/graph.cpp index a663ac7c..fa855c7c 100644 --- a/engine/src/engine/graph/graph.cpp +++ b/engine/src/engine/graph/graph.cpp @@ -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& primitives ) { auto& scene = uf::scene::getCurrentScene(); auto& sceneTextures = scene.getComponent(); auto& sceneMetadataJson = scene.getComponent(); @@ -278,7 +286,7 @@ namespace { uint32_t jointID; }; - uf::stl::string compShaderFilename = graphMetadataJson["shaders"]["skinning"]["compute"].as("/graph/skinning/skinning.deinterleaved.comp.spv"); { + uf::stl::string compShaderFilename = graphMetadataJson["shaders"]["skinning"]["compute"].as("/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(); + 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() ? 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() && isFirstInstance ) { + bool isFirstInstance = ( grouped.size() == primitives.size() ); + bool isSkinned = graphMetadataJson["renderer"]["skinned"].as(); + bool shouldInitializeRender = graphMetadataJson["renderer"]["render"].as(); + + 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>() ) { auto& metadata = entity.getComponent(); auto& transform = entity.getComponent>(); @@ -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 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() ) { - auto& graphic = entity.getComponent(); - auto& attr = mesh.indirect.attributes.front(); - graphic.updateBuffer( (const void*) attr.pointer, attr.length, graphic.metadata.buffers["indirect["+attr.descriptor.name+"]"] ); - } + if ( !entity.hasComponent() ) continue; + if ( updatedGraphics.find(&entity) != updatedGraphics.end() ) continue; + + auto& graphic = entity.getComponent(); + 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, 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 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 isSkinned = graphMetadataJson["renderer"]["skinned"].as(); 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(); 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(); + 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]; } diff --git a/engine/src/ext/texconv/texconv.cpp b/engine/src/ext/texconv/texconv.cpp index bbac6496..ae87c8a3 100644 --- a/engine/src/ext/texconv/texconv.cpp +++ b/engine/src/ext/texconv/texconv.cpp @@ -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& 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 ) { diff --git a/engine/src/utils/image/image.cpp b/engine/src/utils/image/image.cpp index d5a30803..dc3bd554 100644 --- a/engine/src/utils/image/image.cpp +++ b/engine/src/utils/image/image.cpp @@ -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& 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 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 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& 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 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& 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& 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 );