From 1933308b82d3a93c06849444a918b298150f2946 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Wed, 4 Jun 2025 23:20:01 -0500 Subject: [PATCH] VideoCommon: add gpu skinning utility functions --- .../GpuSkinningDataUtils.cpp | 846 ++++++++++++++++++ .../GraphicsModEditor/GpuSkinningDataUtils.h | 19 + 2 files changed, 865 insertions(+) create mode 100644 Source/Core/VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.cpp create mode 100644 Source/Core/VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.h diff --git a/Source/Core/VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.cpp b/Source/Core/VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.cpp new file mode 100644 index 0000000000..005b95e5a9 --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.cpp @@ -0,0 +1,846 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.h" + +#include +#include + +#include "Common/EnumUtils.h" +#include "Common/JsonUtil.h" +#include "Common/Logging/Log.h" +#include "Common/Matrix.h" + +namespace GraphicsModEditor +{ +namespace +{ +Common::Vec3 closestPointTriangle(const Common::Vec3& p, const Common::Vec3& a, + const Common::Vec3& b, const Common::Vec3& c) +{ + const Common::Vec3 ab = b - a; + const Common::Vec3 ac = c - a; + const Common::Vec3 ap = p - a; + + const float d1 = ab.Dot(ap); + const float d2 = ac.Dot(ap); + if (d1 <= 0.f && d2 <= 0.f) + return a; + + const Common::Vec3 bp = p - b; + const float d3 = ab.Dot(bp); + const float d4 = ac.Dot(bp); + if (d3 >= 0.f && d4 <= d3) + return b; + + const Common::Vec3 cp = p - c; + const float d5 = ab.Dot(cp); + const float d6 = ac.Dot(cp); + if (d6 >= 0.f && d5 <= d6) + return c; + + const float vc = d1 * d4 - d3 * d2; + if (vc <= 0.f && d1 >= 0.f && d3 <= 0.f) + { + const float v = d1 / (d1 - d3); + return a + ab * v; + } + + const float vb = d5 * d2 - d1 * d6; + if (vb <= 0.f && d2 >= 0.f && d6 <= 0.f) + { + const float v = d2 / (d2 - d6); + return a + ac * v; + } + + const float va = d3 * d6 - d5 * d4; + if (va <= 0.f && (d4 - d3) >= 0.f && (d5 - d6) >= 0.f) + { + const float v = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return b + (c - b) * v; + } + + const float denom = 1.f / (va + vb + vc); + const float v = vb * denom; + const float w = vc * denom; + return a + ab * v + ac * w; +} + +std::vector GetClosestDistancePerPosition(const Common::Vec3& reference_position, + std::span positions) +{ + std::vector result; + result.reserve(positions.size()); + for (std::size_t i = 0; i < positions.size(); i++) + { + const auto& position = positions[i]; + const float distance = (reference_position - position).LengthSquared(); + result.push_back(distance); + } + return result; +} + +GraphicsModSystem::DrawCallID GetClosestDrawCall(const Common::Vec3& v0, const Common::Vec3& v1, + const Common::Vec3& v2, + const GpuSkinningData& gpu_skinning_data) +{ + std::map draw_call_to_distance; + for (const auto& [draw_call, draw_data] : gpu_skinning_data.m_draw_call_to_gpu_skinning) + { + const auto [iter, inserted] = draw_call_to_distance.try_emplace(draw_call, 0.0f); + + const auto v0_lookup = GetClosestDistancePerPosition(v0, draw_data.positions); + const auto v1_lookup = GetClosestDistancePerPosition(v1, draw_data.positions); + const auto v2_lookup = GetClosestDistancePerPosition(v2, draw_data.positions); + + float smallest = std::numeric_limits::max(); + /*float smallest_v0 = std::numeric_limits::max(); + float smallest_v1 = std::numeric_limits::max(); + float smallest_v2 = std::numeric_limits::max(); + for (std::size_t index = 0; index < draw_data.positions.size(); index++) + { + const auto distance_v0 = v0_lookup[index]; + if (distance_v0 < smallest_v0) + smallest_v0 = distance_v0; + + const auto distance_v1 = v1_lookup[index]; + if (distance_v1 < smallest_v1) + smallest_v1 = distance_v1; + + const auto distance_v2 = v2_lookup[index]; + if (distance_v2 < smallest_v2) + smallest_v2 = distance_v2; + } + smallest = smallest_v0 + smallest_v1 + smallest_v2;*/ + + /*for (std::size_t index = 0; index < draw_data.positions.size(); index++) + { + const auto distance = v0_lookup[index] + v1_lookup[index] + v2_lookup[index]; + if (distance < smallest) + smallest = distance; + }*/ + + for (std::size_t indice_index = 0; indice_index < draw_data.indices.size(); indice_index += 3) + { + const auto i0 = draw_data.indices[indice_index + 0]; + const auto i1 = draw_data.indices[indice_index + 1]; + const auto i2 = draw_data.indices[indice_index + 2]; + + float weight = v0_lookup[i0] + v1_lookup[i1] + v2_lookup[i2]; + if (weight < smallest) + { + smallest = weight; + } + + weight = v0_lookup[i0] + v1_lookup[i2] + v2_lookup[i1]; + if (weight < smallest) + { + smallest = weight; + } + + weight = v0_lookup[i1] + v1_lookup[i0] + v2_lookup[i2]; + if (weight < smallest) + { + smallest = weight; + } + + weight = v0_lookup[i1] + v1_lookup[i2] + v2_lookup[i0]; + if (weight < smallest) + { + smallest = weight; + } + + weight = v0_lookup[i2] + v1_lookup[i0] + v2_lookup[i1]; + if (weight < smallest) + { + smallest = weight; + } + + weight = v0_lookup[i2] + v1_lookup[i1] + v2_lookup[i0]; + if (weight < smallest) + { + smallest = weight; + } + } + iter->second = smallest; + } + + const auto chosen = + std::min_element(draw_call_to_distance.begin(), draw_call_to_distance.end(), + [](const auto& l, const auto& r) { return l.second < r.second; }); + return chosen == draw_call_to_distance.end() ? GraphicsModSystem::DrawCallID::INVALID : + chosen->first; +} + +struct OriginalVertexData +{ + u32 skinning_index; + const Common::Matrix44* transform; +}; +/*std::optional +GetNearestOriginalMeshData(const Common::Vec3& reference_position, GraphicsModSystem::DrawCallID +draw_call_chosen, const GpuSkinningData& gpu_skinning_data) +{ + struct ClosestPointData + { + GraphicsModSystem::DrawCallID draw_call_id; + std::size_t triangle_start_index; + std::size_t point_index; + const GpuSkinningData::GpuSkinningForDrawCall* draw_data; + }; + std::vector closest_points_data; + std::optional nearest; + for (const auto& [draw_call, draw_data] : gpu_skinning_data.m_draw_call_to_gpu_skinning) + { + for (std::size_t starting_index = 0; starting_index < draw_data.indices.size(); + starting_index += 3) + { + const auto orig_mesh_start_index = draw_data.indices[starting_index]; + for (std::size_t i = 0; i < 3; i++) + { + const auto indice_index = starting_index + i; + const auto orig_mesh_index = draw_data.indices[indice_index]; + const auto& original_position = draw_data.positions[orig_mesh_index]; + const float distance = (new_position - original_position).LengthSquared(); + if (!nearest || distance < nearest) + { + closest_points_data.clear(); + nearest = distance; + ClosestPointData closest_point_data; + closest_point_data.draw_call_id = draw_call; + closest_point_data.triangle_start_index = orig_mesh_start_index; + closest_point_data.point_index = orig_mesh_index; + closest_point_data.draw_data = &draw_data; + closest_points_data.push_back(std::move(closest_point_data)); + } + else if (distance == nearest) + { + ClosestPointData closest_point_data; + closest_point_data.draw_call_id = draw_call; + closest_point_data.triangle_start_index = orig_mesh_start_index; + closest_point_data.point_index = orig_mesh_index; + closest_point_data.draw_data = &draw_data; + closest_points_data.push_back(std::move(closest_point_data)); + } + } + } + } + + if (!nearest) + return std::nullopt; + + // We only have one point's data, return it + if (closest_points_data.size() == 1) + { + auto& draw_data = *closest_points_data[0].draw_data; + auto index = closest_points_data[0].point_index; + + OriginalPointData result; + result.draw_call_id = closest_points_data[0].draw_call_id; + result.transform = &draw_data.view_to_object_transforms[index]; + result.skinning_index = draw_data.skinning_index_per_vertex[index]; + return result; + } + + // In the case of a tie, filter the triangles by looking at the other points in the + // reference mesh triangle + const auto get_closest_point = [](const Common::Vec3& new_position, + const std::vector& closest_points_data) { + std::vector closest_points_data_result; + std::optional nearest; + for (const auto& closest_point : closest_points_data) + { + for (std::size_t i = 0; i < 3; i++) + { + const std::size_t triangle_index = i + closest_point.triangle_start_index; + if (triangle_index == closest_point.point_index) + continue; + const auto& original_position = closest_point.draw_data->positions[triangle_index]; + const float distance = (new_position - original_position).LengthSquared(); + if (!nearest || distance < nearest) + { + nearest = distance; + closest_points_data_result.clear(); + closest_points_data_result.push_back(closest_point); + } + else if (distance == nearest) + { + closest_points_data_result.push_back(closest_point); + } + } + } + return closest_points_data_result; + }; + + auto filtered_0 = get_closest_point(weight_0, closest_points_data); + auto filtered_1 = get_closest_point(weight_1, filtered_0); + + if (filtered_1.empty()) + return std::nullopt; + + auto& draw_data = *filtered_1[0].draw_data; + auto index = filtered_1[0].point_index; + + OriginalPointData result; + result.draw_call_id = filtered_1[0].draw_call_id; + result.transform = &draw_data.view_to_object_transforms[index]; + result.skinning_index = draw_data.skinning_index_per_vertex[index]; + return result; + + // In the case of a tie between points... + // pick the point closest to the others on the triangle + nearest = std::nullopt; + std::size_t chosen_index = 0; + const GpuSkinningData::GpuSkinningForDrawCall* chosen_draw_data = nullptr; + GraphicsModSystem::DrawCallID chosen_draw_call = GraphicsModSystem::DrawCallID::INVALID; + for (const auto& closest_point_data : closest_points_data) + { + const auto draw_data = closest_point_data.draw_data; + const auto triangle_start_index = closest_point_data.triangle_start_index; + const auto closest_pt = closestPointTriangle(new_position, + draw_data->positions[triangle_start_index], draw_data->positions[triangle_start_index + 1], + draw_data->positions[triangle_start_index + 2]); + const float distance = (-closest_pt).LengthSquared(); + if (!nearest || distance < nearest) + { + nearest = distance; + chosen_index = closest_point_data.point_index; + chosen_draw_data = closest_point_data.draw_data; + chosen_draw_call = closest_point_data.draw_call_id; + } + } + + OriginalPointData result; + result.draw_call_id = chosen_draw_call; + result.transform = &chosen_draw_data->view_to_object_transforms[chosen_index]; + result.skinning_index = chosen_draw_data->skinning_index_per_vertex[chosen_index]; + return result; + return std::nullopt; +}*/ + +std::optional +GetNearestOriginalVertexData(const Common::Vec3& reference_position, + GraphicsModSystem::DrawCallID draw_call_chosen, + const GpuSkinningData& gpu_skinning_data) +{ + OriginalVertexData result; + if (const auto draw_data = gpu_skinning_data.m_draw_call_to_gpu_skinning.find(draw_call_chosen); + draw_data != gpu_skinning_data.m_draw_call_to_gpu_skinning.end()) + { + std::optional nearest; + for (std::size_t i = 0; i < draw_data->second.positions.size(); i++) + { + const auto& position = draw_data->second.positions[i]; + const float distance = (reference_position - position).LengthSquared(); + if (!nearest || distance < nearest) + { + result.skinning_index = draw_data->second.skinning_index_per_vertex[i]; + result.transform = &draw_data->second.view_to_object_transforms[i]; + nearest = distance; + } + } + return result; + } + return std::nullopt; +} + +Common::Vec3 ReadVertexPosition(const VideoCommon::MeshDataChunk& mesh_chunk, u16 index) +{ + const auto position_offset = mesh_chunk.vertex_declaration.position.offset; + const auto vert_ptr = + reinterpret_cast(mesh_chunk.vertex_data.get()) + index * mesh_chunk.vertex_stride; + Common::Vec3 vertex_position; + std::memcpy(&vertex_position.x, vert_ptr + position_offset, sizeof(float)); + std::memcpy(&vertex_position.y, vert_ptr + sizeof(float) + position_offset, sizeof(float)); + std::memcpy(&vertex_position.z, vert_ptr + sizeof(float) * 2 + position_offset, sizeof(float)); + return vertex_position; +} + +void WriteVertexPosition(VideoCommon::MeshDataChunk& mesh_chunk, + const Common::Vec3& vertex_position, u16 index) +{ + const auto position_offset = mesh_chunk.vertex_declaration.position.offset; + const auto vert_ptr = + reinterpret_cast(mesh_chunk.vertex_data.get()) + index * mesh_chunk.vertex_stride; + + std::memcpy(vert_ptr + position_offset, &vertex_position.x, sizeof(float)); + std::memcpy(vert_ptr + position_offset + sizeof(float), &vertex_position.y, sizeof(float)); + std::memcpy(vert_ptr + position_offset + sizeof(float) * 2, &vertex_position.z, sizeof(float)); +} + +void WriteGpuSkinningIndex(VideoCommon::MeshDataChunk& mesh_chunk, u32 skinning_index, u16 index) +{ + const auto posmtx_offset = mesh_chunk.vertex_declaration.posmtx.offset; + const auto vert_ptr = + reinterpret_cast(mesh_chunk.vertex_data.get()) + index * mesh_chunk.vertex_stride; + + std::memcpy(vert_ptr + posmtx_offset, &skinning_index, sizeof(u32)); +} + +void WriteIndiceIndex(VideoCommon::MeshDataChunk& mesh_chunk, u16 indice_index) +{ + const auto index_ptr = reinterpret_cast(mesh_chunk.indices.get()) + indice_index; + + std::memcpy(index_ptr, &indice_index, sizeof(u16)); +} + +void CopyReferenceData(VideoCommon::MeshDataChunk& skinning_chunk, std::size_t skinning_index_size, + u16 skinning_index, const VideoCommon::MeshDataChunk& reference_chunk, + u16 reference_index) +{ + const auto dest_ptr = reinterpret_cast(skinning_chunk.vertex_data.get()) + + skinning_index * skinning_chunk.vertex_stride; + const auto src_ptr = reinterpret_cast(reference_chunk.vertex_data.get()) + + reference_index * reference_chunk.vertex_stride; + + std::memcpy(dest_ptr + skinning_index_size, src_ptr, reference_chunk.vertex_stride); +} + +void CalculateMeshData(const GpuSkinningData& gpu_skinning_data, + const VideoCommon::MeshData& reference_mesh_data, + VideoCommon::MeshData& calculated_mesh_data) +{ + using MeshVertexData = std::pair; + std::map>> + draw_call_to_chunk_to_mesh_data; + + for (std::size_t chunk_index = 0; chunk_index < reference_mesh_data.m_mesh_chunks.size(); + chunk_index++) + { + const auto& reference_chunk = reference_mesh_data.m_mesh_chunks[chunk_index]; + + // TODO: handle other primitive types + if (reference_chunk.primitive_type != PrimitiveType::Triangles) + return; + + /*auto& new_mesh_chunk = calculated_mesh_data.m_mesh_chunks[chunk_index]; + + // Tell our vertex declaration we have skinning data + new_mesh_chunk.vertex_declaration.posmtx.components = 4; + new_mesh_chunk.vertex_declaration.posmtx.enable = true; + new_mesh_chunk.vertex_declaration.posmtx.integer = true; + new_mesh_chunk.vertex_declaration.posmtx.offset = 0; + new_mesh_chunk.vertex_declaration.posmtx.type = ComponentFormat::UByte; + new_mesh_chunk.components_available |= VB_HAS_POSMTXIDX;*/ + + // Now update our data and calculate the indices per draw call + u32 indice_index = 0; + while (indice_index < reference_chunk.num_indices) + { + std::vector> index_original_mesh_data_pairs; + // std::map draw_call_to_count; + + const auto i0 = reference_chunk.indices[indice_index + 0]; + const auto v0 = ReadVertexPosition(reference_chunk, i0); + const auto v0_transformed = reference_chunk.transform.Transform(v0, 1); + + const auto i1 = reference_chunk.indices[indice_index + 1]; + const auto v1 = ReadVertexPosition(reference_chunk, i1); + const auto v1_transformed = reference_chunk.transform.Transform(v1, 1); + + const auto i2 = reference_chunk.indices[indice_index + 2]; + const auto v2 = ReadVertexPosition(reference_chunk, i2); + const auto v2_transformed = reference_chunk.transform.Transform(v2, 1); + + indice_index += 3; + + const auto draw_call = + GetClosestDrawCall(v0_transformed, v1_transformed, v2_transformed, gpu_skinning_data); + if (draw_call == GraphicsModSystem::DrawCallID::INVALID) + continue; + + auto& skinning_chunk_data = draw_call_to_chunk_to_mesh_data[draw_call]; + + if (const auto original_vertex_data = + GetNearestOriginalVertexData(v0_transformed, draw_call, gpu_skinning_data)) + { + skinning_chunk_data[chunk_index].emplace_back(i0, *original_vertex_data); + } + + if (const auto original_vertex_data = + GetNearestOriginalVertexData(v1_transformed, draw_call, gpu_skinning_data)) + { + skinning_chunk_data[chunk_index].emplace_back(i1, *original_vertex_data); + } + + if (const auto original_vertex_data = + GetNearestOriginalVertexData(v2_transformed, draw_call, gpu_skinning_data)) + { + skinning_chunk_data[chunk_index].emplace_back(i2, *original_vertex_data); + } + + /*if (const auto original_mesh_data = GetNearestOriginalMeshData( + vertex_position_0, vertex_position_1, vertex_position_2, gpu_skinning_data)) + { + draw_call_to_count[original_mesh_data->draw_call_id]++; + index_original_mesh_data_pairs.emplace_back(reference_index_0, *original_mesh_data); + } + + if (const auto original_mesh_data = GetNearestOriginalMeshData( + vertex_position_1, vertex_position_0, vertex_position_2, gpu_skinning_data)) + { + draw_call_to_count[original_mesh_data->draw_call_id]++; + index_original_mesh_data_pairs.emplace_back(reference_index_1, *original_mesh_data); + } + + if (const auto original_mesh_data = GetNearestOriginalMeshData( + vertex_position_2, vertex_position_0, vertex_position_1, gpu_skinning_data)) + { + draw_call_to_count[original_mesh_data->draw_call_id]++; + index_original_mesh_data_pairs.emplace_back(reference_index_2, *original_mesh_data); + }*/ + + /*const auto max_draw_call_pair = std::max_element( + draw_call_to_count.begin(), draw_call_to_count.end(), + [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });*/ + + /*auto& skinning_chunks_per_draw_call = + calculated_mesh_data.m_gpu_skinning_chunks[max_draw_call_pair->first]; + if (skinning_chunks_per_draw_call.size() < reference_mesh_data.m_mesh_chunks.size()) + skinning_chunks_per_draw_call.resize(reference_mesh_data.m_mesh_chunks.size()); + auto& skinning_chunk = skinning_chunks_per_draw_call[chunk_index];*/ + + /*auto& skinning_chunks_per_draw_call = + calculated_mesh_data.m_gpu_skinning_chunks[draw_call]; + if (skinning_chunks_per_draw_call.size() < reference_mesh_data.m_mesh_chunks.size()) + skinning_chunks_per_draw_call.resize(reference_mesh_data.m_mesh_chunks.size()); + auto& skinning_chunk = skinning_chunks_per_draw_call[chunk_index]; + + auto& skinning_chunk_data = draw_call_to_chunk_to_mesh_data[draw_call]; + + for (const auto& [index, original_mesh_data] : index_original_mesh_data_pairs) + { + skinning_chunk_data skinning_chunk.components_available = + reference_chunk.components_available; + skinning_chunk.indices = + std::make_unique(reference_chunk.) skinning_chunk.indices.push_back(index); + + if (indices_seen.insert(index).second) + { + WriteGpuSkinningIndex(new_mesh_chunk, original_mesh_data.skinning_index, index); + if (original_mesh_data.transform->data != Common::Matrix44::Identity().data) + { + // Get existing vertex position + const auto new_vertex_position = ReadVertexPosition(new_mesh_chunk, index); + + // Apply the transform to it + const auto new_vertex_position_transformed = + original_mesh_data.transform->Transform(new_vertex_position, 1); + + // Now write that back to the data + WriteVertexPosition(new_mesh_chunk, new_vertex_position_transformed, index); + } + indices_to_point_data[index] = original_mesh_data; + } + else + { + auto& previous_point_data = indices_to_point_data[index]; + if (previous_point_data.draw_call_id != original_mesh_data.draw_call_id || + previous_point_data.skinning_index != original_mesh_data.skinning_index) + { + ERROR_LOG_FMT(VIDEO, + "Saw duplicate indice '{}' with different data, previous indice: " + "[draw={}, skin_idx={}], new " + "indice: [draw={}, skin_idx={}]", + index, Common::ToUnderlying(previous_point_data.draw_call_id), + previous_point_data.skinning_index, + Common::ToUnderlying(original_mesh_data.draw_call_id), + original_mesh_data.skinning_index); + } + } + }*/ + } + } + + // Copy our reference mesh data into our skinning chunks + for (const auto& [draw_call, chunk_to_mesh_data] : draw_call_to_chunk_to_mesh_data) + { + auto& skinning_chunk_list = calculated_mesh_data.m_gpu_skinning_chunks[draw_call]; + + skinning_chunk_list.reserve(chunk_to_mesh_data.size()); + for (const auto& [chunk_index, mesh_data] : chunk_to_mesh_data) + { + const auto& reference_chunk = reference_mesh_data.m_mesh_chunks[chunk_index]; + const auto& replacement_chunk = calculated_mesh_data.m_mesh_chunks[chunk_index]; + auto& skinning_chunk = skinning_chunk_list.emplace_back(); + skinning_chunk.components_available = reference_chunk.components_available; + skinning_chunk.material_name = reference_chunk.material_name; + skinning_chunk.primitive_type = reference_chunk.primitive_type; + skinning_chunk.transform = reference_chunk.transform; + skinning_chunk.vertex_declaration = reference_chunk.vertex_declaration; + + // Add to our stride for the skinning index + const std::size_t skinning_index_size = 4; + skinning_chunk.vertex_stride = reference_chunk.vertex_stride + skinning_index_size; + skinning_chunk.vertex_declaration.stride += skinning_index_size; + + // Update our declaration to include the skinning index + skinning_chunk.vertex_declaration.posmtx.components = 4; + skinning_chunk.vertex_declaration.posmtx.enable = true; + skinning_chunk.vertex_declaration.posmtx.integer = true; + skinning_chunk.vertex_declaration.posmtx.offset = 0; + skinning_chunk.vertex_declaration.posmtx.type = ComponentFormat::UByte; + skinning_chunk.components_available |= VB_HAS_POSMTXIDX; + + // Update our previous entries to offset by the skinning index + if (skinning_chunk.vertex_declaration.position.enable) + skinning_chunk.vertex_declaration.position.offset += skinning_index_size; + + for (auto& color : skinning_chunk.vertex_declaration.colors) + { + if (color.enable) + color.offset += skinning_index_size; + } + + for (auto& normal : skinning_chunk.vertex_declaration.normals) + { + if (normal.enable) + normal.offset += skinning_index_size; + } + + for (auto& texcoord : skinning_chunk.vertex_declaration.texcoords) + { + if (texcoord.enable) + texcoord.offset += skinning_index_size; + } + + // Create index data + skinning_chunk.indices = std::make_unique(mesh_data.size()); + skinning_chunk.num_indices = static_cast(mesh_data.size()); + + // Create vertex data + skinning_chunk.vertex_data = + std::make_unique(mesh_data.size() * skinning_chunk.vertex_stride); + skinning_chunk.num_vertices = static_cast(mesh_data.size()); + + u16 next_indice = 0; + // Now fill out all data... + for (const auto& [reference_index, original_vertex_data] : mesh_data) + { + CopyReferenceData(skinning_chunk, skinning_index_size, next_indice, replacement_chunk, + reference_index); + WriteGpuSkinningIndex(skinning_chunk, original_vertex_data.skinning_index, next_indice); + if (original_vertex_data.transform->data != Common::Matrix44::Identity().data) + { + // Get existing vertex position + const auto vertex_position = ReadVertexPosition(replacement_chunk, reference_index); + + // Apply the reference transform to it + const auto vertex_position_transformed = + replacement_chunk.transform.Transform(vertex_position, 1); + + // Apply the transform to it + const auto new_vertex_position_transformed = + original_vertex_data.transform->Transform(vertex_position_transformed, 1); + + // Now write that back to the data + WriteVertexPosition(skinning_chunk, new_vertex_position_transformed, next_indice); + } + + WriteIndiceIndex(skinning_chunk, next_indice); + next_indice++; + } + } + } + + // We've copied everything out of the original mesh, we can remove it as it isn't + // used during the gpu skinning process + calculated_mesh_data.m_mesh_chunks.clear(); + + // Calculate bounding box for draw call + /*std::optional min_pos; + std::optional max_pos; + for (std::size_t i = 0; i < draw_data.positions.size(); i++) + { + const auto& position = draw_data.positions[i]; + if (!min_pos) + { + min_pos = position; + } + else + { + if (position.x < min_pos->x) + min_pos->x = position.x; + if (position.y < min_pos->y) + min_pos->y = position.y; + if (position.z < min_pos->z) + min_pos->z = position.z; + } + + if (!max_pos) + { + max_pos = position; + } + else + { + if (position.x > max_pos->x) + max_pos->x = position.x; + if (position.y > max_pos->y) + max_pos->y = position.y; + if (position.z > max_pos->z) + max_pos->z = position.z; + } + } + if (!min_pos || !max_pos) + { + // TODO: error + return; + } + const auto within_bounds = [&](Common::Vec3 position) -> bool { + return position.x >= min_pos->x && position.y >= min_pos->y && position.z >= min_pos->z && + position.x <= max_pos->x && position.y <= max_pos->y && position.z <= max_pos->z; + };*/ +} +} // namespace +picojson::object ToJsonObject(const GpuSkinningData& gpu_skinning_data) +{ + picojson::array arr; + for (const auto& [draw_call, skinning_data] : gpu_skinning_data.m_draw_call_to_gpu_skinning) + { + picojson::object data_json; + data_json.emplace("draw_call_id", std::to_string(Common::ToUnderlying(draw_call))); + + picojson::array positions_arr_json; + for (const auto& position : skinning_data.positions) + { + positions_arr_json.push_back(picojson::value{::ToJsonObject(position)}); + } + data_json.emplace("positions", positions_arr_json); + + picojson::array transforms_arr_json; + for (const auto& transform : skinning_data.view_to_object_transforms) + { + transforms_arr_json.push_back(picojson::value{::ToJsonObject(transform)}); + } + data_json.emplace("view_to_object_transforms", transforms_arr_json); + + picojson::array gpu_skinning_indices_arr_json; + for (const auto& index : skinning_data.skinning_index_per_vertex) + { + gpu_skinning_indices_arr_json.push_back(picojson::value{static_cast(index)}); + } + data_json.emplace("posmtx_indices", gpu_skinning_indices_arr_json); + + // TODO: debug data... + picojson::array indices_arr_json; + for (const auto& indice : skinning_data.indices) + { + indices_arr_json.push_back(picojson::value{static_cast(indice)}); + } + data_json.emplace("indices", indices_arr_json); + + arr.push_back(picojson::value{data_json}); + } + picojson::object obj; + obj.emplace("data", arr); + return obj; +} + +void FromJson(const picojson::object& obj, GpuSkinningData& gpu_skinning_data) +{ + const auto data_json_iter = obj.find("data"); + if (data_json_iter == obj.end()) [[unlikely]] + return; + + if (!data_json_iter->second.is()) + return; + + const auto arr_json = data_json_iter->second.get(); + for (const auto arr_json_val : arr_json) + { + if (!arr_json_val.is()) + continue; + + const auto data_value_json = arr_json_val.get(); + const auto draw_call_id_str = ReadStringFromJson(data_value_json, "draw_call_id").value_or("0"); + u64 draw_call_value = 0; + if (!TryParse(draw_call_id_str, &draw_call_value)) + { + ERROR_LOG_FMT(VIDEO, "'draw_call_id' is not a number!"); + return; + } + const auto draw_call_id = static_cast(draw_call_value); + + GpuSkinningData::GpuSkinningForDrawCall drawcall_data; + if (const auto positions_iter = data_value_json.find("positions"); + positions_iter != data_value_json.end()) + { + if (positions_iter->second.is()) + { + const auto positions_arr = positions_iter->second.get(); + for (const auto& position_arr_value : positions_arr) + { + if (position_arr_value.is()) + { + const auto positions_arr_obj = position_arr_value.get(); + Common::Vec3 vec; + ::FromJson(positions_arr_obj, vec); + drawcall_data.positions.push_back(std::move(vec)); + } + } + } + } + + if (const auto transforms_iter = data_value_json.find("view_to_object_transforms"); + transforms_iter != data_value_json.end()) + { + if (transforms_iter->second.is()) + { + const auto transforms_arr = transforms_iter->second.get(); + for (const auto& transform_arr_value : transforms_arr) + { + if (transform_arr_value.is()) + { + const auto transforms_arr_obj = transform_arr_value.get(); + Common::Matrix44 mat; + ::FromJson(transforms_arr_obj, mat); + drawcall_data.view_to_object_transforms.push_back(std::move(mat)); + } + } + } + } + + if (const auto posmtx_indices_iter = data_value_json.find("posmtx_indices"); + posmtx_indices_iter != data_value_json.end()) + { + if (posmtx_indices_iter->second.is()) + { + const auto posmtx_indices_arr = posmtx_indices_iter->second.get(); + for (const auto& posmtx_index_value : posmtx_indices_arr) + { + if (posmtx_index_value.is()) + { + drawcall_data.skinning_index_per_vertex.push_back( + static_cast(posmtx_index_value.get())); + } + } + } + } + + if (const auto indices_iter = data_value_json.find("indices"); + indices_iter != data_value_json.end()) + { + if (indices_iter->second.is()) + { + const auto indices_arr = indices_iter->second.get(); + for (const auto& indice_value : indices_arr) + { + if (indice_value.is()) + { + drawcall_data.indices.push_back(static_cast(indice_value.get())); + } + } + } + } + + gpu_skinning_data.m_draw_call_to_gpu_skinning[draw_call_id] = std::move(drawcall_data); + } +} + +void CalculateMeshDataWithGpuSkinning(const GpuSkinningData& gpu_skinning_data, + const VideoCommon::MeshData& reference_mesh_data, + VideoCommon::MeshData& calculated_mesh_data) +{ + CalculateMeshData(gpu_skinning_data, reference_mesh_data, calculated_mesh_data); +} +} // namespace GraphicsModEditor diff --git a/Source/Core/VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.h b/Source/Core/VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.h new file mode 100644 index 0000000000..f56fa08d2f --- /dev/null +++ b/Source/Core/VideoCommon/GraphicsModEditor/GpuSkinningDataUtils.h @@ -0,0 +1,19 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "VideoCommon/Assets/MeshAsset.h" +#include "VideoCommon/GraphicsModEditor/EditorTypes.h" + +namespace GraphicsModEditor +{ +picojson::object ToJsonObject(const GpuSkinningData& gpu_skinning_data); +void FromJson(const picojson::object& obj, GpuSkinningData& gpu_skinning_data); + +void CalculateMeshDataWithGpuSkinning(const GpuSkinningData& gpu_skinning_data, + const VideoCommon::MeshData& reference_mesh_data, + VideoCommon::MeshData& calculated_mesh_data); +} // namespace GraphicsModEditor