From: Elizabeth Figura zfigura@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=32572 --- dlls/d3dx9_36/d3dx9_private.h | 2 + dlls/d3dx9_36/mesh.c | 2 +- dlls/d3dx9_36/skin.c | 102 +++++++++++++++++++++++++++++-- dlls/d3dx9_36/tests/mesh.c | 109 ++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 7 deletions(-)
diff --git a/dlls/d3dx9_36/d3dx9_private.h b/dlls/d3dx9_36/d3dx9_private.h index 655529b004b..a52636298be 100644 --- a/dlls/d3dx9_36/d3dx9_private.h +++ b/dlls/d3dx9_36/d3dx9_private.h @@ -347,6 +347,8 @@ struct d3dx_parameters_store unsigned int full_name_tmp_size; };
+extern const unsigned int d3dx_decltype_size[]; + HRESULT d3dx_init_parameters_store(struct d3dx_parameters_store *store, unsigned int count); void d3dx_parameters_store_cleanup(struct d3dx_parameters_store *store); struct d3dx_parameter *get_parameter_by_name(struct d3dx_parameters_store *store, diff --git a/dlls/d3dx9_36/mesh.c b/dlls/d3dx9_36/mesh.c index 23ab7b59708..e425ef40f4b 100644 --- a/dlls/d3dx9_36/mesh.c +++ b/dlls/d3dx9_36/mesh.c @@ -64,7 +64,7 @@ struct d3dx9_mesh D3DXATTRIBUTERANGE *attrib_table; };
-static const UINT d3dx_decltype_size[] = +const unsigned int d3dx_decltype_size[] = { /* D3DDECLTYPE_FLOAT1 */ sizeof(FLOAT), /* D3DDECLTYPE_FLOAT2 */ sizeof(D3DXVECTOR2), diff --git a/dlls/d3dx9_36/skin.c b/dlls/d3dx9_36/skin.c index 294c652c273..ffd8783b7a2 100644 --- a/dlls/d3dx9_36/skin.c +++ b/dlls/d3dx9_36/skin.c @@ -2,6 +2,7 @@ * Skin Info operations specific to D3DX9. * * Copyright (C) 2011 Dylan Smith + * Copyright (C) 2013 Christian Costa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -39,7 +40,7 @@ struct d3dx9_skin_info
DWORD fvf; D3DVERTEXELEMENT9 vertex_declaration[MAX_FVF_DECL_SIZE]; - DWORD num_vertices; + DWORD vertex_count; DWORD num_bones; struct bone *bones; }; @@ -301,7 +302,7 @@ static HRESULT WINAPI d3dx9_skin_info_Clone(ID3DXSkinInfo *iface, ID3DXSkinInfo
TRACE("iface %p, skin_info %p.\n", iface, skin_info);
- if (FAILED(hr = D3DXCreateSkinInfo(skin->num_vertices, skin->vertex_declaration, skin->num_bones, skin_info))) + if (FAILED(hr = D3DXCreateSkinInfo(skin->vertex_count, skin->vertex_declaration, skin->num_bones, skin_info))) return hr;
for (i = 0; i < skin->num_bones; ++i) @@ -396,10 +397,99 @@ static HRESULT WINAPI d3dx9_skin_info_GetDeclaration(ID3DXSkinInfo *iface, static HRESULT WINAPI d3dx9_skin_info_UpdateSkinnedMesh(ID3DXSkinInfo *iface, const D3DXMATRIX *bone_transforms, const D3DXMATRIX *bone_inv_transpose_transforms, const void *src_vertices, void *dst_vertices) { - FIXME("iface %p, bone_transforms %p, bone_inv_transpose_transforms %p, src_vertices %p, dst_vertices %p stub!\n", - iface, bone_transforms, bone_inv_transpose_transforms, src_vertices, dst_vertices); + struct d3dx9_skin_info *skin = impl_from_ID3DXSkinInfo(iface); + DWORD vertex_size = D3DXGetDeclVertexSize(skin->vertex_declaration, 0);
- return E_NOTIMPL; + TRACE("iface %p, bone_transforms %p, bone_inv_transpose_transforms %p, src_vertices %p, dst_vertices %p.\n", + skin, bone_transforms, bone_inv_transpose_transforms, src_vertices, dst_vertices); + + if (bone_inv_transpose_transforms) + { + FIXME("Skinning vertices with two position elements is not supported.\n"); + return E_NOTIMPL; + } + + for (unsigned int i = 0; i < skin->vertex_count; ++i) + { + for (const D3DVERTEXELEMENT9 *element = skin->vertex_declaration; element->Stream != 0xff; ++element) + { + const void *src_element = (uint8_t *)src_vertices + (i * vertex_size) + element->Offset; + void *dst_element = (uint8_t *)dst_vertices + (i * vertex_size) + element->Offset; + unsigned int element_size = d3dx_decltype_size[element->Type]; + + switch (element->Usage) + { + case D3DDECLUSAGE_POSITION: + case D3DDECLUSAGE_NORMAL: + memset(dst_element, 0, element_size); + break; + + default: + FIXME("Unhandled usage %#x.\n", element->Usage); + /* fall through */ + case D3DDECLUSAGE_TEXCOORD: + memcpy(dst_element, src_element, element_size); + break; + } + } + } + + for (unsigned int i = 0; i < skin->num_bones; ++i) + { + const struct bone *bone = &skin->bones[i]; + + for (unsigned int j = 0; j < bone->num_influences; ++j) + { + unsigned int vertex_idx = bone->vertices[j]; + float weight = bone->weights[j]; + + for (const D3DVERTEXELEMENT9 *element = skin->vertex_declaration; element->Stream != 0xff; ++element) + { + const void *src_element = (uint8_t *)src_vertices + (vertex_idx * vertex_size) + element->Offset; + void *dst_element = (uint8_t *)dst_vertices + (vertex_idx * vertex_size) + element->Offset; + D3DXVECTOR3 *dst_vec3 = dst_element; + + switch (element->Usage) + { + case D3DDECLUSAGE_POSITION: + if (element->Type == D3DDECLTYPE_FLOAT3) + { + D3DXVECTOR3 position; + + D3DXVec3TransformCoord(&position, src_element, &bone_transforms[i]); + dst_vec3->x += weight * position.x; + dst_vec3->y += weight * position.y; + dst_vec3->z += weight * position.z; + } + else + { + FIXME("Unhandled position type %#x.\n", element->Type); + return E_NOTIMPL; + } + break; + + case D3DDECLUSAGE_NORMAL: + { + D3DXVECTOR3 normal; + + if (element->Type != D3DDECLTYPE_FLOAT3) + { + FIXME("Unhandled normal type %#x.\n", element->Type); + return E_NOTIMPL; + } + + D3DXVec3TransformNormal(&normal, src_element, &bone_transforms[i]); + dst_vec3->x += weight * normal.x; + dst_vec3->y += weight * normal.y; + dst_vec3->z += weight * normal.z; + break; + } + } + } + } + } + + return D3D_OK; }
static HRESULT WINAPI d3dx9_skin_info_ConvertToBlendedMesh(ID3DXSkinInfo *iface, ID3DXMesh *mesh_in, @@ -479,7 +569,7 @@ HRESULT WINAPI D3DXCreateSkinInfo(DWORD vertex_count, const D3DVERTEXELEMENT9 *d
object->ID3DXSkinInfo_iface.lpVtbl = &d3dx9_skin_info_vtbl; object->ref = 1; - object->num_vertices = vertex_count; + object->vertex_count = vertex_count; object->num_bones = bone_count; object->vertex_declaration[0] = empty_declaration; object->fvf = 0; diff --git a/dlls/d3dx9_36/tests/mesh.c b/dlls/d3dx9_36/tests/mesh.c index 23597dc2d56..8dcf5075466 100644 --- a/dlls/d3dx9_36/tests/mesh.c +++ b/dlls/d3dx9_36/tests/mesh.c @@ -52,6 +52,11 @@ static BOOL compare(FLOAT u, FLOAT v) return (fabs(u-v) < admitted_error); }
+static BOOL compare_vec2(D3DXVECTOR2 u, D3DXVECTOR2 v) +{ + return compare(u.x, v.x) && compare(u.y, v.y); +} + static BOOL compare_vec3(D3DXVECTOR3 u, D3DXVECTOR3 v) { return ( compare(u.x, v.x) && compare(u.y, v.y) && compare(u.z, v.z) ); @@ -5460,6 +5465,109 @@ static void test_create_skin_info(void) ok(hr == D3DERR_INVALIDCALL, "Expected D3DERR_INVALIDCALL, got %#lx\n", hr); }
+static void test_update_skinned_mesh(void) +{ + static const float bone0_weights[2] = {1.0f, 0.5f}, bone1_weights[2] = {1.0f, 0.5f}; + static const DWORD bone0_vertices[2] = {1, 3}, bone1_vertices[2] = {2, 3}; + static const D3DXMATRIX bone_matrices[2] = + { + {{{ + 2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.1f, 0.2f, 0.3f, 1.0f, + }}}, + {{{ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.4f, 0.5f, 1.0f, + }}}, + }; + static const D3DXMATRIX update_matrices[2] = + { + {{{ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 2.0f, 2.0f, 4.0f, 1.0f, + }}}, + {{{ + 2.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -4.0f, -4.0f, 4.0f, 1.0f, + }}}, + }; + + static const struct vertex + { + D3DXVECTOR3 position; + D3DXVECTOR3 normal; + D3DXVECTOR2 texcoord; + } + src_vertices[4] = + { + {{ 1.0f, 1.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}, { 0.2f, 0.2f}}, + {{ 1.0f, 1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}, { 0.2f, 0.4f}}, + {{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, { 0.4f, 0.2f}}, + {{-1.0f, -1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}, { 0.4f, 0.4f}}, + }, + expect_vertices[4] = + { + {{ 0.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 0.0f}, { 0.2f, 0.2f}}, + {{ 3.0f, 3.0f, 3.0f}, { 0.0f, 1.0f, 0.0f}, { 0.2f, 0.4f}}, + {{-6.0f, -5.0f, 5.0f}, { 0.0f, 0.0f, 1.0f}, { 0.4f, 0.2f}}, + {{-2.5f, -2.0f, 3.0f}, {-1.5f, 0.0f, 0.0f}, { 0.4f, 0.4f}}, + }; + + struct vertex dst_vertices[4]; + ID3DXSkinInfo *skin_info; + HRESULT hr; + + static const D3DVERTEXELEMENT9 decl_elements[] = + { + {0, 0, D3DDECLTYPE_FLOAT3, 0, D3DDECLUSAGE_POSITION, 0}, + {0, 12, D3DDECLTYPE_FLOAT3, 0, D3DDECLUSAGE_NORMAL, 0}, + {0, 24, D3DDECLTYPE_FLOAT2, 0, D3DDECLUSAGE_TEXCOORD, 0}, + D3DDECL_END() + }; + + memset(dst_vertices, 0xcc, sizeof(dst_vertices)); + + hr = D3DXCreateSkinInfo(4, decl_elements, 2, &skin_info); + ok(hr == D3D_OK, "Got hr %#lx.\n", hr); + + skin_info->lpVtbl->SetBoneInfluence(skin_info, 0, 2, bone0_vertices, bone0_weights); + ok(hr == D3D_OK, "Got hr %#lx.\n", hr); + skin_info->lpVtbl->SetBoneOffsetMatrix(skin_info, 0, &bone_matrices[0]); + ok(hr == D3D_OK, "Got hr %#lx.\n", hr); + skin_info->lpVtbl->SetBoneInfluence(skin_info, 1, 2, bone1_vertices, bone1_weights); + ok(hr == D3D_OK, "Got hr %#lx.\n", hr); + skin_info->lpVtbl->SetBoneOffsetMatrix(skin_info, 1, &bone_matrices[1]); + ok(hr == D3D_OK, "Got hr %#lx.\n", hr); + skin_info->lpVtbl->UpdateSkinnedMesh(skin_info, update_matrices, NULL, src_vertices, dst_vertices); + ok(hr == D3D_OK, "Got hr %#lx.\n", hr); + for (unsigned int i = 0; i < 4; ++i) + { + winetest_push_context("vertex %u", i); + ok(compare_vec3(dst_vertices[i].position, expect_vertices[i].position), + "Expected position (%.8e, %.8e, %.8e), got (%.8e, %.8e, %.8e).\n", + expect_vertices[i].position.x, expect_vertices[i].position.y, expect_vertices[i].position.z, + dst_vertices[i].position.x, dst_vertices[i].position.y, dst_vertices[i].position.z); + ok(compare_vec3(dst_vertices[i].normal, expect_vertices[i].normal), + "Expected normal (%.8e, %.8e, %.8e), got (%.8e, %.8e, %.8e).\n", + expect_vertices[i].normal.x, expect_vertices[i].normal.y, expect_vertices[i].normal.z, + dst_vertices[i].normal.x, dst_vertices[i].normal.y, dst_vertices[i].normal.z); + ok(compare_vec2(dst_vertices[i].texcoord, expect_vertices[i].texcoord), + "Expected texcoord (%.8e, %.8e), got (%.8e, %.8e).\n", + expect_vertices[i].texcoord.x, expect_vertices[i].texcoord.y, + dst_vertices[i].texcoord.x, dst_vertices[i].texcoord.y); + winetest_pop_context(); + } + skin_info->lpVtbl->Release(skin_info); +} + static void test_convert_adjacency_to_point_reps(void) { HRESULT hr; @@ -11854,6 +11962,7 @@ START_TEST(mesh) D3DXGenerateAdjacencyTest(); test_update_semantics(); test_create_skin_info(); + test_update_skinned_mesh(); test_convert_adjacency_to_point_reps(); test_convert_point_reps_to_adjacency(); test_weld_vertices();
This merge request was approved by Elizabeth Figura.
Matteo Bruni (@Mystral) commented about dlls/d3dx9_36/skin.c:
- Skin Info operations specific to D3DX9.
- Copyright (C) 2011 Dylan Smith
- Copyright (C) 2013 Christian Costa
Did you perhaps mean to set this commit's author to Christian? Otherwise, according to git, there are just two (existing) lines in the file attributed to him.
Matteo Bruni (@Mystral) commented about dlls/d3dx9_36/skin.c:
const D3DXMATRIX *bone_inv_transpose_transforms, const void *src_vertices, void *dst_vertices)
{
- FIXME("iface %p, bone_transforms %p, bone_inv_transpose_transforms %p, src_vertices %p, dst_vertices %p stub!\n",
iface, bone_transforms, bone_inv_transpose_transforms, src_vertices, dst_vertices);
- struct d3dx9_skin_info *skin = impl_from_ID3DXSkinInfo(iface);
- DWORD vertex_size = D3DXGetDeclVertexSize(skin->vertex_declaration, 0);
- return E_NOTIMPL;
- TRACE("iface %p, bone_transforms %p, bone_inv_transpose_transforms %p, src_vertices %p, dst_vertices %p.\n",
skin, bone_transforms, bone_inv_transpose_transforms, src_vertices, dst_vertices);
- if (bone_inv_transpose_transforms)
- {
FIXME("Skinning vertices with two position elements is not supported.\n");
return E_NOTIMPL;
- }
Pretty sure that's not what the inverse of the transpose is for, regardless of what the documentation says.
Usually you want to use the inverse of the transpose of a transformation matrix specifically for normals, to make sure that they remain orthogonal with the transformed geometry. Not sure what's up with vertices with two position elements (if supported at all?), I guess it's possible that the comment from https://learn.microsoft.com/en-us/windows/win32/direct3d9/id3dxskininfo--upd... is also correct.
I tweaked a bit the test ([usm-test.txt](/uploads/27c8e1a1fed580424ee913b067bdda89/usm-test.txt)) to check my hunch and at least it looks like the inverse transpose matrix is used for normals when present.
Matteo Bruni (@Mystral) commented about dlls/d3dx9_36/skin.c:
memcpy(dst_element, src_element, element_size);
break;
}
}
- }
- for (unsigned int i = 0; i < skin->num_bones; ++i)
- {
const struct bone *bone = &skin->bones[i];
for (unsigned int j = 0; j < bone->num_influences; ++j)
{
unsigned int vertex_idx = bone->vertices[j];
float weight = bone->weights[j];
for (const D3DVERTEXELEMENT9 *element = skin->vertex_declaration; element->Stream != 0xff; ++element)
This might be a bit of an unimportant nitpick, but I'd rather make this loop the outermost and avoid empty spinning bone_count * vertices_count times for each vertex element that's not position or normal.
On Mon Sep 29 17:41:27 2025 +0000, Matteo Bruni wrote:
Did you perhaps mean to set this commit's author to Christian? Otherwise, according to git, there are just two (existing) lines in the file attributed to him.
Ugh, yes, I don't know how it even got reset honestly; it should have been applied with git-am in the first place. I did make quite a few changes but the fundamental code is probably still his; I certainly would have had a hard time figuring out what to do without it.
On Mon Sep 29 17:41:27 2025 +0000, Matteo Bruni wrote:
Pretty sure that's not what the inverse of the transpose is for, regardless of what the documentation says. Usually you want to use the inverse of the transpose of a transformation matrix specifically for normals, to make sure that they remain orthogonal with the transformed geometry. Not sure what's up with vertices with two position elements (if supported at all?), I guess it's possible that the comment from https://learn.microsoft.com/en-us/windows/win32/direct3d9/id3dxskininfo--upd... is also correct. I tweaked a bit the test ([usm-test.txt](/uploads/27c8e1a1fed580424ee913b067bdda89/usm-test.txt)) to check my hunch and at least it looks like the inverse transpose matrix is used for normals when present.
Makes sense, thanks. The application in question doesn't need that functionality, so I don't think it's worth implementing, but I'll change the message accordingly.
On Mon Sep 29 17:41:27 2025 +0000, Matteo Bruni wrote:
This might be a bit of an unimportant nitpick, but I'd rather make this loop the outermost and avoid empty spinning bone_count * vertices_count times for each vertex element that's not position or normal.
Yeah, that makes more sense >_<