diff --git a/Editor/MeshWindow.cpp b/Editor/MeshWindow.cpp index 47b1eca66..5fa8dee8a 100644 --- a/Editor/MeshWindow.cpp +++ b/Editor/MeshWindow.cpp @@ -554,7 +554,6 @@ void MeshWindow::Create(EditorComponent* _editor) if (mesh != nullptr && morphTargetCombo.GetSelected() < (int)mesh->morph_targets.size()) { mesh->morph_targets[morphTargetCombo.GetSelected()].weight = args.fValue; - mesh->dirty_morph = true; } }); AddWidget(&morphTargetSlider); diff --git a/WickedEngine/shaders/ShaderInterop_Renderer.h b/WickedEngine/shaders/ShaderInterop_Renderer.h index e35828f1f..fed01f01d 100644 --- a/WickedEngine/shaders/ShaderInterop_Renderer.h +++ b/WickedEngine/shaders/ShaderInterop_Renderer.h @@ -1145,13 +1145,27 @@ CBUFFER(PaintRadiusCB, CBSLOT_RENDERER_MISC) struct SkinningPushConstants { - int bonebuffer_index; + uint vertexCount; int vb_pos_nor_wind; int vb_tan; - int vb_bon; - int so_pos_nor_wind; + int so_tan; + int bonebuffer_index; + int vb_bon; + int morphbuffer_index; + + uint morphbuffer_offset; + uint morph_count; + int morphvb_index; +}; + +struct MorphTargetGPU +{ + float weight; + uint offset_pos; + uint offset_nor; + uint offset_tan; }; struct AerialPerspectiveCapturePushConstants diff --git a/WickedEngine/shaders/skinningCS.hlsl b/WickedEngine/shaders/skinningCS.hlsl index 6f53c23df..83f3ba9ea 100644 --- a/WickedEngine/shaders/skinningCS.hlsl +++ b/WickedEngine/shaders/skinningCS.hlsl @@ -2,46 +2,18 @@ PUSHCONSTANT(push, SkinningPushConstants); -inline void Skinning(inout float3 pos, inout float3 nor, inout float3 tan, in uint4 inBon, in float4 inWei) -{ - if (any(inWei)) - { - float4 p = 0; - float3 n = 0; - float3 t = 0; - float weisum = 0; - - // force loop to reduce register pressure - // also enabled early-exit - [loop] - for (uint i = 0; ((i < 4) && (weisum < 1.0f)); ++i) - { - float4x4 m = bindless_buffers[push.bonebuffer_index].Load(inBon[i] * sizeof(ShaderTransform)).GetMatrix(); - - p += mul(m, float4(pos.xyz, 1)) * inWei[i]; - n += mul((float3x3)m, nor.xyz) * inWei[i]; - t += mul((float3x3)m, tan.xyz) * inWei[i]; - - weisum += inWei[i]; - } - - pos.xyz = p.xyz; - nor.xyz = normalize(n.xyz); - tan.xyz = normalize(t.xyz); - } -} - - [numthreads(64, 1, 1)] void main(uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID) { + const uint vertexID = DTid.x; + [branch] - if (push.vb_pos_nor_wind < 0) + if (push.vb_pos_nor_wind < 0 || vertexID >= push.vertexCount) return; - const uint fetchAddress_POS_NOR = DTid.x * sizeof(float4); - const uint fetchAddress_TAN = DTid.x * sizeof(uint); - const uint fetchAddress_BON = DTid.x * sizeof(uint4); + const uint fetchAddress_POS_NOR = vertexID * sizeof(float4); + const uint fetchAddress_TAN = vertexID * sizeof(uint); + const uint fetchAddress_BON = vertexID * sizeof(uint4); // Manual type-conversion for pos: uint4 pos_nor_u = bindless_buffers[push.vb_pos_nor_wind].Load4(fetchAddress_POS_NOR); @@ -68,28 +40,74 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID) tan.w = (float)((vtan >> 24) & 0x000000FF) / 255.0f * 2.0f - 1.0f; } - float4 ind = 0; - float4 wei = 0; + // Morph targets: [branch] - if (push.vb_bon >= 0) + if (push.morph_count > 0) { - // Manual type-conversion for bone props: - uint4 ind_wei_u = bindless_buffers[push.vb_bon].Load4(fetchAddress_BON); - - ind.x = (ind_wei_u.x >> 0) & 0x0000FFFF; - ind.y = (ind_wei_u.x >> 16) & 0x0000FFFF; - ind.z = (ind_wei_u.y >> 0) & 0x0000FFFF; - ind.w = (ind_wei_u.y >> 16) & 0x0000FFFF; - - wei.x = (float)((ind_wei_u.z >> 0) & 0x0000FFFF) / 65535.0f; - wei.y = (float)((ind_wei_u.z >> 16) & 0x0000FFFF) / 65535.0f; - wei.z = (float)((ind_wei_u.w >> 0) & 0x0000FFFF) / 65535.0f; - wei.w = (float)((ind_wei_u.w >> 16) & 0x0000FFFF) / 65535.0f; + ByteAddressBuffer morphbuffer = bindless_buffers[push.morphbuffer_index]; + ByteAddressBuffer morphvb = bindless_buffers[push.morphvb_index]; + for (uint morph_index = 0; morph_index < push.morph_count; ++morph_index) + { + MorphTargetGPU morph = morphbuffer.Load(push.morphbuffer_offset + morph_index * sizeof(MorphTargetGPU)); + if (morph.offset_pos != ~0u) + { + pos += unpack_half3(morphvb.Load(morph.offset_pos + vertexID * sizeof(uint2))) * morph.weight; + } + if (morph.offset_nor != ~0u) + { + nor.xyz += unpack_half3(morphvb.Load(morph.offset_nor + vertexID * sizeof(uint2))) * morph.weight; + } + } } + // Skinning: + [branch] + if (push.bonebuffer_index >= 0) + { + float4 ind = 0; + float4 wei = 0; + [branch] + if (push.vb_bon >= 0) + { + // Manual type-conversion for bone props: + uint4 ind_wei_u = bindless_buffers[push.vb_bon].Load4(fetchAddress_BON); - // Perform skinning: - Skinning(pos, nor.xyz, tan.xyz, ind, wei); + ind.x = (ind_wei_u.x >> 0) & 0x0000FFFF; + ind.y = (ind_wei_u.x >> 16) & 0x0000FFFF; + ind.z = (ind_wei_u.y >> 0) & 0x0000FFFF; + ind.w = (ind_wei_u.y >> 16) & 0x0000FFFF; + + wei.x = (float)((ind_wei_u.z >> 0) & 0x0000FFFF) / 65535.0f; + wei.y = (float)((ind_wei_u.z >> 16) & 0x0000FFFF) / 65535.0f; + wei.z = (float)((ind_wei_u.w >> 0) & 0x0000FFFF) / 65535.0f; + wei.w = (float)((ind_wei_u.w >> 16) & 0x0000FFFF) / 65535.0f; + } + if (any(wei)) + { + float4 p = 0; + float3 n = 0; + float3 t = 0; + float weisum = 0; + + // force loop to reduce register pressure + // also enabled early-exit + [loop] + for (uint i = 0; ((i < 4) && (weisum < 1.0f)); ++i) + { + float4x4 m = bindless_buffers[push.bonebuffer_index].Load(ind[i] * sizeof(ShaderTransform)).GetMatrix(); + + p += mul(m, float4(pos.xyz, 1)) * wei[i]; + n += mul((float3x3)m, nor.xyz) * wei[i]; + t += mul((float3x3)m, tan.xyz) * wei[i]; + + weisum += wei[i]; + } + + pos.xyz = p.xyz; + nor.xyz = normalize(n.xyz); + tan.xyz = normalize(t.xyz); + } + } // Store data: [branch] diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index e560c5d5e..8439541fa 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -4129,42 +4129,15 @@ void UpdateRenderData( wi::profiler::EndRange(range); } - device->EventBegin("Morph Targets", cmd); + device->EventBegin("Skinning and Morph", cmd); { - auto range = wi::profiler::BeginRangeGPU("Morph Targets", cmd); - bool morphs = false; - for (size_t i = 0; i < vis.scene->meshes.GetCount(); ++i) - { - const MeshComponent& mesh = vis.scene->meshes[i]; - - if (mesh.dirty_morph && !mesh.vertex_positions_morphed.empty()) - { - morphs = true; - mesh.dirty_morph = false; - GraphicsDevice::GPUAllocation allocation = device->AllocateGPU(mesh.vb_pos_nor_wind.size, cmd); - std::memcpy(allocation.data, mesh.vertex_positions_morphed.data(), mesh.vb_pos_nor_wind.size); - device->CopyBuffer(&mesh.generalBuffer, mesh.vb_pos_nor_wind.offset, &allocation.buffer, allocation.offset, mesh.vb_pos_nor_wind.size, cmd); - barrier_stack.push_back(GPUBarrier::Buffer(&mesh.generalBuffer, ResourceState::COPY_DST, ResourceState::SHADER_RESOURCE)); - } - } - if (morphs) - { - barrier_stack_flush(cmd); - } - - wi::profiler::EndRange(range); // Morph Targets - } - device->EventEnd(cmd); - - device->EventBegin("Skinning", cmd); - { - auto range = wi::profiler::BeginRangeGPU("Skinning", cmd); + auto range = wi::profiler::BeginRangeGPU("Skinning and Morph", cmd); for (size_t i = 0; i < vis.scene->meshes.GetCount(); ++i) { Entity entity = vis.scene->meshes.GetEntity(i); const MeshComponent& mesh = vis.scene->meshes[i]; - if (mesh.IsSkinned() && vis.scene->armatures.Contains(mesh.armatureID)) + if (mesh.IsSkinned() || mesh.active_morph_count > 0) { const SoftBodyPhysicsComponent* softbody = vis.scene->softbodies.GetComponent(entity); if (softbody != nullptr && softbody->physicsobject != nullptr) @@ -4174,17 +4147,54 @@ void UpdateRenderData( continue; } - const ArmatureComponent& armature = *vis.scene->armatures.GetComponent(mesh.armatureID); device->BindComputeShader(&shaders[CSTYPE_SKINNING], cmd); SkinningPushConstants push; - push.bonebuffer_index = armature.descriptor_srv; + push.vertexCount = (uint)mesh.vertex_positions.size(); push.vb_pos_nor_wind = mesh.vb_pos_nor_wind.descriptor_srv; push.vb_tan = mesh.vb_tan.descriptor_srv; - push.vb_bon = mesh.vb_bon.descriptor_srv; push.so_pos_nor_wind = mesh.so_pos_nor_wind.descriptor_uav; push.so_tan = mesh.so_tan.descriptor_uav; + const ArmatureComponent* armature = vis.scene->armatures.GetComponent(mesh.armatureID); + if (armature != nullptr) + { + push.bonebuffer_index = armature->descriptor_srv; + } + else + { + push.bonebuffer_index = -1; + } + push.vb_bon = mesh.vb_bon.descriptor_srv; + if (mesh.active_morph_count > 0) + { + GraphicsDevice::GPUAllocation allocation = device->AllocateGPU(sizeof(MorphTargetGPU) * mesh.active_morph_count, cmd); + uint active_morph_count = 0; + for(const MeshComponent::MorphTarget& morph : mesh.morph_targets) + { + if (morph.weight > 0) + { + MorphTargetGPU morph_target_gpu = {}; + morph_target_gpu.weight = morph.weight; + morph_target_gpu.offset_pos = (uint)morph.offset_pos; + morph_target_gpu.offset_nor = (uint)morph.offset_nor; + morph_target_gpu.offset_tan = ~0u; + std::memcpy((MorphTargetGPU*)allocation.data + active_morph_count, &morph_target_gpu, sizeof(morph_target_gpu)); + active_morph_count++; + } + } + push.morph_count = active_morph_count; + push.morphbuffer_index = device->GetDescriptorIndex(&allocation.buffer, SubresourceType::SRV); + push.morphbuffer_offset = (uint)allocation.offset; + push.morphvb_index = device->GetDescriptorIndex(&mesh.generalBuffer, SubresourceType::SRV); + } + else + { + push.morph_count = 0; + push.morphbuffer_index = -1; + push.morphbuffer_offset = 0; + push.morphvb_index = -1; + } device->PushConstants(&push, sizeof(push), cmd); device->Dispatch(((uint32_t)mesh.vertex_positions.size() + 63) / 64, 1, 1, cmd); @@ -4193,9 +4203,9 @@ void UpdateRenderData( } } - wi::profiler::EndRange(range); // skinning + wi::profiler::EndRange(range); // Skinning and Morph } - device->EventEnd(cmd); + device->EventEnd(cmd); // Skinning and Morph barrier_stack_flush(cmd); // wind/skinning flush diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index ca75c6044..5af99ebf0 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -1951,8 +1951,6 @@ namespace wi::scene { target_mesh->morph_targets[j].weight = wi::math::Lerp(target_mesh->morph_targets[j].weight, animation.morph_weights_temp[j], t); } - - target_mesh->dirty_morph = true; } if (target_light != nullptr) @@ -2413,7 +2411,6 @@ namespace wi::scene { MeshComponent::MorphTarget& morph_target = mesh->morph_targets[morph_target_binding.index]; morph_target.weight = wi::math::Lerp(morph_target.weight, morph_target_binding.weight, blend); - mesh->dirty_morph = true; } } } @@ -3097,84 +3094,11 @@ namespace wi::scene mesh._flags &= ~MeshComponent::TLAS_FORCE_DOUBLE_SIDED; - if (!mesh.morph_targets.empty() && mesh.vertex_positions_morphed.size() != mesh.vertex_positions.size()) + mesh.active_morph_count = 0; + for (const MeshComponent::MorphTarget& morph : mesh.morph_targets) { - mesh.vertex_positions_morphed.resize(mesh.vertex_positions.size()); - mesh.dirty_morph = true; - } - - // Update morph targets if needed: - if (mesh.dirty_morph && !mesh.morph_targets.empty()) - { - XMFLOAT3 _min = XMFLOAT3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()); - XMFLOAT3 _max = XMFLOAT3(std::numeric_limits::lowest(), std::numeric_limits::lowest(), std::numeric_limits::lowest()); - - mesh.morph_temp_pos = mesh.vertex_positions; - mesh.morph_temp_nor = mesh.vertex_normals; - - for (const MeshComponent::MorphTarget& morph : mesh.morph_targets) - { - if (morph.weight <= 0) - continue; - if (morph.sparse_indices_positions.empty()) - { - // Flat update: - for (size_t i = 0; i < morph.vertex_positions.size(); ++i) - { - mesh.morph_temp_pos[i].x += morph.weight * morph.vertex_positions[i].x; - mesh.morph_temp_pos[i].y += morph.weight * morph.vertex_positions[i].y; - mesh.morph_temp_pos[i].z += morph.weight * morph.vertex_positions[i].z; - } - } - else - { - // Sparse update: - for (size_t i = 0; i < morph.sparse_indices_positions.size(); ++i) - { - const uint32_t ind = morph.sparse_indices_positions[i]; - mesh.morph_temp_pos[ind].x += morph.weight * morph.vertex_positions[i].x; - mesh.morph_temp_pos[ind].y += morph.weight * morph.vertex_positions[i].y; - mesh.morph_temp_pos[ind].z += morph.weight * morph.vertex_positions[i].z; - } - } - - if (morph.sparse_indices_normals.empty()) - { - // Flat update: - for (size_t i = 0; i < morph.vertex_normals.size(); ++i) - { - mesh.morph_temp_nor[i].x += morph.weight * morph.vertex_normals[i].x; - mesh.morph_temp_nor[i].y += morph.weight * morph.vertex_normals[i].y; - mesh.morph_temp_nor[i].z += morph.weight * morph.vertex_normals[i].z; - } - } - else - { - // Sparse update: - for (size_t i = 0; i < morph.sparse_indices_normals.size(); ++i) - { - const uint32_t ind = morph.sparse_indices_normals[i]; - mesh.morph_temp_nor[ind].x += morph.weight * morph.vertex_normals[i].x; - mesh.morph_temp_nor[ind].y += morph.weight * morph.vertex_normals[i].y; - mesh.morph_temp_nor[ind].z += morph.weight * morph.vertex_normals[i].z; - } - } - } - - for (size_t i = 0; i < mesh.morph_temp_pos.size(); ++i) - { - XMFLOAT3 pos = mesh.morph_temp_pos[i]; - XMFLOAT3 nor = mesh.morph_temp_nor.empty() ? XMFLOAT3(1, 1, 1) : mesh.morph_temp_nor[i]; - const uint8_t wind = mesh.vertex_windweights.empty() ? 0xFF : mesh.vertex_windweights[i]; - - XMStoreFloat3(&nor, XMVector3Normalize(XMLoadFloat3(&nor))); - mesh.vertex_positions_morphed[i].FromFULL(pos, nor, wind); - - _min = wi::math::Min(_min, pos); - _max = wi::math::Max(_max, pos); - } - - mesh.aabb = AABB(_min, _max); + if (morph.weight > 0) + mesh.active_morph_count++; } if (geometryArrayMapped != nullptr) @@ -3274,7 +3198,7 @@ namespace wi::scene { geometry.flags = RaytracingAccelerationStructureDesc::BottomLevel::Geometry::FLAG_OPAQUE; } - if (flags != geometry.flags || mesh.dirty_morph) + if (flags != geometry.flags || mesh.active_morph_count > 0) { mesh.BLAS_state = MeshComponent::BLAS_STATE_NEEDS_REBUILD; } @@ -4503,18 +4427,9 @@ namespace wi::scene { if (jobData.armature == nullptr || jobData.armature->boneData.empty()) { - if (jobData.mesh->vertex_positions_morphed.empty()) - { - p0 = XMLoadFloat3(&jobData.mesh->vertex_positions[i0]); - p1 = XMLoadFloat3(&jobData.mesh->vertex_positions[i1]); - p2 = XMLoadFloat3(&jobData.mesh->vertex_positions[i2]); - } - else - { - p0 = jobData.mesh->vertex_positions_morphed[i0].LoadPOS(); - p1 = jobData.mesh->vertex_positions_morphed[i1].LoadPOS(); - p2 = jobData.mesh->vertex_positions_morphed[i2].LoadPOS(); - } + p0 = XMLoadFloat3(&jobData.mesh->vertex_positions[i0]); + p1 = XMLoadFloat3(&jobData.mesh->vertex_positions[i1]); + p2 = XMLoadFloat3(&jobData.mesh->vertex_positions[i2]); } else { @@ -5296,15 +5211,7 @@ namespace wi::scene XMVECTOR SkinVertex(const MeshComponent& mesh, const ArmatureComponent& armature, uint32_t index, XMVECTOR* N) { - XMVECTOR P; - if (mesh.vertex_positions_morphed.empty()) - { - P = XMLoadFloat3(&mesh.vertex_positions[index]); - } - else - { - P = mesh.vertex_positions_morphed[index].LoadPOS(); - } + XMVECTOR P = XMLoadFloat3(&mesh.vertex_positions[index]); const XMUINT4& ind = mesh.vertex_boneindices[index]; const XMFLOAT4& wei = mesh.vertex_boneweights[index]; diff --git a/WickedEngine/wiScene_Components.cpp b/WickedEngine/wiScene_Components.cpp index 7df3190c9..f266d4b99 100644 --- a/WickedEngine/wiScene_Components.cpp +++ b/WickedEngine/wiScene_Components.cpp @@ -425,6 +425,11 @@ namespace wi::scene so_tan = {}; so_pre = {}; BLASes.clear(); + for (MorphTarget& morph : morph_targets) + { + morph.offset_pos = ~0ull; + morph.offset_nor = ~0ull; + } } void MeshComponent::CreateRenderData() { @@ -531,6 +536,18 @@ namespace wi::scene AlignTo(vertex_boneindices.size() * sizeof(Vertex_BON), alignment) ; + for (MorphTarget& morph : morph_targets) + { + if (!morph.vertex_positions.empty()) + { + bd.size += AlignTo(vertex_positions.size() * sizeof(XMHALF4), alignment); + } + if (!morph.vertex_normals.empty()) + { + bd.size += AlignTo(vertex_normals.size() * sizeof(XMHALF4), alignment); + } + } + // single allocation storage for GPU buffer data: wi::vector buffer_data(bd.size); uint64_t buffer_offset = 0ull; @@ -661,6 +678,66 @@ namespace wi::scene } vertices[i].FromFULL(vertex_boneindices[i], wei); } + } + + // morph buffers: + if (!morph_targets.empty()) + { + for (MorphTarget& morph : morph_targets) + { + if (!morph.vertex_positions.empty()) + { + morph.offset_pos = buffer_offset; + XMHALF4* vertices = (XMHALF4*)(buffer_data.data() + buffer_offset); + std::fill(vertices, vertices + vertex_positions.size(), 0); + if (morph.sparse_indices_positions.empty()) + { + // flat morphs: + for (size_t i = 0; i < morph.vertex_positions.size(); ++i) + { + XMStoreHalf4(vertices + i, XMLoadFloat3(&morph.vertex_positions[i])); + } + } + else + { + // sparse morphs will be flattened for GPU because they will be evaluated in skinning for every vertex: + for (size_t i = 0; i < morph.sparse_indices_positions.size(); ++i) + { + const uint32_t ind = morph.sparse_indices_positions[i]; + XMStoreHalf4(vertices + ind, XMLoadFloat3(&morph.vertex_positions[i])); + } + } + buffer_offset += AlignTo(morph.vertex_positions.size() * sizeof(XMHALF4), alignment); + } + if (!morph.vertex_normals.empty()) + { + morph.offset_nor = buffer_offset; + XMHALF4* vertices = (XMHALF4*)(buffer_data.data() + buffer_offset); + std::fill(vertices, vertices + vertex_normals.size(), 0); + if (morph.sparse_indices_normals.empty()) + { + // flat morphs: + for (size_t i = 0; i < morph.vertex_normals.size(); ++i) + { + XMStoreHalf4(vertices + i, XMLoadFloat3(&morph.vertex_normals[i])); + } + } + else + { + // sparse morphs will be flattened for GPU because they will be evaluated in skinning for every vertex: + for (size_t i = 0; i < morph.sparse_indices_normals.size(); ++i) + { + const uint32_t ind = morph.sparse_indices_normals[i]; + XMStoreHalf4(vertices + ind, XMLoadFloat3(&morph.vertex_normals[i])); + } + } + buffer_offset += AlignTo(morph.vertex_normals.size() * sizeof(XMHALF4), alignment); + } + } + } + + if (!vertex_boneindices.empty() || !morph_targets.empty()) + { CreateStreamoutRenderData(); } diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index 09585fdf5..aa9a0b63b 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -363,6 +363,10 @@ namespace wi::scene wi::vector sparse_indices_positions; // optional, these can be used to target vertices indirectly wi::vector sparse_indices_normals; // optional, these can be used to target vertices indirectly float weight = 0; + + // Non-serialized attributes: + uint64_t offset_pos = ~0ull; + uint64_t offset_nor = ~0ull; }; wi::vector morph_targets; @@ -398,6 +402,7 @@ namespace wi::scene BufferView so_pre; uint32_t geometryOffset = 0; uint32_t meshletCount = 0; + uint32_t active_morph_count = 0; wi::vector BLASes; // one BLAS per LOD enum BLAS_STATE @@ -408,8 +413,6 @@ namespace wi::scene }; mutable BLAS_STATE BLAS_state = BLAS_STATE_NEEDS_REBUILD; - mutable bool dirty_morph = false; - inline void SetRenderable(bool value) { if (value) { _flags |= RENDERABLE; } else { _flags &= ~RENDERABLE; } } inline void SetDoubleSided(bool value) { if (value) { _flags |= DOUBLE_SIDED; } else { _flags &= ~DOUBLE_SIDED; } } inline void SetDoubleSidedShadow(bool value) { if (value) { _flags |= DOUBLE_SIDED_SHADOW; } else { _flags &= ~DOUBLE_SIDED_SHADOW; } } @@ -601,11 +604,6 @@ namespace wi::scene static const wi::graphics::Format FORMAT = wi::graphics::Format::R8G8B8A8_UNORM; }; - // Non serialized attributes: - wi::vector vertex_positions_morphed; - wi::vector morph_temp_pos; - wi::vector morph_temp_nor; - }; struct ImpostorComponent diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 28550e200..a889fc8fc 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 183; + const int revision = 184; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);