Working on graph evaluations. WIP.

AnimGraphEditor
Martin Felis 2022-04-13 15:47:43 +02:00
parent b518220576
commit 2da07ef961
7 changed files with 233 additions and 68 deletions

View File

@ -13,6 +13,18 @@ bool AnimGraph::init(AnimGraphContext& context) {
}
}
std::vector<AnimGraphConnection>& 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<AnimData*>(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<AnimData*>(input_connection.m_source_socket.m_reference.ptr));
m_anim_data_work_buffer.free(static_cast<AnimData*>(
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<AnimData*>(
*graph_output_connection.m_source_socket.m_reference.ptr_ptr);
AnimData* target_data = static_cast<AnimData*>(
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);

View File

@ -9,30 +9,57 @@
#include "AnimGraphNodes.h"
struct AnimDataWorkBuffer {
std::vector<AnimData> m_eval_anim_data;
std::vector<AnimData*> 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<Socket>& getGraphOutputs() { return m_socket_accessor->m_inputs; }
std::vector<Socket>& 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) {

View File

@ -9,11 +9,13 @@
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include "SyncTrack.h"
#include "ozz/base/containers/vector.h"
#include <ozz/base/maths/soa_transform.h>
#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<std::string, ozz::animation::Animation*> 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;
};

View File

@ -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::animation::Animation>()) {
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::animation::Animation>()) {
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;
}

View File

@ -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;

View File

@ -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<AnimData>("GraphOutput", nullptr);
AnimNodeResource& graph_input_node =
graph_resource.getGraphInputNode();
graph_input_node.m_socket_accessor->RegisterOutput<float>(
"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<float*>(graph.getInputPtr("GraphFloatInput"));
Socket* anim_output_socket =
graph.getOutputSocket("GraphOutput");
AnimData* graph_anim_output = static_cast<AnimData*>(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));
}

View File

@ -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();
}
}
}