From 2da07ef96119a1dbcfce273553007a769de423fc Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Wed, 13 Apr 2022 15:47:43 +0200 Subject: [PATCH] Working on graph evaluations. WIP. --- src/AnimGraph/AnimGraph.cc | 44 ++++++++++------ src/AnimGraph/AnimGraph.h | 71 +++++++++++++++++++------ src/AnimGraph/AnimGraphData.h | 16 +++++- src/AnimGraph/AnimGraphNodes.cc | 40 +++++++------- src/AnimGraph/AnimGraphResource.cc | 5 +- tests/AnimGraphEvalTests.cc | 84 +++++++++++++++++++++++++++++- tests/AnimGraphResourceTests.cc | 41 ++++++++++----- 7 files changed, 233 insertions(+), 68 deletions(-) diff --git a/src/AnimGraph/AnimGraph.cc b/src/AnimGraph/AnimGraph.cc index 70328ba..22060e4 100644 --- a/src/AnimGraph/AnimGraph.cc +++ b/src/AnimGraph/AnimGraph.cc @@ -13,6 +13,18 @@ bool AnimGraph::init(AnimGraphContext& context) { } } + std::vector& graph_outputs = m_node_input_connections[0]; + for (size_t i = 0, n = graph_outputs.size(); i < n; i++) { + AnimGraphConnection& connection = graph_outputs[i]; + if (connection.m_target_socket.m_type == SocketType::SocketTypeAnimation) { + AnimData* graph_anim_output = + static_cast(connection.m_target_socket.m_reference.ptr); + assert(graph_anim_output != nullptr); + graph_anim_output->m_local_matrices.resize( + context.m_skeleton->num_soa_joints()); + } + } + return true; } @@ -56,7 +68,7 @@ void AnimGraph::markActiveNodes() { } } - for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { + for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; i--) { AnimNode* node = m_eval_ordered_nodes[i]; if (checkIsNodeActive(node)) { int node_index = node->m_index; @@ -78,7 +90,9 @@ void AnimGraph::markActiveNodes() { } } -void AnimGraph::prepareNodeEval(size_t node_index) { +void AnimGraph::prepareNodeEval( + AnimGraphContext& graph_context, + size_t node_index) { for (size_t i = 0, n = m_node_output_connections[node_index].size(); i < n; i++) { AnimGraphConnection& output_connection = @@ -88,9 +102,10 @@ void AnimGraph::prepareNodeEval(size_t node_index) { continue; } + assert (*output_connection.m_source_socket.m_reference.ptr_ptr == nullptr); + (*output_connection.m_source_socket.m_reference.ptr_ptr) = - m_anim_data_work_buffer.peek(); - m_anim_data_work_buffer.pop(); + m_anim_data_work_buffer.allocate(graph_context); } for (size_t i = 0, n = m_node_input_connections[node_index].size(); i < n; @@ -117,13 +132,12 @@ void AnimGraph::finishNodeEval(size_t node_index) { continue; } - m_anim_data_work_buffer.push( - static_cast(input_connection.m_source_socket.m_reference.ptr)); + m_anim_data_work_buffer.free(static_cast( + input_connection.m_source_socket.m_reference.ptr)); (*input_connection.m_source_socket.m_reference.ptr_ptr) = nullptr; } } - void AnimGraph::evalInputNode() { for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) { AnimGraphConnection& graph_input_connection = @@ -154,7 +168,11 @@ void AnimGraph::evalOutputNode() { graph_output_connection.m_source_socket.m_reference.ptr, graph_output_connection.m_target_socket.m_type_size); } else { - // TODO: how to deal with anim data outputs? + AnimData* source_data = static_cast( + *graph_output_connection.m_source_socket.m_reference.ptr_ptr); + AnimData* target_data = static_cast( + graph_output_connection.m_target_socket.m_reference.ptr); + target_data->m_local_matrices = source_data->m_local_matrices; } } } @@ -208,14 +226,6 @@ void AnimGraph::updateTime(float dt) { } void AnimGraph::evaluate(AnimGraphContext& context) { - constexpr int eval_stack_size = 5; - int eval_stack_index = eval_stack_size; - AnimData eval_buffers[eval_stack_size]; - AnimData* eval_stack[eval_stack_size]; - for (size_t i = 0; i < eval_stack_size; i++) { - eval_stack[i] = &eval_buffers[i]; - } - for (int i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { AnimNode* node = m_eval_ordered_nodes[i]; @@ -223,7 +233,7 @@ void AnimGraph::evaluate(AnimGraphContext& context) { continue; } - prepareNodeEval(node->m_index); + prepareNodeEval(context, node->m_index); node->Evaluate(context); diff --git a/src/AnimGraph/AnimGraph.h b/src/AnimGraph/AnimGraph.h index 7e23260..3379899 100644 --- a/src/AnimGraph/AnimGraph.h +++ b/src/AnimGraph/AnimGraph.h @@ -9,30 +9,57 @@ #include "AnimGraphNodes.h" struct AnimDataWorkBuffer { - std::vector m_eval_anim_data; - std::vector m_available_data; + struct AnimDataList { + AnimData* m_anim_data = nullptr; + AnimDataList* next = nullptr; + }; - AnimDataWorkBuffer(size_t stack_size) { - m_eval_anim_data.resize(stack_size); - m_available_data.resize(stack_size); + AnimDataList* m_anim_data_list = nullptr; + size_t m_num_allocations = 0; - for (size_t i = 0; i < stack_size; i++) { - m_available_data[i] = &m_eval_anim_data[i]; + ~AnimDataWorkBuffer() { + AnimDataList* list_node = m_anim_data_list; + while (list_node != nullptr) { + AnimDataList* current_node = list_node; + list_node = list_node->next; + //delete current_node->m_anim_data; + current_node->m_anim_data = nullptr; + delete current_node; } } - void push(AnimData* anim_data) { - assert (m_available_data.size() < m_eval_anim_data.size()); - m_available_data.push_back(anim_data); + AnimData* allocate(AnimGraphContext& graph_context) { + if (m_anim_data_list == nullptr) { + AnimData* result = new AnimData(); + result->m_local_matrices.resize(graph_context.m_skeleton->num_soa_joints()); + m_num_allocations++; + return result; + } + + AnimData* result = m_anim_data_list->m_anim_data; + m_anim_data_list = m_anim_data_list->next; + delete m_anim_data_list; + + return result; } - void pop() { - assert (m_available_data.size() > 0); - m_available_data.pop_back(); + void free(AnimData* anim_data) { + AnimDataList* list_node = new AnimDataList; + list_node->next = m_anim_data_list; + list_node->m_anim_data = anim_data; + m_anim_data_list = list_node; } - AnimData* peek() { - return m_available_data.back(); + size_t size() { + size_t result = 0; + + AnimDataList* node = m_anim_data_list; + while (node != nullptr) { + result ++; + node = node->next; + } + + return result; } }; @@ -53,7 +80,7 @@ struct AnimGraph { std::vector& getGraphOutputs() { return m_socket_accessor->m_inputs; } std::vector& getGraphInputs() { return m_socket_accessor->m_outputs; } - AnimDataWorkBuffer m_anim_data_work_buffer = AnimDataWorkBuffer(5); + AnimDataWorkBuffer m_anim_data_work_buffer; ~AnimGraph() { delete[] m_input_buffer; @@ -76,7 +103,7 @@ struct AnimGraph { } void evalInputNode(); - void prepareNodeEval(size_t node_index); + void prepareNodeEval(AnimGraphContext& graph_context, size_t node_index); void finishNodeEval(size_t node_index); void evalOutputNode(); @@ -106,6 +133,16 @@ struct AnimGraph { return nullptr; } + void* getOutputPtr(const std::string& name) const { + const Socket* input_socket = getOutputSocket(name); + if (input_socket != nullptr) { + return input_socket->m_reference.ptr; + } + + return nullptr; + } + + int getNodeEvalOrderIndex(const AnimNode* node) { for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { if (m_eval_ordered_nodes[i] == node) { diff --git a/src/AnimGraph/AnimGraphData.h b/src/AnimGraph/AnimGraphData.h index 7fc29ff..5b1b060 100644 --- a/src/AnimGraph/AnimGraphData.h +++ b/src/AnimGraph/AnimGraphData.h @@ -9,11 +9,13 @@ #include #include #include +#include #include "SyncTrack.h" #include "ozz/base/containers/vector.h" #include #include "ozz/animation/runtime/skeleton.h" +#include "ozz/animation/runtime/animation.h" // // Data types @@ -24,6 +26,18 @@ struct AnimGraph; struct AnimGraphContext { AnimGraph* m_graph = nullptr; ozz::animation::Skeleton* m_skeleton = nullptr; + + typedef std::map AnimationFileMap; + AnimationFileMap m_animation_map; + + void freeAnimations() { + AnimationFileMap::iterator animation_map_iter = m_animation_map.begin(); + + while (animation_map_iter != m_animation_map.end()) { + delete animation_map_iter->second; + animation_map_iter++; + } + } }; struct AnimData { @@ -66,7 +80,7 @@ struct Socket { void* ptr; void** ptr_ptr; }; - SocketReference m_reference; + SocketReference m_reference = { 0 }; int m_flags = 0; size_t m_type_size = 0; }; diff --git a/src/AnimGraph/AnimGraphNodes.cc b/src/AnimGraph/AnimGraphNodes.cc index 8d9dd7d..bc0be18 100644 --- a/src/AnimGraph/AnimGraphNodes.cc +++ b/src/AnimGraph/AnimGraphNodes.cc @@ -33,39 +33,43 @@ void Blend2Node::Evaluate(AnimGraphContext& context) { if (!blend_job.Run()) { ozz::log::Err() << "Error blending animations." << std::endl; } - bool m_sync_blend = false; } // // AnimSamplerNode // AnimSamplerNode::~AnimSamplerNode() noexcept { - delete m_animation; m_animation = nullptr; } bool AnimSamplerNode::Init(AnimGraphContext& context) { assert (m_animation == nullptr); - m_animation = new ozz::animation::Animation(); - assert(m_filename.size() != 0); - ozz::io::File file(m_filename.c_str(), "rb"); - if (!file.opened()) { - ozz::log::Err() << "Failed to open animation file " << m_filename << "." - << std::endl; - return false; - } - ozz::io::IArchive archive(&file); - if (!archive.TestTag()) { - ozz::log::Err() << "Failed to load animation instance from file " - << m_filename << "." << std::endl; - return false; + + AnimGraphContext::AnimationFileMap::const_iterator animation_map_iter; + animation_map_iter = context.m_animation_map.find(m_filename); + if (animation_map_iter != context.m_animation_map.end()) { + m_animation = animation_map_iter->second; + } else { + m_animation = new ozz::animation::Animation(); + ozz::io::File file(m_filename.c_str(), "rb"); + if (!file.opened()) { + ozz::log::Err() << "Failed to open animation file " << m_filename << "." + << std::endl; + return false; + } + ozz::io::IArchive archive(&file); + if (!archive.TestTag()) { + ozz::log::Err() << "Failed to load animation instance from file " + << m_filename << "." << std::endl; + return false; + } + + context.m_animation_map[m_filename] = m_animation; } assert (context.m_skeleton != nullptr); - const int num_soa_joints = context.m_skeleton->num_soa_joints(); - const int num_joints = context.m_skeleton->num_joints(); - m_sampling_cache.Resize(num_joints); + m_sampling_cache.Resize(context.m_skeleton->num_joints()); return true; } diff --git a/src/AnimGraph/AnimGraphResource.cc b/src/AnimGraph/AnimGraphResource.cc index efe9288..25a737f 100644 --- a/src/AnimGraph/AnimGraphResource.cc +++ b/src/AnimGraph/AnimGraphResource.cc @@ -486,8 +486,9 @@ void AnimGraphResource::connectRuntimeNodes(AnimGraph& instance) const { // // Wire up outputs to inputs. // - (*target_socket->m_reference.ptr_ptr) = source_socket->m_reference.ptr; - // (*source_socket->m_reference.ptr_ptr) = target_socket->m_reference.ptr; + if (target_socket->m_type != SocketType::SocketTypeAnimation) { + (*target_socket->m_reference.ptr_ptr) = source_socket->m_reference.ptr; + } size_t target_node_index = target_node->m_index; diff --git a/tests/AnimGraphEvalTests.cc b/tests/AnimGraphEvalTests.cc index e9b47d8..c1ddd16 100644 --- a/tests/AnimGraphEvalTests.cc +++ b/tests/AnimGraphEvalTests.cc @@ -63,7 +63,7 @@ struct SimpleAnimFixture { bone0_translations.push_back(translation_key); translation_key.time = 1.f; - translation_key.value = ozz::math::Float3(1.f, 6.f, 9.f); + translation_key.value = ozz::math::Float3(1.f, 0.f, 9.f); bone0_translations.push_back(translation_key); bone0_track.translations = bone0_translations; @@ -119,3 +119,85 @@ TEST_CASE_METHOD( CHECK( sampled_translation.z[0] == Approx(translation_key.value.z).margin(0.01)); } + +TEST_CASE_METHOD( + SimpleAnimFixture, + "AnimGraphSimpleEval", + "[AnimGraphEvalTests]") { + AnimGraphResource graph_resource; + + // Add nodes + size_t trans_x_node_index = + graph_resource.addNode(AnimNodeResourceFactory("AnimSampler")); + size_t trans_y_node_index = + graph_resource.addNode(AnimNodeResourceFactory("AnimSampler")); + size_t blend_node_index = + graph_resource.addNode(AnimNodeResourceFactory("Blend2")); + + // Setup nodes + AnimNodeResource& trans_x_node = graph_resource.m_nodes[trans_x_node_index]; + trans_x_node.m_socket_accessor->SetPropertyValue("Filename", "trans_x"); + trans_x_node.m_name = "trans_x"; + + AnimNodeResource& trans_y_node = graph_resource.m_nodes[trans_y_node_index]; + trans_y_node.m_socket_accessor->SetPropertyValue("Filename", "trans_y"); + trans_y_node.m_name = "trans_y"; + + AnimNodeResource& blend_node = graph_resource.m_nodes[blend_node_index]; + blend_node.m_name = "BlendWalkRun"; + + // Setup graph outputs and inputs + AnimNodeResource& graph_output_node = graph_resource.getGraphOutputNode(); + graph_output_node.m_socket_accessor->RegisterInput("GraphOutput", nullptr); + + AnimNodeResource& graph_input_node = + graph_resource.getGraphInputNode(); + graph_input_node.m_socket_accessor->RegisterOutput( + "GraphFloatInput", + nullptr); + + // Wire up nodes + graph_resource.connectSockets(trans_x_node, "Output", blend_node, "Input0"); + graph_resource.connectSockets(trans_y_node, "Output", blend_node, "Input1"); + graph_resource.connectSockets( + blend_node, + "Output", + graph_resource.getGraphOutputNode(), + "GraphOutput"); + REQUIRE(graph_resource.connectSockets(graph_input_node, "GraphFloatInput", blend_node, "Weight")); + + // Prepare animation maps + AnimGraphContext graph_context; + graph_context.m_skeleton = skeleton.get(); + graph_context.m_animation_map["trans_x"] = animation_translate_x.get(); + graph_context.m_animation_map["trans_y"] = animation_translate_y.get(); + + // Instantiate graph + AnimGraph graph = graph_resource.createInstance(); + graph_context.m_graph = &graph; + graph.init(graph_context); + + // Get runtime graph inputs and outputs + float* graph_float_input = nullptr; + graph_float_input = + static_cast(graph.getInputPtr("GraphFloatInput")); + + Socket* anim_output_socket = + graph.getOutputSocket("GraphOutput"); + + AnimData* graph_anim_output = static_cast(graph.getOutputPtr("GraphOutput")); + + // Evaluate graph + *graph_float_input = 0.1f; + + graph.markActiveNodes(); + CHECK(graph.m_nodes[trans_x_node_index]->m_state == AnimNodeEvalState::Activated); + CHECK(graph.m_nodes[trans_y_node_index]->m_state == AnimNodeEvalState::Activated); + CHECK(graph.m_nodes[blend_node_index]->m_state == AnimNodeEvalState::Activated); + + graph.updateTime(0.5f); + graph.evaluate(graph_context); + + CHECK(graph_anim_output->m_local_matrices[0].translation.x[0] == Approx(0.5).margin(0.1)); + CHECK(graph_anim_output->m_local_matrices[0].translation.y[0] == Approx(0.05).margin(0.01)); +} \ No newline at end of file diff --git a/tests/AnimGraphResourceTests.cc b/tests/AnimGraphResourceTests.cc index 563dfc6..da86937 100644 --- a/tests/AnimGraphResourceTests.cc +++ b/tests/AnimGraphResourceTests.cc @@ -129,23 +129,31 @@ TEST_CASE("BasicGraph", "[AnimGraphResource]") { REQUIRE(anim_sampler_run->m_animation != nullptr); WHEN("Emulating Graph Evaluation") { - CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 5); - graph.prepareNodeEval(walk_node_index); + CHECK(graph.m_anim_data_work_buffer.size() == 0); + graph.prepareNodeEval(graph_context, walk_node_index); graph.finishNodeEval(walk_node_index); - CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 4); - graph.prepareNodeEval(run_node_index); + CHECK(graph.m_anim_data_work_buffer.m_num_allocations == 1); + CHECK(graph.m_anim_data_work_buffer.size() == 0); + + graph.prepareNodeEval(graph_context, run_node_index); graph.finishNodeEval(run_node_index); - CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 3); - graph.prepareNodeEval(blend_node_index); + CHECK(graph.m_anim_data_work_buffer.m_num_allocations == 2); + CHECK(graph.m_anim_data_work_buffer.size() == 0); + + graph.prepareNodeEval(graph_context, blend_node_index); CHECK(blend2_instance->i_input0 == anim_sampler_walk->o_output); CHECK(blend2_instance->i_input1 == anim_sampler_run->o_output); - CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 2); + CHECK(graph.m_anim_data_work_buffer.m_num_allocations == 3); + CHECK(graph.m_anim_data_work_buffer.size() == 0); + graph.finishNodeEval(blend_node_index); CHECK(anim_sampler_walk->o_output == nullptr); CHECK(anim_sampler_run->o_output == nullptr); - CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 4); + CHECK(graph.m_anim_data_work_buffer.m_num_allocations == 3); + CHECK(graph.m_anim_data_work_buffer.size() == 2); - graph.prepareNodeEval(0); + // TODO: rethink output node evaluation + graph.prepareNodeEval(graph_context, 0); const Socket* graph_output_socket = graph.getOutputSocket("GraphOutput"); CHECK(blend2_instance->o_output == (*graph_output_socket->m_reference.ptr_ptr)); AnimData* graph_output = @@ -153,8 +161,11 @@ TEST_CASE("BasicGraph", "[AnimGraphResource]") { graph.finishNodeEval(0); CHECK(blend2_instance->o_output == nullptr); CHECK(graph_output == (*graph_output_socket->m_reference.ptr_ptr)); - CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 5); + CHECK(graph.m_anim_data_work_buffer.m_num_allocations == 3); + CHECK(graph.m_anim_data_work_buffer.size() == 3); } + + graph_context.freeAnimations(); } TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") { @@ -282,7 +293,8 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { *graph_float_input = 123.456f; AND_WHEN("Evaluating Graph") { - AnimGraphContext context = {&anim_graph, nullptr}; + AnimGraphContext context; + context.m_graph = &anim_graph; anim_graph.updateTime(0.f); anim_graph.evaluate(context); @@ -299,6 +311,8 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { CHECK(vec3_output[1] == *graph_float_input); CHECK(vec3_output[2] == *graph_float_input); } + + context.freeAnimations(); } } } @@ -414,7 +428,8 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { *graph_float_input = 123.456f; AND_WHEN("Evaluating Graph") { - AnimGraphContext context = {&anim_graph, nullptr}; + AnimGraphContext context; + context.m_graph = &anim_graph; anim_graph.updateTime(0.f); anim_graph.evaluate(context); @@ -442,6 +457,8 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { CHECK(float1_output == Approx(*graph_float_input * 2.)); CHECK(float2_output == Approx(*graph_float_input * 3.)); } + + context.freeAnimations(); } } }