save code

This commit is contained in:
xsl
2026-04-26 00:12:56 +08:00
parent 50680ee854
commit c5ec7c7dc5
22 changed files with 1170 additions and 216 deletions
+244 -40
View File
@@ -1,4 +1,4 @@
#include "FaceApp.h"
#include "FaceApp.h"
#include "hardcode_data.h"
#include "lodepng.h"
#include <sstream>
@@ -47,10 +47,13 @@ void FaceApp::Stop()
void ReceiveFacePoint(float* pos, int pointCount, int width, int height)
{
FaceApp* self = FaceApp::Get();
if (self != nullptr)
if (self == nullptr)
{
FaceApp::Get()->update_face_vertex_buffer(pos, pointCount);
FACE_DBG_LOG_THROTTLED("ReceiveFacePoint.noFaceApp",
"ReceiveFacePoint dropped: FaceApp::Get() == nullptr");
return;
}
self->update_face_vertex_buffer(pos, pointCount);
}
// 定义帧数据结构
@@ -60,10 +63,12 @@ struct FrameData {
int height;
int rowStride;
size_t dataSize;
int rotation; // CameraX 给的 rotationDegrees,用于 shader 端 UV 旋转
bool mirrorX; // 前置摄像头时为 trueshader 在 gl_Position.x 翻转一次
FrameData(uint8_t* d, int w, int h, int rs, size_t ds)
: data(nullptr), width(w), height(h), rowStride(rs), dataSize(ds) {
// 深拷贝数据
FrameData(uint8_t* d, int w, int h, int rs, size_t ds, int rot, bool mx)
: data(nullptr), width(w), height(h), rowStride(rs), dataSize(ds),
rotation(rot), mirrorX(mx) {
if (d && ds > 0) {
data = new uint8_t[dataSize];
memcpy(data, d, dataSize);
@@ -77,18 +82,16 @@ struct FrameData {
}
}
// 禁止拷贝构造和赋值(使用移动语义)
FrameData(const FrameData&) = delete;
FrameData& operator=(const FrameData&) = delete;
// 移动构造
FrameData(FrameData&& other) noexcept
: data(other.data), width(other.width), height(other.height),
rowStride(other.rowStride), dataSize(other.dataSize) {
rowStride(other.rowStride), dataSize(other.dataSize),
rotation(other.rotation), mirrorX(other.mirrorX) {
other.data = nullptr;
}
// 移动赋值
FrameData& operator=(FrameData&& other) noexcept {
if (this != &other) {
if (data) delete[] data;
@@ -97,6 +100,8 @@ struct FrameData {
height = other.height;
rowStride = other.rowStride;
dataSize = other.dataSize;
rotation = other.rotation;
mirrorX = other.mirrorX;
other.data = nullptr;
}
return *this;
@@ -108,7 +113,7 @@ std::queue<FrameData> frameQueue;
std::mutex queueMutex;
const int MAX_QUEUE_SIZE = 4;
void TextureLoadProcessWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize)
void TextureLoadProcessWithVulkan(uint8_t* data, int width, int height, int rowStride, size_t dataSize, int rotation, bool mirrorX)
{
if (!FaceApp::Get()->isInited()) {
return;
@@ -116,24 +121,25 @@ void TextureLoadProcessWithVulkan(uint8_t* data, int width, int height, int rowS
std::lock_guard<std::mutex> lock(queueMutex);
// 添加新帧数据到队列
frameQueue.emplace(data, width, height, rowStride, dataSize);
frameQueue.emplace(data, width, height, rowStride, dataSize, rotation, mirrorX);
// 如果队列大小达到阈值,开始处理最旧的一帧
if (frameQueue.size() >= MAX_QUEUE_SIZE) {
Texture& tex_bg = FaceApp::Get()->tex_bg;
FrameData& oldestFrame = frameQueue.front();
FaceApp::Get()->processWithVulkan(
// 走 FaceApp::processCameraFrame:相比 base::processWithVulkan,它多做两件
// 事 —— ① 检测帧尺寸变化时 destroy/recreate tex_bg(不同手机或 CameraX
// 重建 session 后第一帧尺寸可能与之前不同);② 缓存 raw_aspect / rotation
// / mirrorX 到 m_cameraAspect / m_cameraRotation / m_mirrorX,由 render()
// 推到 push constant。
FaceApp::Get()->processCameraFrame(
oldestFrame.data,
oldestFrame.width,
oldestFrame.height,
oldestFrame.rowStride,
oldestFrame.dataSize,
tex_bg,
false,
FaceApp::Get()->commandPool,
"data_from_camera"
oldestFrame.rotation,
oldestFrame.mirrorX
);
frameQueue.pop();
@@ -298,15 +304,21 @@ void FaceApp::create_face_pipelines()
VkPipelineRasterizationStateCreateInfo rasterization_state{};
rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterization_state.polygonMode = VK_POLYGON_MODE_FILL;
// ★ 关键:face 必须 NONE。原因:
// 1. face mesh 是 2D 平面网格(texture.vert 让所有顶点 z=0.5),不存在
// 自遮挡,没必要 cull;
// 2. 前置摄像头镜子效果是在 vertex shader 里通过 NDC.x 翻转实现的
// `position.x *= (1.0 - 2.0 * pc.mirror_x)`)。X 翻转会把所有三角
// 形的卷绕方向从 CCW 变 CW。如果这里 cullMode=BACK_BIT + frontFace=
// CCW,前置时整张脸会被 cull,face 完全不可见——这正是历史踩坑点。
// 所以请勿改回 BACK_BIT。如果未来要再加 cull,必须配两套 pipeline(前置
// 用 CW、后置用 CCW),或在前置时改成 frontFace=CW,否则一定会复现这个 bug。
rasterization_state.cullMode = VK_CULL_MODE_NONE;
rasterization_state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterization_state.flags = 0;
rasterization_state.depthClampEnable = VK_FALSE;
rasterization_state.lineWidth = 1.0f;
rasterization_state.cullMode = VK_CULL_MODE_BACK_BIT;
rasterization_state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
@@ -333,13 +345,16 @@ void FaceApp::create_face_pipelines()
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
// Note: Using reversed depth-buffer for increased precision, so Greater depth values are kept
// face 是 2D 平面网格(texture.vert 让顶点 z=0.5 一致),完全不需要 depth
// test/write。bg 管线本来也是关 depth 的;保持两边一致,让"先 bg 再 face"
// 的覆盖顺序由 draw 顺序而非 depth 决定,避免 reversed-depthGREATER+
// renderpass 没显式 clear depth attachment 时引发的"face fragment 全部
// 被 depth test fail"的隐性 bug。
VkPipelineDepthStencilStateCreateInfo depth_stencil_state = {};
depth_stencil_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil_state.depthTestEnable = VK_TRUE;
depth_stencil_state.depthWriteEnable = VK_TRUE;
depth_stencil_state.depthCompareOp = VK_COMPARE_OP_GREATER;
depth_stencil_state.depthTestEnable = VK_FALSE;
depth_stencil_state.depthWriteEnable = VK_FALSE;
depth_stencil_state.depthCompareOp = VK_COMPARE_OP_ALWAYS;
depth_stencil_state.front = depth_stencil_state.back;
depth_stencil_state.back.compareOp = VK_COMPARE_OP_ALWAYS;
@@ -617,6 +632,8 @@ void FaceApp::setup_descriptor_set()
void FaceApp::update_descriptor_set(vector<Texture>& texs, vector<VkDescriptorSet>& descriptSet)
{
FACE_DBG_LOG("update_descriptor_set: texs.size=%zu descriptSet.size=%zu",
texs.size(), descriptSet.size());
for (int i = 0; i < texs.size(); ++i)
{
if (texs[i].image == VK_NULL_HANDLE)
@@ -697,21 +714,84 @@ void FaceApp::render(VkCommandBuffer commandBuffer, long long frameTime)
//Application::render(commandBuffer);
// === 分辨率适配:每帧把"业务参数"缩放到当前屏幕物理像素 ===
// 业务层(MotionManager / InitArg)传进来的 radius / offset_x / offset_y 一直
// 都是按"480x480 设计画布"给的,比如 radius=240 表示在 480 屏上半径 240px
// (即 50% 短边)。现在屏幕变成任意分辨率后,要保持视觉一致:
// 实际像素 = 业务值 * (画布短边 / 480)
// 同时把 canvas/screen 几何写进 push constantshader 用它们:
// 1) 把"画布 NDC ∈ [-1,+1]" 缩到屏幕中央正方形(letterbox);
// 2) 把 fragment 里的圆心从写死的 (240,240) 改成屏幕真实中心。
//
// 注意 pushConstants 本身保留业务原始值不动(SetInitArg 设进来的),
// 这里只把缩放后的副本 pc 推给 GPU。下面 ux/uy 仍然写回 pushConstants
// 是因为它们和分辨率无关,是 motion 帧的纹理坐标。
updateCanvasMetrics();
PushConstants pc = pushConstants;
const float k = m_canvasSize / 480.0f;
pc.radius *= k;
pc.offset_x *= k;
pc.offset_y *= k;
pc.canvas_size = m_canvasSize;
pc.screen_w = m_screenW;
pc.screen_h = m_screenH;
// 相机帧元信息:由 processCameraFrame 在每帧上传纹理之前更新。第一帧之前
// (还没收到相机数据时)m_cameraAspect/m_cameraRotation 是构造函数设的
// 4:3 + 90° 默认值 —— 这两个值正是 CameraX RATIO_4_3 + 大部分 Android 后置
// 主摄竖屏的典型组合,所以即使首帧 bg 用默认值渲染也不会出现明显错位。
pc.camera_aspect = m_cameraAspect;
pc.camera_rotation = m_cameraRotation;
// mirror_x: 前置摄像头时 = 1.0,所有顶点 shader 在最后输出 gl_Position 前
// 把 NDC.x 翻转一次,达到镜子效果(用户右手 → 屏幕左侧)。bg 与 face 都
// 同步翻转,对齐保持。后置时 = 0.0,shader 公式 (1 - 2 * mirror_x) = 1 短路。
pc.mirror_x = m_mirrorX;
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout_bg, 0, 1, &m_descriptor_set_bg, 0, NULL);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_graphicsPipeline_bg);
vkCmdPushConstants(commandBuffer, m_pipelineLayout_bg, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushConstants), &pushConstants);
// bg.frag 也读 push constantr/g/b/radius/screen_w/screen_h)所以 stage 必须
// 同时包含 fragment;老代码这里只写了 VERTEX_BIT,是个隐藏的 spec 违规。
vkCmdPushConstants(commandBuffer, m_pipelineLayout_bg, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc);
vkCmdDraw(commandBuffer, 6, 1, 0, 0);
// 诊断:把 face 渲染所有"前置门"状态打成一行节流日志。一旦 face 没出现,
// 直接看这条就能知道断在哪一关:
// _isChangeMostion=0 → 业务还没调 PlayMotionList / changeMotionList
// _curMotions=0 → motion list 是空的(getMotionByName 全 miss
// _curMotionIndex 越界 → 业务逻辑或 callback 状态机错了
// delta_last_update >= 2000 → mediapipe / passDataToNative 链路 2s 没动静
const long long now_ms = getCurrentTimeMillis();
const long long delta_lu = now_ms - (long long)last_update_time;
FACE_DBG_LOG_THROTTLED("render.face.gate",
"render.face gate _isChangeMostion=%d _curMotions=%zu"
" _curMotionIndex=%d _curFrameIndex=%d delta_lastUpdate=%lldms"
" _playMotion=%d",
(int)_isChangeMostion, _curMotions.size(),
(int)_curMotionIndex, (int)_curFrameIndex,
delta_lu, (int)_playMotion);
if (!_isChangeMostion)
{
return;
}
if (_curMotions.empty())
{
FACE_DBG_LOG_THROTTLED("render.face.emptyMotions",
"render.face dropped: _isChangeMostion=1 but _curMotions is empty");
return;
}
string curMotionName = _curMotions[_curMotionIndex].name;
int loadMotionIndex = motion_list_map[curMotionName];
// face draw 前关键参数节流日志,保留少量"任何时候挂了能立刻判断断点"的字段。
FACE_DBG_LOG_THROTTLED("render.face.preDraw",
"render.face preDraw motion='%s' loadMotionIdx=%d obj_idx=%zu mirror=%.1f",
curMotionName.c_str(), loadMotionIndex, obj_indices.size(), pc.mirror_x);
//if (cur_left)
//{
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descriptor_sets_left[loadMotionIndex], 0, NULL);
@@ -730,12 +810,14 @@ void FaceApp::render(VkCommandBuffer commandBuffer, long long frameTime)
float uy = _curMotions[_curMotionIndex].frames[_curFrameIndex].y;
pushConstants.ux = ux;
pushConstants.uy = uy;
pc.ux = ux;
pc.uy = uy;
}
}
//fsValues[1] = 0;
//fsValues[2] = 360;
vkCmdPushConstants(commandBuffer, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT| VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pushConstants), &pushConstants);
vkCmdPushConstants(commandBuffer, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT| VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc);
//vkCmdPushConstants(commandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(fsValues), fsValues);
@@ -751,7 +833,16 @@ void FaceApp::render(VkCommandBuffer commandBuffer, long long frameTime)
#else
if (getCurrentTimeMillis() - last_update_time < 2000)
{
vkCmdDrawIndexed(commandBuffer, obj_indices.size(), 1, 0, 0, 0);
FACE_DBG_LOG_THROTTLED("render.face.draw",
"render.face vkCmdDrawIndexed indices=%zu motion=%s frame=%d",
obj_indices.size(), curMotionName.c_str(), (int)_curFrameIndex);
vkCmdDrawIndexed(commandBuffer, obj_indices.size(), 1, 0, 0, 0);
}
else
{
FACE_DBG_LOG_THROTTLED("render.face.heartbeatStale",
"render.face SKIP vkCmdDrawIndexed: delta_last_update=%lldms (>= 2000ms)",
(long long)(getCurrentTimeMillis() - last_update_time));
}
#endif
}
@@ -964,11 +1055,14 @@ void FaceApp::recreatePipelinesForSwapchain()
void FaceApp::update_uniform_buffers()
{
uint32_t width = 480;
uint32_t height = 480;
float zoom = 2;
// 注意:当前 vertex shader (texture.vert) 实际并没有使用 ubo.projection 把模型
// 投影到屏幕——它直接拿 inPos.xy*2-1 当 NDC 用。所以下面 aspect 取 1.0 只是
// 为了让 ubo 不再依赖原本写死的 480x480 输入分辨率;要真正影响屏幕显示比例的
// 是 push constant 里的 canvas_size / screen_w / screen_h,由 render() 写入。
const float aspect = 1.0f;
const float zoom = 2.0f;
// Vertex shader
ubo_vs.projection = glm::perspective(glm::radians(60.0f), static_cast<float>(width) / static_cast<float>(height), 0.001f, 256.0f);
ubo_vs.projection = glm::perspective(glm::radians(60.0f), aspect, 0.001f, 256.0f);
glm::mat4 view_matrix = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom));
ubo_vs.model = view_matrix * glm::translate(glm::mat4(1.0f), camera_pos);
@@ -981,6 +1075,26 @@ void FaceApp::update_uniform_buffers()
memcpy(uniform_buffer_mapped, &ubo_vs, sizeof(ubo_vs));
}
void FaceApp::updateCanvasMetrics()
{
// 把"屏幕中央 min(w,h) 的正方形"作为设计画布。画布外的部分会落在
// shader 计算的 NDC ∈ [-1,+1] 之外,被 GPU 自动裁剪为黑色 letterbox。
//
// swapChainExtent 来自 Application::createSwapChain 里的
// surface_capabilities.currentExtent,即当前窗口/Surface 的真实分辨率。
// 任何 swapchain 重建(onWindowLost -> onWindowInit)都会刷新它,所以
// 这里每帧重算是廉价且自洽的。
m_screenW = (float)swapChainExtent.width;
m_screenH = (float)swapChainExtent.height;
m_canvasSize = (m_screenW < m_screenH) ? m_screenW : m_screenH;
if (m_canvasSize <= 0.0f) {
// swapchain 还没初始化 / 已销毁时给个安全值,避免 shader 除零。
m_canvasSize = 480.0f;
m_screenW = 480.0f;
m_screenH = 480.0f;
}
}
void FaceApp::createVertexBuffer()
{
VkDeviceSize vertexBufferSize = sizeof(TextureLoadingVertexStructure) * obj_vertices.size();
@@ -1029,17 +1143,85 @@ void FaceApp::uploadVertexData() {
void FaceApp::processCameraFrame(uint8_t* data, int width, int height,
int rowStride, size_t dataSize, int rotation,
bool mirrorX)
{
if (!isInited() || data == nullptr || width <= 0 || height <= 0) {
return;
}
// 1) rotation 收敛到 0/90/180/270。CameraX 文档保证只会给这四个值,
// 但理论上 ((rotation % 360) + 360) % 360 更鲁棒。
int rotNorm = ((rotation % 360) + 360) % 360;
if (rotNorm != 0 && rotNorm != 90 && rotNorm != 180 && rotNorm != 270) {
// 非法值时保守用 90(与原 hardcode 一致)。
rotNorm = 90;
}
// 2) 元信息更新(无锁单写者:仅相机分析线程写,render 线程读)。
// 放在 sizeChanged 判断前后都安全:render() 每帧重新拷到 push constant。
m_cameraAspect = (float)width / (float)height;
m_cameraRotation = (float)rotNorm;
m_mirrorX = mirrorX ? 1.0f : 0.0f;
// 3) 检查帧尺寸是否与现有 tex_bg 一致。
// 场景:CameraX session 重建(如 Activity onResume)后第一帧分辨率可能
// 不同;或者 setTargetResolution 在不同设备上落地为不同尺寸。
bool sizeChanged = (tex_bg.image != VK_NULL_HANDLE)
&& (tex_bg.width != width || tex_bg.height != height);
if (!sizeChanged) {
// 快路径(每帧):直接走 base 标准上传路径。第一次 image == VK_NULL_HANDLE
// base 会自动 createTexture(width, height, ...) 自适应分辨率;之后每帧
// image 已存在,直接走 staging buffer 拷贝。
processWithVulkan(data, width, height, rowStride, dataSize,
tex_bg, false, commandPool, "data_from_camera");
return;
}
// 慢路径(罕见,整个 session 通常 1 次):尺寸变化 → 重建 image + 刷新
// descriptor set。整个 ceremony 必须严格串行:
// ① createTextureMtx 与 drawFrame / 其它 processWithVulkan 互斥
// FaceApp::drawFrame 进 Application::drawFrame 之前会持此锁,所以
// 我们持锁期间渲染线程被阻塞,不会有新的 cmdbuf 绑定 m_descriptor_set_bg
// 并 submit)。
// ② vkDeviceWaitIdle 等待所有"已 submit 但未完成"的 cmdbuf 跑完,确保
// 销毁旧 image/view 时 GPU 已经不再采样它。
// ③ destroy → create → updateTexture → refresh_descriptor_set_bg
// 全部在锁内完成,期间 m_descriptor_set_bg 处于"指向无效 view"的中间
// 状态,但因为渲染线程被锁阻塞,这个中间状态对 GPU 不可见。
// 注意不能调 base::processWithVulkan,那会再次 lock createTextureMtx 死锁;
// 直接调 createTexture + updateTexture,这两个 base 方法不持 createTextureMtx。
#ifndef _WIN32
DebugLog::log("processCameraFrame: bg size changed %dx%d -> %dx%d, recreate texture",
tex_bg.width, tex_bg.height, width, height);
#endif
std::unique_lock<std::mutex> lk_tex(createTextureMtx);
{
std::lock_guard<std::mutex> lk_pool(poolQueueMtx);
vkDeviceWaitIdle(device);
}
destroyTexture(device, tex_bg);
createTexture(device, physicalDevice, width, height, tex_bg, false,
"data_from_camera", dataSize);
updateTexture(device, physicalDevice, commandPool, graphicsQueue,
data, width, height, rowStride, dataSize, tex_bg);
refresh_descriptor_set_bg();
}
void FaceApp::update_face_vertex_buffer(float* pos, int pointCount)
{
if(!isInited())
{
FACE_DBG_LOG_THROTTLED("update_face_vertex_buffer.notInited",
"update_face_vertex_buffer dropped: !isInited()");
return;
}
std::lock_guard<std::mutex> lock(mtx_point);
last_update_time = getCurrentTimeMillis();
for (int i = 0; i < obj_vertices.size(); ++i)
{
@@ -1251,8 +1433,12 @@ void FaceApp::setup_descriptor_set_layout_bg()
pipeline_layout_create_info.pSetLayouts = &m_descriptorSetLayout_bg;
// bg.frag 同样要读 push constantr/g/b/radius,以及分辨率适配重构后的
// screen_w/screen_h),所以 stage 必须包含 FRAGMENT_BIT。老代码这里只写了
// VERTEX_BIT,按 Vulkan spec 是非法的(验证层会 warning,部分驱动会读到
// 未定义内容),借这次重构修掉。
VkPushConstantRange pushConstantRanges[1];
pushConstantRanges[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
pushConstantRanges[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
pushConstantRanges[0].offset = 0;
pushConstantRanges[0].size = sizeof(PushConstants);
@@ -1273,7 +1459,18 @@ void FaceApp::setup_descriptor_set_bg()
VK_CHECK(vkAllocateDescriptorSets(device, &alloc_info, &m_descriptor_set_bg));
refresh_descriptor_set_bg();
}
void FaceApp::refresh_descriptor_set_bg()
{
if (m_descriptor_set_bg == VK_NULL_HANDLE) {
return;
}
if (tex_bg.view == VK_NULL_HANDLE || tex_bg.sampler == VK_NULL_HANDLE) {
// 还没 loadTexture / processCameraFrame 过,没法绑定。
return;
}
VkDescriptorImageInfo image_descriptor;
image_descriptor.imageView = tex_bg.view;
@@ -1288,9 +1485,7 @@ void FaceApp::setup_descriptor_set_bg()
write_descriptor_set.pImageInfo = &image_descriptor;
write_descriptor_set.descriptorCount = 1;
std::vector<VkWriteDescriptorSet> write_descriptor_sets = { write_descriptor_set };
vkUpdateDescriptorSets(device, static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, NULL);
vkUpdateDescriptorSets(device, 1, &write_descriptor_set, 0, NULL);
}
void FaceApp::destroyTexture(VkDevice device, Texture& texture) {
@@ -1604,6 +1799,7 @@ void FaceApp::loadMotionThread()
string name = m.name;
if (motion_list_map.find(name) != motion_list_map.end())
{
FACE_DBG_LOG("loadMotionThread: skip already-loaded motion='%s'", name.c_str());
continue;
}
m_texs_left.push_back(Texture());
@@ -1620,6 +1816,10 @@ void FaceApp::loadMotionThread()
#endif // _WIN32
motion_list_map[name] = m_texs_left.size() - 1;
_loadMotionMap[name] = m;
FACE_DBG_LOG("loadMotionThread: loaded motion='%s' idx=%zu tex_image=%p tex_view=%p tex_sampler=%p frames=%zu",
name.c_str(), m_texs_left.size() - 1,
(void*)newTex.image, (void*)newTex.view, (void*)newTex.sampler,
m.frames.size());
}
// for (int i = 0; i < _loadMotions.size(); ++i)
@@ -1680,7 +1880,11 @@ void FaceApp::changeMotionList(vector<string> motions, AnimationFinishedCallback
vector<Motion> motion_list;
for (auto ms : motions) {
Motion m = getMotionByName(ms);
motion_list.push_back(m);
auto it = motion_list_map.find(ms);
int idx = (it == motion_list_map.end()) ? -1 : (int)it->second;
FACE_DBG_LOG("changeMotionList: requested='%s' idx=%d frames=%zu (-1=NOT FOUND)",
ms.c_str(), idx, m.frames.size());
motion_list.push_back(m);
}
_curMotions = motion_list;