Merge remote-tracking branch 'origin/master' into 1.9
# Conflicts: # Source/Engine/Renderer/RenderList.cpp # Source/Engine/Renderer/RenderList.h
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Core/Math/Transform.h"
|
||||
#include "Engine/Animations/Curve.h"
|
||||
|
||||
@@ -68,6 +69,16 @@ public:
|
||||
uint64 GetMemoryUsage() const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Single track with events.
|
||||
/// </summary>
|
||||
struct EventAnimationData
|
||||
{
|
||||
float Duration = 0.0f;
|
||||
StringAnsi TypeName;
|
||||
StringAnsi JsonData;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior.
|
||||
/// </summary>
|
||||
@@ -120,10 +131,15 @@ struct AnimationData
|
||||
String RootNodeName;
|
||||
|
||||
/// <summary>
|
||||
/// The per skeleton node animation channels.
|
||||
/// The per-skeleton node animation channels.
|
||||
/// </summary>
|
||||
Array<NodeAnimationData> Channels;
|
||||
|
||||
/// <summary>
|
||||
/// The animation event tracks.
|
||||
/// </summary>
|
||||
Array<Pair<String, StepCurve<EventAnimationData>>> Events;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the length of the animation (in seconds).
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Engine/Visject/VisjectGraph.h"
|
||||
#include "Engine/Content/Assets/Animation.h"
|
||||
#include "Engine/Core/Collections/ChunkedArray.h"
|
||||
#include "Engine/Core/Collections/BitArray.h"
|
||||
#include "Engine/Animations/AlphaBlend.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "../Config.h"
|
||||
@@ -892,7 +893,7 @@ private:
|
||||
|
||||
int32 GetRootNodeIndex(Animation* anim);
|
||||
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed);
|
||||
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override);
|
||||
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override, BitArray<InlinedAllocation<8>>* usedNodes = nullptr);
|
||||
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);
|
||||
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha);
|
||||
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace
|
||||
{
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
nodes->Nodes[i].Orientation.Normalize();
|
||||
nodes->Nodes.Get()[i].Orientation.Normalize();
|
||||
}
|
||||
if (rootMotionMode != RootMotionExtraction::NoExtraction)
|
||||
{
|
||||
@@ -222,7 +222,7 @@ FORCE_INLINE void GetAnimSamplePos(bool loop, float length, float startTimePos,
|
||||
prevPos = GetAnimPos(prevTimePos, startTimePos, loop, length);
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode)
|
||||
void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight, ProcessAnimationMode mode, BitArray<InlinedAllocation<8>>* usedNodes)
|
||||
{
|
||||
PROFILE_CPU_ASSET(anim);
|
||||
|
||||
@@ -240,9 +240,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
}
|
||||
|
||||
// Evaluate nested animations
|
||||
bool hasNested = false;
|
||||
BitArray<InlinedAllocation<8>> usedNodesThis;
|
||||
if (anim->NestedAnims.Count() != 0)
|
||||
{
|
||||
if (usedNodes == nullptr)
|
||||
{
|
||||
// Per-channel bit to indicate which channels were used by nested
|
||||
usedNodesThis.Resize(nodes->Nodes.Count());
|
||||
usedNodes = &usedNodesThis;
|
||||
}
|
||||
|
||||
for (auto& e : anim->NestedAnims)
|
||||
{
|
||||
const auto& nestedAnim = e.Second;
|
||||
@@ -262,8 +269,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
nestedAnimPrevPos = nestedAnimPrevPos * frameRateMatchScale;
|
||||
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
|
||||
|
||||
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
|
||||
hasNested = true;
|
||||
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode, usedNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,6 +301,15 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
{
|
||||
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i);
|
||||
}
|
||||
|
||||
// Mark node as used
|
||||
if (usedNodes)
|
||||
usedNodes->Set(i, true);
|
||||
}
|
||||
else if (usedNodes && usedNodes != &usedNodesThis)
|
||||
{
|
||||
// Skip for nested animations so other one or top-level anim will update remaining nodes
|
||||
continue;
|
||||
}
|
||||
|
||||
// Blend node
|
||||
@@ -316,7 +331,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
dstNode.Scale = srcNode.Scale * weight;
|
||||
dstNode.Orientation = srcNode.Orientation * weight;
|
||||
}
|
||||
else if (!hasNested)
|
||||
else
|
||||
{
|
||||
dstNode = srcNode;
|
||||
}
|
||||
@@ -1177,14 +1192,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0]));
|
||||
auto mask = node->Assets[0].As<SkeletonMask>();
|
||||
auto maskAssetBox = node->GetBox(4); // 4 is the id of skeleton mask parameter node.
|
||||
|
||||
// Check if have some mask asset connected with the mask node
|
||||
if (maskAssetBox->HasConnection())
|
||||
// Use the mask connected with this node instead of default mask asset
|
||||
auto maskAssetBox = node->TryGetBox(4);
|
||||
if (maskAssetBox && maskAssetBox->HasConnection())
|
||||
{
|
||||
const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null);
|
||||
|
||||
// Use the mask connected with this node instead of default mask asset
|
||||
if (assetBoxValue != Value::Null)
|
||||
mask = (SkeletonMask*)assetBoxValue.AsAsset;
|
||||
}
|
||||
|
||||
@@ -10,79 +10,102 @@ void InverseKinematics::SolveAimIK(const Transform& node, const Vector3& target,
|
||||
Quaternion::FindBetween(fromNode, toTarget, outNodeCorrection);
|
||||
}
|
||||
|
||||
void InverseKinematics::SolveTwoBoneIK(Transform& rootNode, Transform& jointNode, Transform& targetNode, const Vector3& target, const Vector3& jointTarget, bool allowStretching, float maxStretchScale)
|
||||
{
|
||||
Real lowerLimbLength = (targetNode.Translation - jointNode.Translation).Length();
|
||||
Real upperLimbLength = (jointNode.Translation - rootNode.Translation).Length();
|
||||
Vector3 jointPos = jointNode.Translation;
|
||||
Vector3 desiredDelta = target - rootNode.Translation;
|
||||
Real desiredLength = desiredDelta.Length();
|
||||
Real limbLengthLimit = lowerLimbLength + upperLimbLength;
|
||||
|
||||
Vector3 desiredDir;
|
||||
if (desiredLength < ZeroTolerance)
|
||||
void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJointTransform, Transform& endEffectorTransform, const Vector3& targetPosition, const Vector3& poleVector, bool allowStretching, float maxStretchScale)
|
||||
{
|
||||
// Calculate limb segment lengths
|
||||
Real lowerLimbLength = (endEffectorTransform.Translation - midJointTransform.Translation).Length();
|
||||
Real upperLimbLength = (midJointTransform.Translation - rootTransform.Translation).Length();
|
||||
Vector3 midJointPos = midJointTransform.Translation;
|
||||
|
||||
// Calculate the direction and length towards the target
|
||||
Vector3 toTargetVector = targetPosition - rootTransform.Translation;
|
||||
Real toTargetLength = toTargetVector.Length();
|
||||
Real totalLimbLength = lowerLimbLength + upperLimbLength;
|
||||
|
||||
// Normalize the direction vector or set a default direction if too small
|
||||
Vector3 toTargetDir;
|
||||
if (toTargetLength < ZeroTolerance)
|
||||
{
|
||||
desiredLength = ZeroTolerance;
|
||||
desiredDir = Vector3(1, 0, 0);
|
||||
toTargetLength = ZeroTolerance;
|
||||
toTargetDir = Vector3(1, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredDir = desiredDelta.GetNormalized();
|
||||
toTargetDir = toTargetVector.GetNormalized();
|
||||
}
|
||||
|
||||
Vector3 jointTargetDelta = jointTarget - rootNode.Translation;
|
||||
const Real jointTargetLengthSqr = jointTargetDelta.LengthSquared();
|
||||
// Calculate the pole vector direction
|
||||
Vector3 poleVectorDelta = poleVector - rootTransform.Translation;
|
||||
const Real poleVectorLengthSqr = poleVectorDelta.LengthSquared();
|
||||
|
||||
Vector3 jointPlaneNormal, jointBendDir;
|
||||
if (jointTargetLengthSqr < ZeroTolerance * ZeroTolerance)
|
||||
Vector3 jointPlaneNormal, bendDirection;
|
||||
if (poleVectorLengthSqr < ZeroTolerance * ZeroTolerance)
|
||||
{
|
||||
jointBendDir = Vector3::Forward;
|
||||
bendDirection = Vector3::Forward;
|
||||
jointPlaneNormal = Vector3::Up;
|
||||
}
|
||||
else
|
||||
{
|
||||
jointPlaneNormal = desiredDir ^ jointTargetDelta;
|
||||
jointPlaneNormal = toTargetDir ^ poleVectorDelta;
|
||||
if (jointPlaneNormal.LengthSquared() < ZeroTolerance * ZeroTolerance)
|
||||
{
|
||||
desiredDir.FindBestAxisVectors(jointPlaneNormal, jointBendDir);
|
||||
toTargetDir.FindBestAxisVectors(jointPlaneNormal, bendDirection);
|
||||
}
|
||||
else
|
||||
{
|
||||
jointPlaneNormal.Normalize();
|
||||
jointBendDir = jointTargetDelta - (jointTargetDelta | desiredDir) * desiredDir;
|
||||
jointBendDir.Normalize();
|
||||
bendDirection = poleVectorDelta - (poleVectorDelta | toTargetDir) * toTargetDir;
|
||||
bendDirection.Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle limb stretching if allowed
|
||||
if (allowStretching)
|
||||
{
|
||||
const Real initialStretchRatio = 1.0f;
|
||||
const Real range = maxStretchScale - initialStretchRatio;
|
||||
if (range > ZeroTolerance && limbLengthLimit > ZeroTolerance)
|
||||
const Real stretchRange = maxStretchScale - initialStretchRatio;
|
||||
if (stretchRange > ZeroTolerance && totalLimbLength > ZeroTolerance)
|
||||
{
|
||||
const Real reachRatio = desiredLength / limbLengthLimit;
|
||||
const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / range);
|
||||
const Real reachRatio = toTargetLength / totalLimbLength;
|
||||
const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / stretchRange);
|
||||
if (scalingFactor > ZeroTolerance)
|
||||
{
|
||||
lowerLimbLength *= 1.0f + scalingFactor;
|
||||
upperLimbLength *= 1.0f + scalingFactor;
|
||||
limbLengthLimit *= 1.0f + scalingFactor;
|
||||
totalLimbLength *= 1.0f + scalingFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 resultEndPos = target;
|
||||
Vector3 resultJointPos = jointPos;
|
||||
// Calculate new positions for joint and end effector
|
||||
Vector3 newEndEffectorPos = targetPosition;
|
||||
Vector3 newMidJointPos = midJointPos;
|
||||
|
||||
if (desiredLength >= limbLengthLimit)
|
||||
{
|
||||
resultEndPos = rootNode.Translation + limbLengthLimit * desiredDir;
|
||||
resultJointPos = rootNode.Translation + upperLimbLength * desiredDir;
|
||||
if (toTargetLength >= totalLimbLength) {
|
||||
// Target is beyond the reach of the limb
|
||||
Vector3 rootToEnd = (targetPosition - rootTransform.Translation).GetNormalized();
|
||||
|
||||
// Calculate the slight offset towards the pole vector
|
||||
Vector3 rootToPole = (poleVector - rootTransform.Translation).GetNormalized();
|
||||
Vector3 slightBendDirection = Vector3::Cross(rootToEnd, rootToPole);
|
||||
if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance) {
|
||||
slightBendDirection = Vector3::Up;
|
||||
}
|
||||
else {
|
||||
slightBendDirection.Normalize();
|
||||
}
|
||||
|
||||
// Calculate the direction from root to mid joint with a slight offset towards the pole vector
|
||||
Vector3 midJointDirection = Vector3::Cross(slightBendDirection, rootToEnd).GetNormalized();
|
||||
Real slightOffset = upperLimbLength * 0.01f; // Small percentage of the limb length for slight offset
|
||||
newMidJointPos = rootTransform.Translation + rootToEnd * (upperLimbLength - slightOffset) + midJointDirection * slightOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Real twoAb = 2.0f * upperLimbLength * desiredLength;
|
||||
const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + desiredLength * desiredLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
|
||||
// Target is within reach, calculate joint position
|
||||
const Real twoAb = 2.0f * upperLimbLength * toTargetLength;
|
||||
const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + toTargetLength * toTargetLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
|
||||
const bool reverseUpperBone = cosAngle < 0.0f;
|
||||
const Real angle = Math::Acos(cosAngle);
|
||||
const Real jointLineDist = upperLimbLength * Math::Sin(angle);
|
||||
@@ -90,23 +113,66 @@ void InverseKinematics::SolveTwoBoneIK(Transform& rootNode, Transform& jointNode
|
||||
Real projJointDist = projJointDistSqr > 0.0f ? Math::Sqrt(projJointDistSqr) : 0.0f;
|
||||
if (reverseUpperBone)
|
||||
projJointDist *= -1.0f;
|
||||
resultJointPos = rootNode.Translation + projJointDist * desiredDir + jointLineDist * jointBendDir;
|
||||
newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection;
|
||||
}
|
||||
|
||||
// Update root joint orientation
|
||||
{
|
||||
const Vector3 oldDir = (jointPos - rootNode.Translation).GetNormalized();
|
||||
const Vector3 newDir = (resultJointPos - rootNode.Translation).GetNormalized();
|
||||
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
|
||||
rootNode.Orientation = deltaRotation * rootNode.Orientation;
|
||||
// Vector from root joint to mid joint (local Y-axis direction)
|
||||
Vector3 localY = (newMidJointPos - rootTransform.Translation).GetNormalized();
|
||||
|
||||
// Vector from mid joint to end effector (used to calculate plane normal)
|
||||
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
||||
|
||||
// Calculate the plane normal (local Z-axis direction)
|
||||
Vector3 localZ = Vector3::Cross(localY, midToEnd).GetNormalized();
|
||||
|
||||
// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
|
||||
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
|
||||
|
||||
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
|
||||
localZ = Vector3::Cross(localX, localY).GetNormalized();
|
||||
|
||||
// Construct a rotation from the orthogonal basis vectors
|
||||
Quaternion newRootJointOrientation = Quaternion::LookRotation(localZ, localY);
|
||||
|
||||
// Apply the new rotation to the root joint
|
||||
rootTransform.Orientation = newRootJointOrientation;
|
||||
}
|
||||
|
||||
|
||||
// Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane
|
||||
{
|
||||
const Vector3 oldDir = (targetNode.Translation - jointPos).GetNormalized();
|
||||
const Vector3 newDir = (resultEndPos - resultJointPos).GetNormalized();
|
||||
const Quaternion deltaRotation = Quaternion::FindBetween(oldDir, newDir);
|
||||
jointNode.Orientation = deltaRotation * jointNode.Orientation;
|
||||
jointNode.Translation = resultJointPos;
|
||||
// Vector from mid joint to end effector (local Y-axis direction after rotation)
|
||||
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
||||
|
||||
// Calculate the plane normal using the root, mid joint, and end effector positions (will be the local Z-axis direction)
|
||||
Vector3 rootToMid = (newMidJointPos - rootTransform.Translation).GetNormalized();
|
||||
Vector3 planeNormal = Vector3::Cross(rootToMid, midToEnd).GetNormalized();
|
||||
|
||||
|
||||
// Vector from mid joint to end effector (local Y-axis direction)
|
||||
Vector3 localY = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
||||
|
||||
// Calculate the plane normal using the root, mid joint, and end effector positions (local Z-axis direction)
|
||||
Vector3 localZ = Vector3::Cross(rootToMid, localY).GetNormalized();
|
||||
|
||||
//// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
|
||||
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
|
||||
|
||||
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
|
||||
localZ = Vector3::Cross(localX, localY).GetNormalized();
|
||||
|
||||
|
||||
// Construct a rotation from the orthogonal basis vectors
|
||||
// The axes are used differently here than a standard LookRotation to align Z towards the end and Y perpendicular
|
||||
Quaternion newMidJointOrientation = Quaternion::LookRotation(localZ, localY); // Assuming FromLookRotation creates a rotation with the first vector as forward and the second as up
|
||||
|
||||
// Apply the new rotation to the mid joint
|
||||
midJointTransform.Orientation = newMidJointOrientation;
|
||||
midJointTransform.Translation = newMidJointPos;
|
||||
}
|
||||
|
||||
targetNode.Translation = resultEndPos;
|
||||
// Update end effector transform
|
||||
endEffectorTransform.Translation = newEndEffectorPos;
|
||||
}
|
||||
|
||||
@@ -152,12 +152,17 @@ void AudioSource::Play()
|
||||
RequestStreamingBuffersUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (SourceIDs.HasItems())
|
||||
{
|
||||
// Play it right away
|
||||
SetNonStreamingBuffer();
|
||||
PlayInternal();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Source was nt properly added to the Audio Backend
|
||||
LOG(Warning, "Cannot play unitialized audio source.");
|
||||
}
|
||||
}
|
||||
|
||||
void AudioSource::Pause()
|
||||
|
||||
@@ -184,9 +184,8 @@ void SoftAssetReferenceBase::OnUnloaded(Asset* asset)
|
||||
Asset::Asset(const SpawnParams& params, const AssetInfo* info)
|
||||
: ManagedScriptingObject(params)
|
||||
, _refCount(0)
|
||||
, _loadingTask(nullptr)
|
||||
, _isLoaded(false)
|
||||
, _loadFailed(false)
|
||||
, _loadState(0)
|
||||
, _loadingTask(0)
|
||||
, _deleteFileOnUnload(false)
|
||||
, _isVirtual(false)
|
||||
{
|
||||
@@ -225,10 +224,10 @@ void Asset::OnDeleteObject()
|
||||
|
||||
// Unload asset data (in a safe way to protect asset data)
|
||||
Locker.Lock();
|
||||
if (_isLoaded)
|
||||
if (IsLoaded())
|
||||
{
|
||||
unload(false);
|
||||
_isLoaded = false;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
|
||||
}
|
||||
Locker.Unlock();
|
||||
|
||||
@@ -319,11 +318,6 @@ void Asset::ChangeID(const Guid& newId)
|
||||
Content::onAssetChangeId(this, oldId, newId);
|
||||
}
|
||||
|
||||
bool Asset::LastLoadFailed() const
|
||||
{
|
||||
return _loadFailed != 0;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Asset::ShouldDeleteFileOnUnload() const
|
||||
@@ -337,7 +331,7 @@ uint64 Asset::GetMemoryUsage() const
|
||||
{
|
||||
uint64 result = sizeof(Asset);
|
||||
Locker.Lock();
|
||||
if (_loadingTask)
|
||||
if (Platform::AtomicRead(&_loadingTask))
|
||||
result += sizeof(ContentLoadTask);
|
||||
result += (OnLoaded.Capacity() + OnReloading.Capacity() + OnUnloaded.Capacity()) * sizeof(EventType::FunctionType);
|
||||
Locker.Unlock();
|
||||
@@ -368,7 +362,7 @@ void Asset::Reload()
|
||||
{
|
||||
// Unload current data
|
||||
unload(true);
|
||||
_isLoaded = false;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Unloaded);
|
||||
}
|
||||
|
||||
// Start reloading process
|
||||
@@ -426,7 +420,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
|
||||
// Check if has missing loading task
|
||||
Platform::MemoryBarrier();
|
||||
const auto loadingTask = _loadingTask;
|
||||
const auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
if (loadingTask == nullptr)
|
||||
{
|
||||
LOG(Warning, "WaitForLoaded asset \'{0}\' failed. No loading task attached and asset is not loaded.", ToString());
|
||||
@@ -516,7 +510,7 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
Content::tryCallOnLoaded((Asset*)this);
|
||||
}
|
||||
|
||||
return _isLoaded == 0;
|
||||
return !IsLoaded();
|
||||
}
|
||||
|
||||
void Asset::InitAsVirtual()
|
||||
@@ -525,14 +519,14 @@ void Asset::InitAsVirtual()
|
||||
_isVirtual = true;
|
||||
|
||||
// Be a loaded thing
|
||||
_isLoaded = true;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
|
||||
}
|
||||
|
||||
void Asset::CancelStreaming()
|
||||
{
|
||||
// Cancel loading task but go over asset locker to prevent case if other load threads still loads asset while it's reimported on other thread
|
||||
Locker.Lock();
|
||||
ContentLoadTask* loadTask = _loadingTask;
|
||||
auto loadTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
Locker.Unlock();
|
||||
if (loadTask)
|
||||
{
|
||||
@@ -575,10 +569,11 @@ ContentLoadTask* Asset::createLoadingTask()
|
||||
void Asset::startLoading()
|
||||
{
|
||||
ASSERT(!IsLoaded());
|
||||
ASSERT(_loadingTask == nullptr);
|
||||
_loadingTask = createLoadingTask();
|
||||
ASSERT(_loadingTask != nullptr);
|
||||
_loadingTask->Start();
|
||||
ASSERT(Platform::AtomicRead(&_loadingTask) == 0);
|
||||
auto loadingTask = createLoadingTask();
|
||||
ASSERT(loadingTask != nullptr);
|
||||
Platform::AtomicStore(&_loadingTask, (intptr)loadingTask);
|
||||
loadingTask->Start();
|
||||
}
|
||||
|
||||
void Asset::releaseStorage()
|
||||
@@ -593,7 +588,7 @@ bool Asset::IsInternalType() const
|
||||
bool Asset::onLoad(LoadAssetTask* task)
|
||||
{
|
||||
// It may fail when task is cancelled and new one is created later (don't crash but just end with an error)
|
||||
if (task->Asset.Get() != this || _loadingTask == nullptr)
|
||||
if (task->Asset.Get() != this || Platform::AtomicRead(&_loadingTask) == 0)
|
||||
return true;
|
||||
|
||||
Locker.Lock();
|
||||
@@ -606,15 +601,15 @@ bool Asset::onLoad(LoadAssetTask* task)
|
||||
}
|
||||
const bool isLoaded = result == LoadResult::Ok;
|
||||
const bool failed = !isLoaded;
|
||||
_loadFailed = failed;
|
||||
_isLoaded = !failed;
|
||||
LoadState state = LoadState::Loaded;
|
||||
Platform::AtomicStore(&_loadState, (int64)(isLoaded ? LoadState::Loaded : LoadState::LoadFailed));
|
||||
if (failed)
|
||||
{
|
||||
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(result));
|
||||
}
|
||||
|
||||
// Unlink task
|
||||
_loadingTask = nullptr;
|
||||
Platform::AtomicStore(&_loadingTask, 0);
|
||||
ASSERT(failed || IsLoaded() == isLoaded);
|
||||
|
||||
Locker.Unlock();
|
||||
@@ -663,12 +658,12 @@ void Asset::onUnload_MainThread()
|
||||
OnUnloaded(this);
|
||||
|
||||
// Check if is during loading
|
||||
if (_loadingTask != nullptr)
|
||||
auto loadingTask = (ContentLoadTask*)Platform::AtomicRead(&_loadingTask);
|
||||
if (loadingTask != nullptr)
|
||||
{
|
||||
// Cancel loading
|
||||
auto task = _loadingTask;
|
||||
_loadingTask = nullptr;
|
||||
Platform::AtomicStore(&_loadingTask, 0);
|
||||
LOG(Warning, "Cancel loading task for \'{0}\'", ToString());
|
||||
task->Cancel();
|
||||
loadingTask->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,11 +34,17 @@ public:
|
||||
DECLARE_ENUM_7(LoadResult, Ok, Failed, MissingDataChunk, CannotLoadData, CannotLoadStorage, CannotLoadInitData, InvalidData);
|
||||
|
||||
protected:
|
||||
volatile int64 _refCount;
|
||||
ContentLoadTask* _loadingTask;
|
||||
enum class LoadState : int64
|
||||
{
|
||||
Unloaded,
|
||||
Loaded,
|
||||
LoadFailed,
|
||||
};
|
||||
|
||||
volatile int64 _refCount;
|
||||
volatile int64 _loadState;
|
||||
volatile intptr _loadingTask;
|
||||
|
||||
int8 _isLoaded : 1; // Indicates that asset is loaded
|
||||
int8 _loadFailed : 1; // Indicates that last asset loading has failed
|
||||
int8 _deleteFileOnUnload : 1; // Indicates that asset source file should be removed on asset unload
|
||||
int8 _isVirtual : 1; // Indicates that asset is pure virtual (generated or temporary, has no storage so won't be saved)
|
||||
|
||||
@@ -111,13 +117,16 @@ public:
|
||||
/// </summary>
|
||||
API_PROPERTY() FORCE_INLINE bool IsLoaded() const
|
||||
{
|
||||
return _isLoaded != 0;
|
||||
return Platform::AtomicRead(&_loadState) == (int64)LoadState::Loaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if last asset loading failed, otherwise false.
|
||||
/// </summary>
|
||||
API_PROPERTY() bool LastLoadFailed() const;
|
||||
API_PROPERTY() bool LastLoadFailed() const
|
||||
{
|
||||
return Platform::AtomicRead(&_loadState) == (int64)LoadState::LoadFailed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this asset is virtual (generated or temporary, has no storage so it won't be saved).
|
||||
|
||||
@@ -1434,8 +1434,7 @@ Asset::LoadResult VisualScript::load()
|
||||
if (_instances.HasItems())
|
||||
{
|
||||
// Mark as already loaded so any WaitForLoaded checks during GetDefaultInstance bellow will handle this Visual Script as ready to use
|
||||
_loadFailed = false;
|
||||
_isLoaded = true;
|
||||
Platform::AtomicStore(&_loadState, (int64)LoadState::Loaded);
|
||||
|
||||
// Setup scripting type
|
||||
CacheScriptingType();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#include "Engine/Graphics/Graphics.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
@@ -688,101 +689,135 @@ bool Content::FastTmpAssetClone(const StringView& path, String& resultPath)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
|
||||
class CloneAssetFileTask : public MainThreadTask
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
|
||||
public:
|
||||
StringView dstPath;
|
||||
StringView srcPath;
|
||||
Guid dstId;
|
||||
bool* output;
|
||||
|
||||
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
|
||||
|
||||
// Check source file
|
||||
if (!FileSystem::FileExists(srcPath))
|
||||
protected:
|
||||
bool Run() override
|
||||
{
|
||||
LOG(Warning, "Missing source file.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special case for json resources
|
||||
if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
|
||||
{
|
||||
if (FileSystem::CopyFile(dstPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
if (JsonStorageProxy::ChangeId(dstPath, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
*output = Content::CloneAssetFile(dstPath, srcPath, dstId);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Check if destination file is missing
|
||||
if (!FileSystem::FileExists(dstPath))
|
||||
bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPath, const Guid& dstId)
|
||||
{
|
||||
// Best to run this on the main thread to avoid clone conflicts.
|
||||
if (IsInMainThread())
|
||||
{
|
||||
// Use quick file copy
|
||||
if (FileSystem::CopyFile(dstPath, srcPath))
|
||||
PROFILE_CPU();
|
||||
ASSERT(FileSystem::AreFilePathsEqual(srcPath, dstPath) == false && dstId.IsValid());
|
||||
|
||||
LOG(Info, "Cloning asset \'{0}\' to \'{1}\'({2}).", srcPath, dstPath, dstId);
|
||||
|
||||
// Check source file
|
||||
if (!FileSystem::FileExists(srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
LOG(Warning, "Missing source file.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change ID
|
||||
auto storage = ContentStorageManager::GetStorage(dstPath);
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (storage == nullptr || storage->ChangeAssetID(e, dstId))
|
||||
// Special case for json resources
|
||||
if (JsonStorageProxy::IsValidExtension(FileSystem::GetExtension(srcPath).ToLower()))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
if (FileSystem::CopyFile(dstPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
if (JsonStorageProxy::ChangeId(dstPath, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if destination file is missing
|
||||
if (!FileSystem::FileExists(dstPath))
|
||||
{
|
||||
// Use quick file copy
|
||||
if (FileSystem::CopyFile(dstPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change ID
|
||||
auto storage = ContentStorageManager::GetStorage(dstPath);
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (storage == nullptr || storage->ChangeAssetID(e, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use temporary file
|
||||
String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D);
|
||||
if (FileSystem::CopyFile(tmpPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change asset ID
|
||||
{
|
||||
auto storage = ContentStorageManager::GetStorage(tmpPath);
|
||||
if (!storage)
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (storage->ChangeAssetID(e, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock destination file
|
||||
ContentStorageManager::EnsureAccess(dstPath);
|
||||
|
||||
// Copy temp file to the destination
|
||||
if (FileSystem::CopyFile(dstPath, tmpPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
FileSystem::DeleteFile(tmpPath);
|
||||
|
||||
// Reload storage
|
||||
if (auto storage = ContentStorageManager::GetStorage(dstPath))
|
||||
{
|
||||
storage->Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use temporary file
|
||||
String tmpPath = Globals::TemporaryFolder / Guid::New().ToString(Guid::FormatType::D);
|
||||
if (FileSystem::CopyFile(tmpPath, srcPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file.");
|
||||
return true;
|
||||
}
|
||||
CloneAssetFileTask* task = New<CloneAssetFileTask>();
|
||||
task->dstId = dstId;
|
||||
task->dstPath = dstPath;
|
||||
task->srcPath = srcPath;
|
||||
|
||||
// Change asset ID
|
||||
{
|
||||
auto storage = ContentStorageManager::GetStorage(tmpPath);
|
||||
if (!storage)
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (storage->ChangeAssetID(e, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool result = false;
|
||||
task->output = &result;
|
||||
task->Start();
|
||||
task->Wait();
|
||||
|
||||
// Unlock destination file
|
||||
ContentStorageManager::EnsureAccess(dstPath);
|
||||
|
||||
// Copy temp file to the destination
|
||||
if (FileSystem::CopyFile(dstPath, tmpPath))
|
||||
{
|
||||
LOG(Warning, "Cannot copy file to destination.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
FileSystem::DeleteFile(tmpPath);
|
||||
|
||||
// Reload storage
|
||||
if (auto storage = ContentStorageManager::GetStorage(dstPath))
|
||||
{
|
||||
storage->Reload();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -31,12 +31,11 @@ public:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
{
|
||||
Asset->_loadFailed = true;
|
||||
Asset->_isLoaded = false;
|
||||
Platform::AtomicStore(&Asset->_loadState, (int64)Asset::LoadState::LoadFailed);
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
LOG(Error, "Loading asset \'{0}\' result: {1}.", ToString(), ToString(Result::TaskFailed));
|
||||
Asset->_loadingTask = nullptr;
|
||||
}
|
||||
Asset->Locker.Unlock();
|
||||
}
|
||||
@@ -77,8 +76,8 @@ protected:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
Asset->_loadingTask = nullptr;
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
Asset->Locker.Unlock();
|
||||
Asset = nullptr;
|
||||
}
|
||||
@@ -91,8 +90,8 @@ protected:
|
||||
if (Asset)
|
||||
{
|
||||
Asset->Locker.Lock();
|
||||
if (Asset->_loadingTask == this)
|
||||
Asset->_loadingTask = nullptr;
|
||||
if (Platform::AtomicRead(&Asset->_loadingTask) == (intptr)this)
|
||||
Platform::AtomicStore(&Asset->_loadingTask, 0);
|
||||
Asset->Locker.Unlock();
|
||||
Asset = nullptr;
|
||||
}
|
||||
|
||||
@@ -413,6 +413,7 @@ bool AssetsImportingManagerService::Init()
|
||||
{ TEXT("jpg"), ASSET_FILES_EXTENSION, ImportTexture::Import },
|
||||
{ TEXT("hdr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
|
||||
{ TEXT("raw"), ASSET_FILES_EXTENSION, ImportTexture::Import },
|
||||
{ TEXT("exr"), ASSET_FILES_EXTENSION, ImportTexture::Import },
|
||||
|
||||
// IES Profiles
|
||||
{ TEXT("ies"), ASSET_FILES_EXTENSION, ImportTexture::ImportIES },
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "Engine/Content/Storage/ContentStorageManager.h"
|
||||
#include "Engine/Content/Assets/Animation.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
#include "Engine/Level/Actors/EmptyActor.h"
|
||||
#include "Engine/Level/Actors/StaticModel.h"
|
||||
#include "Engine/Level/Prefabs/Prefab.h"
|
||||
@@ -141,48 +142,6 @@ void RepackMeshLightmapUVs(ModelData& data)
|
||||
}
|
||||
}
|
||||
|
||||
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
|
||||
{
|
||||
// Skip if file is missing
|
||||
if (!FileSystem::FileExists(context.TargetAssetPath))
|
||||
return;
|
||||
|
||||
// Try to load asset that gets reimported
|
||||
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
|
||||
if (asset == nullptr)
|
||||
return;
|
||||
if (asset->WaitForLoaded())
|
||||
return;
|
||||
|
||||
// Get model object
|
||||
ModelBase* model = nullptr;
|
||||
if (asset.Get()->GetTypeName() == Model::TypeName)
|
||||
{
|
||||
model = ((Model*)asset.Get());
|
||||
}
|
||||
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
|
||||
{
|
||||
model = ((SkinnedModel*)asset.Get());
|
||||
}
|
||||
if (!model)
|
||||
return;
|
||||
|
||||
// Peek materials
|
||||
for (int32 i = 0; i < modelData.Materials.Count(); i++)
|
||||
{
|
||||
auto& dstSlot = modelData.Materials[i];
|
||||
|
||||
if (model->MaterialSlots.Count() > i)
|
||||
{
|
||||
auto& srcSlot = model->MaterialSlots[i];
|
||||
|
||||
dstSlot.Name = srcSlot.Name;
|
||||
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
|
||||
dstSlot.AssetID = srcSlot.Material.GetID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetupMaterialSlots(ModelData& data, const Array<MaterialSlotEntry>& materials)
|
||||
{
|
||||
Array<int32> materialSlotsTable;
|
||||
@@ -458,10 +417,62 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
data = &dataThis;
|
||||
}
|
||||
|
||||
// Check if restore materials on model reimport
|
||||
if (options.RestoreMaterialsOnReimport && data->Materials.HasItems())
|
||||
// Check if restore local changes on asset reimport
|
||||
constexpr bool RestoreAnimEventsOnReimport = true;
|
||||
const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems();
|
||||
const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems();
|
||||
if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath))
|
||||
{
|
||||
TryRestoreMaterials(context, *data);
|
||||
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
|
||||
if (asset && !asset->WaitForLoaded())
|
||||
{
|
||||
auto* model = ScriptingObject::Cast<ModelBase>(asset);
|
||||
auto* animation = ScriptingObject::Cast<Animation>(asset);
|
||||
if (restoreMaterials && model)
|
||||
{
|
||||
// Copy material settings
|
||||
for (int32 i = 0; i < data->Materials.Count(); i++)
|
||||
{
|
||||
auto& dstSlot = data->Materials[i];
|
||||
if (model->MaterialSlots.Count() > i)
|
||||
{
|
||||
auto& srcSlot = model->MaterialSlots[i];
|
||||
dstSlot.Name = srcSlot.Name;
|
||||
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
|
||||
dstSlot.AssetID = srcSlot.Material.GetID();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (restoreAnimEvents && animation)
|
||||
{
|
||||
// Copy anim event tracks
|
||||
for (const auto& e : animation->Events)
|
||||
{
|
||||
auto& clone = data->Animations[0].Events.AddOne();
|
||||
clone.First = e.First;
|
||||
const auto& eKeys = e.Second.GetKeyframes();
|
||||
auto& cloneKeys = clone.Second.GetKeyframes();
|
||||
clone.Second.Resize(eKeys.Count());
|
||||
for (int32 i = 0; i < eKeys.Count(); i++)
|
||||
{
|
||||
const auto& eKey = eKeys[i];
|
||||
auto& cloneKey = cloneKeys[i];
|
||||
cloneKey.Time = eKey.Time;
|
||||
cloneKey.Value.Duration = eKey.Value.Duration;
|
||||
if (eKey.Value.Instance)
|
||||
{
|
||||
cloneKey.Value.TypeName = eKey.Value.Instance->GetType().Fullname;
|
||||
rapidjson_flax::StringBuffer buffer;
|
||||
CompactJsonWriter writer(buffer);
|
||||
writer.StartObject();
|
||||
eKey.Value.Instance->Serialize(writer, nullptr);
|
||||
writer.EndObject();
|
||||
cloneKey.Value.JsonData.Set(buffer.GetString(), buffer.GetSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When using generated lightmap UVs those coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space
|
||||
|
||||
@@ -44,6 +44,49 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
static void Merge(T* data, T* tmp, int32 start, int32 mid, int32 end)
|
||||
{
|
||||
int32 h = start;
|
||||
int32 i = start;
|
||||
int32 j = mid + 1;
|
||||
|
||||
while (h <= mid && j <= end)
|
||||
{
|
||||
if (data[h] < data[j])
|
||||
tmp[i] = data[h++];
|
||||
else
|
||||
tmp[i] = data[j++];
|
||||
i++;
|
||||
}
|
||||
|
||||
if (h > mid)
|
||||
{
|
||||
for (int32 k = j; k <= end; k++)
|
||||
tmp[i++] = data[k];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 k = h; k <= mid; k++)
|
||||
tmp[i++] = data[k];
|
||||
}
|
||||
|
||||
for (int32 k = start; k <= end; k++)
|
||||
data[k] = tmp[k];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void MergeSort(T* data, T* tmp, int32 start, int32 end)
|
||||
{
|
||||
if (start >= end)
|
||||
return;
|
||||
const int32 mid = (start + end) / 2;
|
||||
MergeSort(data, tmp, start, mid);
|
||||
MergeSort(data, tmp, mid + 1, end);
|
||||
Merge(data, tmp, start, mid, end);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
|
||||
@@ -263,6 +306,33 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Merge Sort algorithm (recursive version, uses temporary memory).
|
||||
/// </summary>
|
||||
/// <param name="data">The data pointer.</param>
|
||||
/// <param name="count">The elements count.</param>
|
||||
/// <param name="tmp">The additional temporary memory buffer for sorting data. If null then will be automatically allocated within this function call.</param>
|
||||
template<typename T>
|
||||
static void MergeSort(T* data, int32 count, T* tmp = nullptr)
|
||||
{
|
||||
if (count < 2)
|
||||
return;
|
||||
const bool alloc = tmp == nullptr;
|
||||
if (alloc)
|
||||
tmp = (T*)Platform::Allocate(sizeof(T) * count, 16);
|
||||
MergeSort(data, tmp, 0, count - 1);
|
||||
if (alloc)
|
||||
Platform::Free(tmp);
|
||||
}
|
||||
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
FORCE_INLINE static void MergeSort(Array<T, AllocationType>& data, Array<T, AllocationType>* tmp = nullptr)
|
||||
{
|
||||
if (tmp)
|
||||
tmp->Resize(data.Count());
|
||||
MergeSort(data.Get(), data.Count(), tmp ? tmp->Get() : nullptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Radix Sort algorithm (uses temporary keys collection).
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#define FORCE_INLINE inline
|
||||
#define FORCE_NOINLINE __attribute__((noinline))
|
||||
#define NO_RETURN __attribute__((noreturn))
|
||||
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
|
||||
#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
|
||||
#define PACK_BEGIN()
|
||||
#define PACK_END() __attribute__((__packed__))
|
||||
#define ALIGN_BEGIN(_align)
|
||||
@@ -44,6 +46,8 @@
|
||||
#define FORCE_INLINE inline
|
||||
#define FORCE_NOINLINE __attribute__((noinline))
|
||||
#define NO_RETURN __attribute__((noreturn))
|
||||
#define NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
|
||||
#define NO_SANITIZE_THREAD __attribute__((no_sanitize_thread))
|
||||
#define PACK_BEGIN()
|
||||
#define PACK_END() __attribute__((__packed__))
|
||||
#define ALIGN_BEGIN(_align)
|
||||
@@ -69,6 +73,8 @@
|
||||
#define FORCE_INLINE __forceinline
|
||||
#define FORCE_NOINLINE __declspec(noinline)
|
||||
#define NO_RETURN __declspec(noreturn)
|
||||
#define NO_SANITIZE_ADDRESS
|
||||
#define NO_SANITIZE_THREAD
|
||||
#define PACK_BEGIN() __pragma(pack(push, 1))
|
||||
#define PACK_END() ; __pragma(pack(pop))
|
||||
#define ALIGN_BEGIN(_align) __declspec(align(_align))
|
||||
|
||||
@@ -94,4 +94,15 @@ namespace Utilities
|
||||
return (x * 0x01010101) >> 24;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Copy memory region but ignoring address sanatizer checks for memory regions.
|
||||
NO_SANITIZE_ADDRESS static void UnsafeMemoryCopy(void* dst, const void* src, uint64 size)
|
||||
{
|
||||
#if BUILD_RELEASE
|
||||
memcpy(dst, src, static_cast<size_t>(size));
|
||||
#else
|
||||
for (uint64 i = 0; i < size; i++)
|
||||
((byte*)dst)[i] = ((byte*)src)[i];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ PACK_STRUCT(struct Data {
|
||||
Matrix ViewProjection;
|
||||
Float2 Padding;
|
||||
float ClipPosZBias;
|
||||
bool EnableDepthTest;
|
||||
uint32 EnableDepthTest;
|
||||
});
|
||||
|
||||
struct PsData
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CommandLine.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
#include "Engine/Core/Utilities.h"
|
||||
#include <iostream>
|
||||
|
||||
CommandLine::OptionsData CommandLine::Options;
|
||||
@@ -81,7 +82,7 @@ bool CommandLine::Parse(const Char* cmdLine)
|
||||
if (pos) \
|
||||
{ \
|
||||
len = ARRAY_COUNT(text) - 1; \
|
||||
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
*(end - len) = 0; \
|
||||
end -= len; \
|
||||
Options.field = true; \
|
||||
@@ -98,7 +99,7 @@ bool CommandLine::Parse(const Char* cmdLine)
|
||||
} \
|
||||
Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \
|
||||
len = static_cast<int32>((argEnd - pos) + 1); \
|
||||
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
*(end - len) = 0; \
|
||||
end -= len; \
|
||||
}
|
||||
@@ -114,7 +115,7 @@ bool CommandLine::Parse(const Char* cmdLine)
|
||||
{ \
|
||||
Options.field = String(argStart, static_cast<int32>(argEnd - argStart)); \
|
||||
len = static_cast<int32>((argEnd - pos) + 1); \
|
||||
Platform::MemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \
|
||||
*(end - len) = 0; \
|
||||
end -= len; \
|
||||
} \
|
||||
|
||||
@@ -324,10 +324,12 @@ void Engine::OnUpdate()
|
||||
|
||||
// Call event
|
||||
Update();
|
||||
UpdateGraph->Execute();
|
||||
|
||||
// Update services
|
||||
EngineService::OnUpdate();
|
||||
|
||||
// Run async
|
||||
UpdateGraph->Execute();
|
||||
}
|
||||
|
||||
void Engine::OnLateUpdate()
|
||||
|
||||
@@ -221,9 +221,7 @@ Asset::LoadResult GameplayGlobals::load()
|
||||
// Get data
|
||||
const auto chunk = GetChunk(0);
|
||||
if (!chunk || !chunk->IsLoaded())
|
||||
{
|
||||
return LoadResult::MissingDataChunk;
|
||||
}
|
||||
MemoryReadStream stream(chunk->Get(), chunk->Size());
|
||||
|
||||
// Load all variables
|
||||
@@ -234,15 +232,16 @@ Asset::LoadResult GameplayGlobals::load()
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
stream.ReadString(&name, 71);
|
||||
if (name.IsEmpty())
|
||||
{
|
||||
LOG(Warning, "Empty variable name");
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
auto& e = Variables[name];
|
||||
stream.ReadVariant(&e.DefaultValue);
|
||||
e.Value = e.DefaultValue;
|
||||
}
|
||||
if (stream.HasError())
|
||||
{
|
||||
// Failed to load data
|
||||
Variables.Clear();
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
|
||||
return LoadResult::Ok;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public:
|
||||
|
||||
// Rollback state and cancel
|
||||
_context = nullptr;
|
||||
_state = TaskState::Queued;
|
||||
SetState(TaskState::Queued);
|
||||
Cancel();
|
||||
}
|
||||
|
||||
@@ -148,8 +148,7 @@ protected:
|
||||
ASSERT(_context != nullptr);
|
||||
_context->OnCancelSync(this);
|
||||
_context = nullptr;
|
||||
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
|
||||
void GPUTask::Execute(GPUTasksContext* context)
|
||||
{
|
||||
// Begin
|
||||
ASSERT(IsQueued() && _context == nullptr);
|
||||
_state = TaskState::Running;
|
||||
SetState(TaskState::Running);
|
||||
|
||||
// Perform an operation
|
||||
const auto result = run(context);
|
||||
@@ -19,7 +18,7 @@ void GPUTask::Execute(GPUTasksContext* context)
|
||||
// Process result
|
||||
if (IsCancelRequested())
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else if (result != Result::Ok)
|
||||
{
|
||||
|
||||
@@ -941,7 +941,19 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
|
||||
}
|
||||
|
||||
// Animation events
|
||||
stream->WriteInt32(0);
|
||||
stream->WriteInt32(anim.Events.Count());
|
||||
for (auto& e : anim.Events)
|
||||
{
|
||||
stream->WriteString(e.First, 172);
|
||||
stream->WriteInt32(e.Second.GetKeyframes().Count());
|
||||
for (const auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
stream->WriteFloat(k.Time);
|
||||
stream->WriteFloat(k.Value.Duration);
|
||||
stream->WriteStringAnsi(k.Value.TypeName, 17);
|
||||
stream->WriteJson(k.Value.JsonData);
|
||||
}
|
||||
}
|
||||
|
||||
// Nested animations
|
||||
stream->WriteInt32(0);
|
||||
|
||||
@@ -13,4 +13,12 @@ namespace FlaxEngine
|
||||
Bit = bit;
|
||||
}
|
||||
}
|
||||
|
||||
public partial struct AntiAliasingSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not to show the TAA settings.
|
||||
/// </summary>
|
||||
public bool ShowTAASettings => (Mode == AntialiasingMode.TemporalAntialiasing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1888,25 +1888,25 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable
|
||||
/// <summary>
|
||||
/// The diameter (in texels) inside which jitter samples are spread. Smaller values result in crisper but more aliased output, while larger values result in more stable but blurrier output.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\")")
|
||||
API_FIELD(Attributes="Limit(0.1f, 1f, 0.001f), EditorOrder(1), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_JitterSpread), EditorDisplay(null, \"TAA Jitter Spread\"), VisibleIf(nameof(ShowTAASettings))")
|
||||
float TAA_JitterSpread = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of sharpening applied to the color buffer. TAA can induce a slight loss of details in high frequency regions. Sharpening alleviates this issue. High values may introduce dark-border artifacts.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\")")
|
||||
API_FIELD(Attributes="Limit(0, 3f, 0.001f), EditorOrder(2), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_Sharpness), EditorDisplay(null, \"TAA Sharpness\"), VisibleIf(nameof(ShowTAASettings))")
|
||||
float TAA_Sharpness = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// The blend coefficient for stationary fragments. Controls the percentage of history samples blended into the final color for fragments with minimal active motion.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\")")
|
||||
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(3), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_StationaryBlending), EditorDisplay(null, \"TAA Stationary Blending\"), VisibleIf(nameof(ShowTAASettings))")
|
||||
float TAA_StationaryBlending = 0.95f;
|
||||
|
||||
/// <summary>
|
||||
/// The blending coefficient for moving fragments. Controls the percentage of history samples blended into the final color for fragments with significant active motion.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\")")
|
||||
API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\"), VisibleIf(nameof(ShowTAASettings))")
|
||||
float TAA_MotionBlending = 0.85f;
|
||||
|
||||
public:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "GPUTimerQueryVulkan.h"
|
||||
#endif
|
||||
#include "DescriptorSetVulkan.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
|
||||
void CmdBufferVulkan::AddWaitSemaphore(VkPipelineStageFlags waitFlags, SemaphoreVulkan* waitSemaphore)
|
||||
@@ -32,7 +33,7 @@ void CmdBufferVulkan::Begin()
|
||||
// Acquire a descriptor pool set on
|
||||
if (_descriptorPoolSetContainer == nullptr)
|
||||
{
|
||||
_descriptorPoolSetContainer = &_device->DescriptorPoolsManager->AcquirePoolSetContainer();
|
||||
_descriptorPoolSetContainer = _device->DescriptorPoolsManager->AcquirePoolSetContainer();
|
||||
}
|
||||
|
||||
_state = State::IsInsideBegin;
|
||||
@@ -138,7 +139,7 @@ void CmdBufferVulkan::RefreshFenceStatus()
|
||||
|
||||
if (_descriptorPoolSetContainer)
|
||||
{
|
||||
_device->DescriptorPoolsManager->ReleasePoolSet(*_descriptorPoolSetContainer);
|
||||
_descriptorPoolSetContainer->LastFrameUsed = Engine::FrameCount;
|
||||
_descriptorPoolSetContainer = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -279,6 +280,7 @@ void CmdBufferManagerVulkan::WaitForCmdBuffer(CmdBufferVulkan* cmdBuffer, float
|
||||
void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ASSERT_LOW_LAYER(_activeCmdBuffer == nullptr)
|
||||
for (int32 i = 0; i < _pool._cmdBuffers.Count(); i++)
|
||||
{
|
||||
auto cmdBuffer = _pool._cmdBuffers.Get()[i];
|
||||
@@ -286,8 +288,7 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
|
||||
if (cmdBuffer->GetState() == CmdBufferVulkan::State::ReadyForBegin)
|
||||
{
|
||||
_activeCmdBuffer = cmdBuffer;
|
||||
_activeCmdBuffer->Begin();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -295,8 +296,12 @@ void CmdBufferManagerVulkan::PrepareForNewActiveCommandBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
// Always begin fresh command buffer for rendering
|
||||
_activeCmdBuffer = _pool.Create();
|
||||
if (_activeCmdBuffer == nullptr)
|
||||
{
|
||||
// Always begin fresh command buffer for rendering
|
||||
_activeCmdBuffer = _pool.Create();
|
||||
}
|
||||
|
||||
_activeCmdBuffer->Begin();
|
||||
|
||||
#if VULKAN_USE_QUERIES
|
||||
|
||||
@@ -247,8 +247,7 @@ void TypedDescriptorPoolSetVulkan::Reset()
|
||||
|
||||
DescriptorPoolSetContainerVulkan::DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device)
|
||||
: _device(device)
|
||||
, _lastFrameUsed(Engine::FrameCount)
|
||||
, _used(true)
|
||||
, LastFrameUsed(Engine::FrameCount)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -278,12 +277,6 @@ void DescriptorPoolSetContainerVulkan::Reset()
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorPoolSetContainerVulkan::SetUsed(bool used)
|
||||
{
|
||||
_used = used;
|
||||
_lastFrameUsed = used ? Engine::FrameCount : _lastFrameUsed;
|
||||
}
|
||||
|
||||
DescriptorPoolsManagerVulkan::DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device)
|
||||
: _device(device)
|
||||
{
|
||||
@@ -294,26 +287,21 @@ DescriptorPoolsManagerVulkan::~DescriptorPoolsManagerVulkan()
|
||||
_poolSets.ClearDelete();
|
||||
}
|
||||
|
||||
DescriptorPoolSetContainerVulkan& DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
|
||||
DescriptorPoolSetContainerVulkan* DescriptorPoolsManagerVulkan::AcquirePoolSetContainer()
|
||||
{
|
||||
ScopeLock lock(_locker);
|
||||
for (auto* poolSet : _poolSets)
|
||||
{
|
||||
if (poolSet->IsUnused())
|
||||
if (poolSet->Refs == 0)
|
||||
{
|
||||
poolSet->SetUsed(true);
|
||||
poolSet->LastFrameUsed = Engine::FrameCount;
|
||||
poolSet->Reset();
|
||||
return *poolSet;
|
||||
return poolSet;
|
||||
}
|
||||
}
|
||||
const auto poolSet = New<DescriptorPoolSetContainerVulkan>(_device);
|
||||
_poolSets.Add(poolSet);
|
||||
return *poolSet;
|
||||
}
|
||||
|
||||
void DescriptorPoolsManagerVulkan::ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet)
|
||||
{
|
||||
poolSet.SetUsed(false);
|
||||
return poolSet;
|
||||
}
|
||||
|
||||
void DescriptorPoolsManagerVulkan::GC()
|
||||
@@ -322,7 +310,7 @@ void DescriptorPoolsManagerVulkan::GC()
|
||||
for (int32 i = _poolSets.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
const auto poolSet = _poolSets[i];
|
||||
if (poolSet->IsUnused() && Engine::FrameCount - poolSet->GetLastFrameUsed() > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT)
|
||||
if (poolSet->Refs == 0 && Engine::FrameCount - poolSet->LastFrameUsed > VULKAN_RESOURCE_DELETE_SAFE_FRAMES_COUNT)
|
||||
{
|
||||
_poolSets.RemoveAt(i);
|
||||
Delete(poolSet);
|
||||
|
||||
@@ -212,8 +212,6 @@ class DescriptorPoolSetContainerVulkan
|
||||
private:
|
||||
GPUDeviceVulkan* _device;
|
||||
Dictionary<uint32, TypedDescriptorPoolSetVulkan*> _typedDescriptorPools;
|
||||
uint64 _lastFrameUsed;
|
||||
bool _used;
|
||||
|
||||
public:
|
||||
DescriptorPoolSetContainerVulkan(GPUDeviceVulkan* device);
|
||||
@@ -222,17 +220,9 @@ public:
|
||||
public:
|
||||
TypedDescriptorPoolSetVulkan* AcquireTypedPoolSet(const DescriptorSetLayoutVulkan& layout);
|
||||
void Reset();
|
||||
void SetUsed(bool used);
|
||||
|
||||
bool IsUnused() const
|
||||
{
|
||||
return !_used;
|
||||
}
|
||||
|
||||
uint64 GetLastFrameUsed() const
|
||||
{
|
||||
return _lastFrameUsed;
|
||||
}
|
||||
mutable uint64 Refs = 0;
|
||||
mutable uint64 LastFrameUsed;
|
||||
};
|
||||
|
||||
class DescriptorPoolsManagerVulkan
|
||||
@@ -246,8 +236,7 @@ public:
|
||||
DescriptorPoolsManagerVulkan(GPUDeviceVulkan* device);
|
||||
~DescriptorPoolsManagerVulkan();
|
||||
|
||||
DescriptorPoolSetContainerVulkan& AcquirePoolSetContainer();
|
||||
void ReleasePoolSet(DescriptorPoolSetContainerVulkan& poolSet);
|
||||
DescriptorPoolSetContainerVulkan* AcquirePoolSetContainer();
|
||||
void GC();
|
||||
};
|
||||
|
||||
|
||||
@@ -112,7 +112,11 @@ ComputePipelineStateVulkan::ComputePipelineStateVulkan(GPUDeviceVulkan* device,
|
||||
ComputePipelineStateVulkan::~ComputePipelineStateVulkan()
|
||||
{
|
||||
DSWriteContainer.Release();
|
||||
CurrentTypedDescriptorPoolSet = nullptr;
|
||||
if (CurrentTypedDescriptorPoolSet)
|
||||
{
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
|
||||
CurrentTypedDescriptorPoolSet = nullptr;
|
||||
}
|
||||
DescriptorSetsLayout = nullptr;
|
||||
DescriptorSetHandles.Resize(0);
|
||||
DynamicOffsets.Resize(0);
|
||||
@@ -206,7 +210,11 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass)
|
||||
void GPUPipelineStateVulkan::OnReleaseGPU()
|
||||
{
|
||||
DSWriteContainer.Release();
|
||||
CurrentTypedDescriptorPoolSet = nullptr;
|
||||
if (CurrentTypedDescriptorPoolSet)
|
||||
{
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
|
||||
CurrentTypedDescriptorPoolSet = nullptr;
|
||||
}
|
||||
DescriptorSetsLayout = nullptr;
|
||||
DescriptorSetHandles.Resize(0);
|
||||
DynamicOffsets.Resize(0);
|
||||
|
||||
@@ -41,17 +41,17 @@ public:
|
||||
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
|
||||
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
|
||||
{
|
||||
ASSERT(cmdBufferPoolSet);
|
||||
if (CurrentTypedDescriptorPoolSet)
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
|
||||
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool AllocateDescriptorSets()
|
||||
{
|
||||
ASSERT(CurrentTypedDescriptorPoolSet);
|
||||
return CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*DescriptorSetsLayout, DescriptorSetHandles.Get());
|
||||
}
|
||||
|
||||
@@ -165,7 +165,10 @@ public:
|
||||
DescriptorPoolSetContainerVulkan* cmdBufferPoolSet = cmdBuffer->GetDescriptorPoolSet();
|
||||
if (CurrentTypedDescriptorPoolSet == nullptr || CurrentTypedDescriptorPoolSet->GetOwner() != cmdBufferPoolSet)
|
||||
{
|
||||
if (CurrentTypedDescriptorPoolSet)
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs--;
|
||||
CurrentTypedDescriptorPoolSet = cmdBufferPoolSet->AcquireTypedPoolSet(*DescriptorSetsLayout);
|
||||
CurrentTypedDescriptorPoolSet->GetOwner()->Refs++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "Engine/Animations/Animations.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Core/Math/OrientedBoundingBox.h"
|
||||
#include "Engine/Core/Math/Matrix3x3.h"
|
||||
#include "Editor/Editor.h"
|
||||
#endif
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
@@ -1018,6 +1020,45 @@ void AnimatedModel::OnDebugDrawSelected()
|
||||
ModelInstanceActor::OnDebugDrawSelected();
|
||||
}
|
||||
|
||||
void AnimatedModel::OnDebugDraw()
|
||||
{
|
||||
if (ShowDebugDrawSkeleton && SkinnedModel && AnimationGraph)
|
||||
{
|
||||
if (GraphInstance.NodesPose.IsEmpty())
|
||||
PreInitSkinningData();
|
||||
Matrix world;
|
||||
GetLocalToWorldMatrix(world);
|
||||
|
||||
// Draw bounding box at the node locations
|
||||
const float boxSize = Math::Min(1.0f, (float)_sphere.Radius / 100.0f);
|
||||
OrientedBoundingBox localBox(Vector3(-boxSize), Vector3(boxSize));
|
||||
for (int32 nodeIndex = 0; nodeIndex < GraphInstance.NodesPose.Count(); nodeIndex++)
|
||||
{
|
||||
Matrix transform = GraphInstance.NodesPose[nodeIndex] * world;
|
||||
Float3 scale, translation;
|
||||
Matrix3x3 rotation;
|
||||
transform.Decompose(scale, rotation, translation);
|
||||
transform = Matrix::Invert(Matrix::Scaling(scale)) * transform;
|
||||
OrientedBoundingBox box = localBox * transform;
|
||||
DEBUG_DRAW_WIRE_BOX(box, Color::Green, 0, false);
|
||||
}
|
||||
|
||||
// Nodes connections
|
||||
for (int32 nodeIndex = 0; nodeIndex < SkinnedModel->Skeleton.Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
int32 parentIndex = SkinnedModel->Skeleton.Nodes[nodeIndex].ParentIndex;
|
||||
if (parentIndex != -1)
|
||||
{
|
||||
Float3 parentPos = (GraphInstance.NodesPose[parentIndex] * world).GetTranslation();
|
||||
Float3 bonePos = (GraphInstance.NodesPose[nodeIndex] * world).GetTranslation();
|
||||
DEBUG_DRAW_LINE(parentPos, bonePos, Color::Green, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModelInstanceActor::OnDebugDraw();
|
||||
}
|
||||
|
||||
BoundingBox AnimatedModel::GetEditorBox() const
|
||||
{
|
||||
if (SkinnedModel)
|
||||
|
||||
@@ -168,6 +168,13 @@ public:
|
||||
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
|
||||
ScriptingObjectReference<Actor> RootMotionTarget;
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// If checked, the skeleton pose will be shawn during debug shapes drawing.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skinned Model\")") bool ShowDebugDrawSkeleton = false;
|
||||
#endif
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The graph instance data container. For dynamic usage only at runtime, not serialized.
|
||||
@@ -416,6 +423,7 @@ public:
|
||||
void Draw(RenderContextBatch& renderContextBatch) override;
|
||||
#if USE_EDITOR
|
||||
void OnDebugDrawSelected() override;
|
||||
void OnDebugDraw() override;
|
||||
BoundingBox GetEditorBox() const override;
|
||||
#endif
|
||||
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
|
||||
|
||||
@@ -105,10 +105,8 @@ void NavMesh::OnDataAssetLoaded()
|
||||
if (Data.Tiles.HasItems())
|
||||
return;
|
||||
|
||||
const bool isEnabled = IsDuringPlay() && IsActiveInHierarchy();
|
||||
|
||||
// Remove added tiles
|
||||
if (isEnabled)
|
||||
if (_navMeshActive)
|
||||
{
|
||||
RemoveTiles();
|
||||
}
|
||||
@@ -120,7 +118,7 @@ void NavMesh::OnDataAssetLoaded()
|
||||
IsDataDirty = false;
|
||||
|
||||
// Add loaded tiles
|
||||
if (isEnabled)
|
||||
if (_navMeshActive)
|
||||
{
|
||||
AddTiles();
|
||||
}
|
||||
@@ -156,15 +154,36 @@ void NavMesh::OnEnable()
|
||||
// Base
|
||||
Actor::OnEnable();
|
||||
|
||||
GetScene()->Navigation.Meshes.Add(this);
|
||||
AddTiles();
|
||||
if (!_navMeshActive)
|
||||
{
|
||||
GetScene()->Navigation.Meshes.Add(this);
|
||||
AddTiles();
|
||||
_navMeshActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NavMesh::OnDisable()
|
||||
{
|
||||
RemoveTiles();
|
||||
GetScene()->Navigation.Meshes.Remove(this);
|
||||
if (_navMeshActive)
|
||||
{
|
||||
RemoveTiles();
|
||||
GetScene()->Navigation.Meshes.Remove(this);
|
||||
_navMeshActive = false;
|
||||
}
|
||||
|
||||
// Base
|
||||
Actor::OnDisable();
|
||||
}
|
||||
|
||||
void NavMesh::Initialize()
|
||||
{
|
||||
// Base
|
||||
Actor::Initialize();
|
||||
|
||||
if (!_navMeshActive && IsActiveInHierarchy())
|
||||
{
|
||||
GetScene()->Navigation.Meshes.Add(this);
|
||||
AddTiles();
|
||||
_navMeshActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ private:
|
||||
void RemoveTiles();
|
||||
void OnDataAssetLoaded();
|
||||
|
||||
private:
|
||||
bool _navMeshActive = false;
|
||||
|
||||
public:
|
||||
// [Actor]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
@@ -77,4 +80,5 @@ protected:
|
||||
// [Actor]
|
||||
void OnEnable() override;
|
||||
void OnDisable() override;
|
||||
void Initialize() override;
|
||||
};
|
||||
|
||||
@@ -55,13 +55,13 @@ public:
|
||||
{
|
||||
return __sync_fetch_and_add(dst, value);
|
||||
}
|
||||
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
|
||||
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
|
||||
{
|
||||
int32 result;
|
||||
__atomic_load(dst, &result, __ATOMIC_RELAXED);
|
||||
return result;
|
||||
}
|
||||
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
|
||||
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
|
||||
{
|
||||
int64 result;
|
||||
__atomic_load(dst, &result, __ATOMIC_RELAXED);
|
||||
|
||||
@@ -45,21 +45,21 @@ public:
|
||||
{
|
||||
return __sync_fetch_and_add(dst, value);
|
||||
}
|
||||
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
|
||||
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
|
||||
{
|
||||
return __atomic_load_n(dst, __ATOMIC_RELAXED);
|
||||
}
|
||||
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
|
||||
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
|
||||
{
|
||||
return __atomic_load_n(dst, __ATOMIC_RELAXED);
|
||||
}
|
||||
FORCE_INLINE static void AtomicStore(int32 volatile* dst, int32 value)
|
||||
{
|
||||
__atomic_store(dst, &value, __ATOMIC_SEQ_CST);
|
||||
__atomic_store_n((volatile int32*)dst, value, __ATOMIC_RELAXED);
|
||||
}
|
||||
FORCE_INLINE static void AtomicStore(int64 volatile* dst, int64 value)
|
||||
{
|
||||
__atomic_store(dst, &value, __ATOMIC_SEQ_CST);
|
||||
__atomic_store_n((volatile int64*)dst, value, __ATOMIC_RELAXED);
|
||||
}
|
||||
FORCE_INLINE static void Prefetch(void const* ptr)
|
||||
{
|
||||
|
||||
@@ -270,14 +270,14 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="dst">A pointer to the destination value.</param>
|
||||
/// <returns>The function returns the value of the destination parameter.</returns>
|
||||
static int32 AtomicRead(int32 volatile* dst) = delete;
|
||||
static int32 AtomicRead(int32 const volatile* dst) = delete;
|
||||
|
||||
/// <summary>
|
||||
/// Performs an atomic 64-bit variable read operation on the specified values.
|
||||
/// </summary>
|
||||
/// <param name="dst">A pointer to the destination value.</param>
|
||||
/// <returns>The function returns the value of the destination parameter.</returns>
|
||||
static int64 AtomicRead(int64 volatile* dst) = delete;
|
||||
static int64 AtomicRead(int64 const volatile* dst) = delete;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a 32-bit variable to the specified value as an atomic operation.
|
||||
|
||||
@@ -69,13 +69,13 @@ public:
|
||||
{
|
||||
return __sync_fetch_and_add(dst, value);
|
||||
}
|
||||
FORCE_INLINE static int32 AtomicRead(int32 volatile* dst)
|
||||
FORCE_INLINE static int32 AtomicRead(int32 const volatile* dst)
|
||||
{
|
||||
int32 result;
|
||||
__atomic_load(dst, &result, __ATOMIC_SEQ_CST);
|
||||
return result;
|
||||
}
|
||||
FORCE_INLINE static int64 AtomicRead(int64 volatile* dst)
|
||||
FORCE_INLINE static int64 AtomicRead(int64 const volatile* dst)
|
||||
{
|
||||
int64 result;
|
||||
__atomic_load(dst, &result, __ATOMIC_SEQ_CST);
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
/// <summary>
|
||||
/// Locks the critical section.
|
||||
/// </summary>
|
||||
void Lock() const
|
||||
NO_SANITIZE_THREAD void Lock() const
|
||||
{
|
||||
pthread_mutex_lock(_mutexPtr);
|
||||
#if BUILD_DEBUG
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
/// Attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section.
|
||||
/// </summary>
|
||||
/// <returns>True if calling thread took ownership of the critical section.</returns>
|
||||
bool TryLock() const
|
||||
NO_SANITIZE_THREAD bool TryLock() const
|
||||
{
|
||||
return pthread_mutex_trylock(_mutexPtr) == 0;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
/// <summary>
|
||||
/// Releases the lock on the critical section.
|
||||
/// </summary>
|
||||
void Unlock() const
|
||||
NO_SANITIZE_THREAD void Unlock() const
|
||||
{
|
||||
#if BUILD_DEBUG
|
||||
((UnixCriticalSection*)this)->_owningThreadId = 0;
|
||||
|
||||
@@ -63,13 +63,13 @@ public:
|
||||
return _interlockedexchangeadd64(dst, value);
|
||||
#endif
|
||||
}
|
||||
static int32 AtomicRead(int32 volatile* dst)
|
||||
static int32 AtomicRead(int32 const volatile* dst)
|
||||
{
|
||||
return (int32)_InterlockedCompareExchange((long volatile*)dst, 0, 0);
|
||||
}
|
||||
static int64 AtomicRead(int64 volatile* dst)
|
||||
static int64 AtomicRead(int64 const volatile* dst)
|
||||
{
|
||||
return _InterlockedCompareExchange64(dst, 0, 0);
|
||||
return _InterlockedCompareExchange64((int64 volatile*)dst, 0, 0);
|
||||
}
|
||||
static void AtomicStore(int32 volatile* dst, int32 value)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace
|
||||
// Cached data for the draw calls sorting
|
||||
Array<uint64> SortingKeys[2];
|
||||
Array<int32> SortingIndices;
|
||||
Array<DrawBatch> SortingBatches;
|
||||
Array<RenderList*> FreeRenderList;
|
||||
|
||||
struct MemPoolEntry
|
||||
@@ -594,12 +595,13 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass)
|
||||
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass, bool stable)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
const auto* drawCallsData = drawCalls.Get();
|
||||
const auto* listData = list.Indices.Get();
|
||||
const int32 listSize = list.Indices.Count();
|
||||
ZoneValue(listSize);
|
||||
|
||||
// Peek shared memory
|
||||
#define PREPARE_CACHE(list) (list).Clear(); (list).Resize(listSize)
|
||||
@@ -656,6 +658,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
|
||||
}
|
||||
|
||||
DrawBatch batch;
|
||||
static_assert(sizeof(DrawBatch) == sizeof(uint64) * 2, "Fix the size of draw batch to optimize memory access.");
|
||||
batch.SortKey = sortedKeys[i];
|
||||
batch.StartIndex = i;
|
||||
batch.BatchSize = batchSize;
|
||||
@@ -665,8 +668,12 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
|
||||
i += batchSize;
|
||||
}
|
||||
|
||||
// Sort draw calls batches by depth
|
||||
Sorting::QuickSort(list.Batches);
|
||||
// When using depth buffer draw calls are already almost ideally sorted by Radix Sort but transparency needs more stability to prevent flickering
|
||||
if (stable)
|
||||
{
|
||||
// Sort draw calls batches by depth
|
||||
Sorting::MergeSort(list.Batches, &SortingBatches);
|
||||
}
|
||||
}
|
||||
|
||||
FORCE_INLINE bool CanUseInstancing(DrawPass pass)
|
||||
|
||||
@@ -217,17 +217,17 @@ struct DrawBatch
|
||||
/// <summary>
|
||||
/// The first draw call index.
|
||||
/// </summary>
|
||||
int32 StartIndex;
|
||||
uint16 StartIndex;
|
||||
|
||||
/// <summary>
|
||||
/// A number of draw calls to be submitted at once.
|
||||
/// </summary>
|
||||
int32 BatchSize;
|
||||
uint16 BatchSize;
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of instances (sum from all draw calls in this batch).
|
||||
/// </summary>
|
||||
int32 InstanceCount;
|
||||
uint32 InstanceCount;
|
||||
|
||||
bool operator<(const DrawBatch& other) const
|
||||
{
|
||||
@@ -525,7 +525,8 @@ public:
|
||||
/// <param name="pass">The draw pass (optional).</param>
|
||||
API_FUNCTION() FORCE_INLINE void SortDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, bool reverseDistance, DrawCallsListType listType, DrawPass pass = DrawPass::All)
|
||||
{
|
||||
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass);
|
||||
const bool stable = listType == DrawCallsListType::Forward;
|
||||
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass, stable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -536,7 +537,8 @@ public:
|
||||
/// <param name="list">The collected draw calls indices list.</param>
|
||||
/// <param name="drawCalls">The collected draw calls list.</param>
|
||||
/// <param name="pass">The draw pass (optional).</param>
|
||||
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass = DrawPass::All);
|
||||
/// <param name="stable">If set to <c>true</c> draw batches will be additionally sorted to prevent any flickering, otherwise Depth Buffer will smooth out any non-stability in sorting.</param>
|
||||
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass = DrawPass::All, bool stable = false);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the collected draw calls.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "BinaryModule.h"
|
||||
#include "ScriptingObject.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Utilities.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "ManagedCLR/MAssembly.h"
|
||||
@@ -436,6 +437,7 @@ void ScriptingType::SetupScriptVTable(ScriptingTypeHandle baseTypeHandle)
|
||||
}
|
||||
}
|
||||
|
||||
NO_SANITIZE_ADDRESS
|
||||
void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle baseTypeHandle, int32 wrapperIndex)
|
||||
{
|
||||
// Analyze vtable size
|
||||
@@ -475,7 +477,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
|
||||
|
||||
// Duplicate vtable
|
||||
Script.VTable = (void**)((byte*)Platform::Allocate(totalSize, 16) + prefixSize);
|
||||
Platform::MemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
|
||||
Utilities::UnsafeMemoryCopy((byte*)Script.VTable - prefixSize, (byte*)vtable - prefixSize, prefixSize + size);
|
||||
|
||||
// Override vtable entries
|
||||
if (interfacesCount)
|
||||
@@ -508,7 +510,7 @@ void ScriptingType::SetupScriptObjectVTable(void* object, ScriptingTypeHandle ba
|
||||
const int32 interfaceSize = interfaceCount * sizeof(void*);
|
||||
|
||||
// Duplicate interface vtable
|
||||
Platform::MemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
|
||||
Utilities::UnsafeMemoryCopy((byte*)Script.VTable + interfaceOffset, (byte*)vtableInterface - prefixSize, prefixSize + interfaceSize);
|
||||
|
||||
// Override interface vtable entries
|
||||
const auto scriptOffset = interfaces->ScriptVTableOffset;
|
||||
|
||||
@@ -497,7 +497,9 @@ void ReadStream::Read(Variant& data)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CRASH;
|
||||
_hasError = true;
|
||||
LOG(Error, "Invalid Variant type. Corrupted data.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,6 +948,13 @@ void WriteStream::WriteJson(ISerializable* obj, const void* otherObj)
|
||||
WriteInt32(0);
|
||||
}
|
||||
|
||||
void WriteStream::WriteJson(const StringAnsiView& json)
|
||||
{
|
||||
WriteInt32(FLAXENGINE_VERSION_BUILD);
|
||||
WriteInt32((int32)json.Length());
|
||||
WriteBytes((byte*)json.Get(), (int32)json.Length());
|
||||
}
|
||||
|
||||
void WriteStream::WriteString(const StringView& data)
|
||||
{
|
||||
Write(data);
|
||||
|
||||
@@ -233,6 +233,7 @@ public:
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
/// <param name="otherObj">The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.</param>
|
||||
void WriteJson(ISerializable* obj, const void* otherObj = nullptr);
|
||||
void WriteJson(const StringAnsiView& json);
|
||||
|
||||
public:
|
||||
// Writes String to the stream
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
void Task::Start()
|
||||
{
|
||||
if (_state != TaskState::Created)
|
||||
if (GetState() != TaskState::Created)
|
||||
return;
|
||||
|
||||
OnStart();
|
||||
|
||||
// Change state
|
||||
_state = TaskState::Queued;
|
||||
SetState(TaskState::Queued);
|
||||
|
||||
// Add task to the execution queue
|
||||
Enqueue();
|
||||
@@ -110,7 +110,6 @@ Task* Task::ContinueWith(const Function<bool()>& action, Object* target)
|
||||
Task* Task::StartNew(Task* task)
|
||||
{
|
||||
ASSERT(task);
|
||||
|
||||
task->Start();
|
||||
return task;
|
||||
}
|
||||
@@ -137,11 +136,10 @@ Task* Task::StartNew(Function<bool()>::Signature& action, Object* target)
|
||||
|
||||
void Task::Execute()
|
||||
{
|
||||
// Begin
|
||||
if (IsCanceled())
|
||||
return;
|
||||
ASSERT(IsQueued());
|
||||
_state = TaskState::Running;
|
||||
SetState(TaskState::Running);
|
||||
|
||||
// Perform an operation
|
||||
bool failed = Run();
|
||||
@@ -149,7 +147,7 @@ void Task::Execute()
|
||||
// Process result
|
||||
if (IsCancelRequested())
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
SetState(TaskState::Canceled);
|
||||
}
|
||||
else if (failed)
|
||||
{
|
||||
@@ -167,10 +165,8 @@ void Task::OnStart()
|
||||
|
||||
void Task::OnFinish()
|
||||
{
|
||||
ASSERT(IsRunning());
|
||||
ASSERT(!IsCancelRequested());
|
||||
|
||||
_state = TaskState::Finished;
|
||||
ASSERT(IsRunning() && !IsCancelRequested());
|
||||
SetState(TaskState::Finished);
|
||||
|
||||
// Send event further
|
||||
if (_continueWith)
|
||||
@@ -181,7 +177,7 @@ void Task::OnFinish()
|
||||
|
||||
void Task::OnFail()
|
||||
{
|
||||
_state = TaskState::Failed;
|
||||
SetState(TaskState::Failed);
|
||||
|
||||
// Send event further
|
||||
if (_continueWith)
|
||||
@@ -209,8 +205,7 @@ void Task::OnCancel()
|
||||
const auto state = GetState();
|
||||
if (state != TaskState::Finished && state != TaskState::Failed)
|
||||
{
|
||||
_state = TaskState::Canceled;
|
||||
|
||||
SetState(TaskState::Canceled);
|
||||
OnEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class FLAXENGINE_API Task : public Object, public NonCopyable
|
||||
//
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// The cancel flag used to indicate that there is request to cancel task operation.
|
||||
/// </summary>
|
||||
@@ -65,14 +64,18 @@ protected:
|
||||
/// </summary>
|
||||
Task* _continueWith = nullptr;
|
||||
|
||||
public:
|
||||
void SetState(TaskState state)
|
||||
{
|
||||
Platform::AtomicStore((int64 volatile*)&_state, (uint64)state);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the task state.
|
||||
/// </summary>
|
||||
FORCE_INLINE TaskState GetState() const
|
||||
{
|
||||
return static_cast<TaskState>(Platform::AtomicRead((int64 volatile*)&_state));
|
||||
return (TaskState)Platform::AtomicRead((int64 const volatile*)&_state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,7 +97,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Checks if operation failed.
|
||||
/// </summary>
|
||||
@@ -153,7 +155,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Starts this task execution (and will continue with all children).
|
||||
/// </summary>
|
||||
@@ -199,7 +200,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Continues that task execution with a given task (will call Start on given task after finishing that one).
|
||||
/// </summary>
|
||||
@@ -232,7 +232,6 @@ public:
|
||||
Task* ContinueWith(const Function<bool()>& action, Object* target = nullptr);
|
||||
|
||||
public:
|
||||
|
||||
/// <summary>
|
||||
/// Starts the new task.
|
||||
/// </summary>
|
||||
@@ -312,7 +311,6 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/// <summary>
|
||||
/// Executes this task.
|
||||
/// It should be called by the task consumer (thread pool or other executor of this task type).
|
||||
@@ -328,7 +326,6 @@ protected:
|
||||
virtual bool Run() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
virtual void Enqueue() = 0;
|
||||
virtual void OnStart();
|
||||
virtual void OnFinish();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
#include "Engine/Utilities/AnsiPathTempFile.h"
|
||||
|
||||
// Import Assimp library
|
||||
// Source: https://github.com/assimp/assimp
|
||||
@@ -157,7 +158,7 @@ struct AssimpImporterData
|
||||
Array<AssimpBone> Bones;
|
||||
Dictionary<int32, Array<int32>> MeshIndexToNodeIndex;
|
||||
|
||||
AssimpImporterData(const char* path, const ModelTool::Options& options)
|
||||
AssimpImporterData(const StringView& path, const ModelTool::Options& options)
|
||||
: Path(path)
|
||||
, Options(options)
|
||||
{
|
||||
@@ -735,7 +736,7 @@ void ImportAnimation(int32 index, ModelData& data, AssimpImporterData& importerD
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
static bool AssimpInited = false;
|
||||
if (!AssimpInited)
|
||||
@@ -784,7 +785,10 @@ bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& opt
|
||||
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
|
||||
|
||||
// Import file
|
||||
context.Scene = context.AssimpImporter.ReadFile(path, flags);
|
||||
{
|
||||
AnsiPathTempFile tempFile(path);
|
||||
context.Scene = context.AssimpImporter.ReadFile(tempFile.Path.Get(), flags);
|
||||
}
|
||||
if (context.Scene == nullptr)
|
||||
{
|
||||
LOG_STR(Warning, String(context.AssimpImporter.GetErrorString()));
|
||||
|
||||
@@ -816,7 +816,7 @@ void BakeTransforms(FbxScene* scene)
|
||||
scene->GetRootNode()->ConvertPivotAnimationRecursive(nullptr, FbxNode::eDestinationPivot, frameRate, false);
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& data, Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataAutodeskFbxSdk(const String& path, ImportedModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
ScopeLock lock(FbxSdkManager::Locker);
|
||||
|
||||
@@ -836,7 +836,7 @@ bool ModelTool::ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& da
|
||||
auto ios = FbxSdkManager::Manager->GetIOSettings();
|
||||
ios->SetBoolProp(IMP_FBX_MODEL, importMeshes);
|
||||
ios->SetBoolProp(IMP_FBX_ANIMATION, importAnimations);
|
||||
if (!importer->Initialize(path, -1, ios))
|
||||
if (!importer->Initialize(StringAnsi(path), -1, ios))
|
||||
{
|
||||
errorMsg = String::Format(TEXT("Failed to initialize FBX importer. {0}"), String(importer->GetStatus().GetErrorString()));
|
||||
return false;
|
||||
|
||||
@@ -103,7 +103,7 @@ struct OpenFbxImporterData
|
||||
Array<const ofbx::Material*> Materials;
|
||||
Array<MaterialSlotEntry> ImportedMaterials;
|
||||
|
||||
OpenFbxImporterData(const char* path, const ModelTool::Options& options, ofbx::IScene* scene)
|
||||
OpenFbxImporterData(const String& path, const ModelTool::Options& options, ofbx::IScene* scene)
|
||||
: Scene(scene)
|
||||
, ScenePtr(scene)
|
||||
, Path(path)
|
||||
@@ -1114,11 +1114,11 @@ static Float3 FbxVectorFromAxisAndSign(int axis, int sign)
|
||||
return { 0.f, 0.f, 0.f };
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg)
|
||||
bool ModelTool::ImportDataOpenFBX(const String& path, ModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
// Import file
|
||||
Array<byte> fileData;
|
||||
if (File::ReadAllBytes(String(path), fileData))
|
||||
if (File::ReadAllBytes(path, fileData))
|
||||
{
|
||||
errorMsg = TEXT("Cannot load file.");
|
||||
return true;
|
||||
|
||||
@@ -471,64 +471,37 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
|
||||
options.MergeMeshes = false; // Meshes merging doesn't make sense when we want to import each mesh individually
|
||||
// TODO: maybe we could update meshes merger to collapse meshes within the same name if splitting is enabled?
|
||||
|
||||
// Validate path
|
||||
// Note: Assimp/Autodesk supports only ANSI characters in imported file path
|
||||
StringAnsi importPath;
|
||||
String tmpPath;
|
||||
if (path.IsANSI() == false)
|
||||
{
|
||||
// Use temporary file
|
||||
LOG(Warning, "Model Tool doesn't support importing files from paths using non ASNI characters. Using temporary file.");
|
||||
FileSystem::GetTempFilePath(tmpPath);
|
||||
if (tmpPath.IsANSI() == false || FileSystem::CopyFile(tmpPath, path))
|
||||
{
|
||||
errorMsg = TEXT("Path with non ANSI characters is invalid.");
|
||||
return true;
|
||||
}
|
||||
importPath = tmpPath.ToStringAnsi();
|
||||
}
|
||||
else
|
||||
{
|
||||
importPath = path.ToStringAnsi();
|
||||
}
|
||||
|
||||
// Call importing backend
|
||||
#if (USE_AUTODESK_FBX_SDK || USE_OPEN_FBX) && USE_ASSIMP
|
||||
if (path.EndsWith(TEXT(".fbx"), StringSearchCase::IgnoreCase))
|
||||
{
|
||||
#if USE_AUTODESK_FBX_SDK
|
||||
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
|
||||
return true;
|
||||
#elif USE_OPEN_FBX
|
||||
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataOpenFBX(path, data, options, errorMsg))
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataAssimp(path, data, options, errorMsg))
|
||||
return true;
|
||||
}
|
||||
#elif USE_ASSIMP
|
||||
if (ImportDataAssimp(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataAssimp(path, data, options, errorMsg))
|
||||
return true;
|
||||
#elif USE_AUTODESK_FBX_SDK
|
||||
if (ImportDataAutodeskFbxSdk(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataAutodeskFbxSdk(path, data, options, errorMsg))
|
||||
return true;
|
||||
#elif USE_OPEN_FBX
|
||||
if (ImportDataOpenFBX(importPath.Get(), data, options, errorMsg))
|
||||
if (ImportDataOpenFBX(path, data, options, errorMsg))
|
||||
return true;
|
||||
#else
|
||||
LOG(Error, "Compiled without model importing backend.");
|
||||
return true;
|
||||
#endif
|
||||
|
||||
// Remove temporary file
|
||||
if (tmpPath.HasChars() && FileSystem::FileExists(tmpPath))
|
||||
{
|
||||
FileSystem::DeleteFile(tmpPath);
|
||||
}
|
||||
|
||||
// Remove namespace prefixes from the nodes names
|
||||
{
|
||||
for (auto& node : data.Nodes)
|
||||
@@ -1248,7 +1221,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
}
|
||||
|
||||
// Apply the import transformation
|
||||
if (!importTransform.IsIdentity())
|
||||
if (!importTransform.IsIdentity() && data.Nodes.HasItems())
|
||||
{
|
||||
if (options.Type == ModelType::SkinnedModel)
|
||||
{
|
||||
|
||||
@@ -386,13 +386,13 @@ public:
|
||||
private:
|
||||
static void CalculateBoneOffsetMatrix(const Array<SkeletonNode>& nodes, Matrix& offsetMatrix, int32 nodeIndex);
|
||||
#if USE_ASSIMP
|
||||
static bool ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg);
|
||||
static bool ImportDataAssimp(const String& path, ModelData& data, Options& options, String& errorMsg);
|
||||
#endif
|
||||
#if USE_AUTODESK_FBX_SDK
|
||||
static bool ImportDataAutodeskFbxSdk(const char* path, ModelData& data, Options& options, String& errorMsg);
|
||||
static bool ImportDataAutodeskFbxSdk(const String& path, ModelData& data, Options& options, String& errorMsg);
|
||||
#endif
|
||||
#if USE_OPEN_FBX
|
||||
static bool ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg);
|
||||
static bool ImportDataOpenFBX(const String& path, ModelData& data, Options& options, String& errorMsg);
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ public class TextureTool : EngineModule
|
||||
|
||||
bool useDirectXTex = false;
|
||||
bool useStb = false;
|
||||
bool useExr = options.Target.IsEditor;
|
||||
|
||||
switch (options.Platform.Target)
|
||||
{
|
||||
@@ -58,6 +59,10 @@ public class TextureTool : EngineModule
|
||||
options.PrivateDependencies.Add("bc7enc16");
|
||||
}
|
||||
}
|
||||
if (useExr)
|
||||
{
|
||||
options.PrivateDependencies.Add("tinyexr");
|
||||
}
|
||||
if (options.Target.IsEditor && astc.IsSupported(options))
|
||||
{
|
||||
// ASTC for mobile (iOS and Android)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#endif
|
||||
#include "Engine/Utilities/AnsiPathTempFile.h"
|
||||
|
||||
// Import DirectXTex library
|
||||
// Source: https://github.com/Microsoft/DirectXTex
|
||||
@@ -24,6 +25,19 @@ DECLARE_HANDLE(HMONITOR);
|
||||
#endif
|
||||
#include <ThirdParty/DirectXTex/DirectXTex.h>
|
||||
|
||||
#if USE_EDITOR
|
||||
// Import tinyexr library
|
||||
// Source: https://github.com/syoyo/tinyexr
|
||||
#define TINYEXR_IMPLEMENTATION
|
||||
#define TINYEXR_USE_MINIZ 1
|
||||
#define TINYEXR_USE_STB_ZLIB 0
|
||||
#define TINYEXR_USE_THREAD 0
|
||||
#define TINYEXR_USE_OPENMP 0
|
||||
#undef min
|
||||
#undef max
|
||||
#include <ThirdParty/tinyexr/tinyexr.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
FORCE_INLINE PixelFormat ToPixelFormat(const DXGI_FORMAT format)
|
||||
@@ -36,7 +50,7 @@ namespace
|
||||
return static_cast<DXGI_FORMAT>(format);
|
||||
}
|
||||
|
||||
HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, float threshold, DirectX::ScratchImage& cImages)
|
||||
HRESULT Compress(const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DirectX::TEX_COMPRESS_FLAGS compress, float threshold, DirectX::ScratchImage& cImages)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
if ((format == DXGI_FORMAT_BC7_UNORM || format == DXGI_FORMAT_BC7_UNORM_SRGB || format == DXGI_FORMAT_BC6H_UF16 || format == DXGI_FORMAT_BC6H_SF16) &&
|
||||
@@ -60,12 +74,12 @@ namespace
|
||||
size_t _nimages;
|
||||
const DirectX::TexMetadata& _metadata;
|
||||
DXGI_FORMAT _format;
|
||||
DWORD _compress;
|
||||
DirectX::TEX_COMPRESS_FLAGS _compress;
|
||||
DirectX::ScratchImage& _cImages;
|
||||
public:
|
||||
HRESULT CompressResult = E_FAIL;
|
||||
|
||||
GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DWORD compress, DirectX::ScratchImage& cImages)
|
||||
GPUCompressTask(ConditionVariable& signal, const DirectX::Image* srcImages, size_t nimages, const DirectX::TexMetadata& metadata, DXGI_FORMAT format, DirectX::TEX_COMPRESS_FLAGS compress, DirectX::ScratchImage& cImages)
|
||||
: GPUTask(Type::Custom)
|
||||
, _signal(&signal)
|
||||
, _srcImages(srcImages)
|
||||
@@ -276,6 +290,46 @@ HRESULT LoadFromRAWFile(const StringView& path, DirectX::ScratchImage& image)
|
||||
return image.InitializeFromImage(img);
|
||||
}
|
||||
|
||||
HRESULT LoadFromEXRFile(const StringView& path, DirectX::ScratchImage& image)
|
||||
{
|
||||
#if USE_EDITOR
|
||||
// Load exr file
|
||||
AnsiPathTempFile tempFile(path);
|
||||
float* pixels;
|
||||
int width, height;
|
||||
const char* err = nullptr;
|
||||
int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
|
||||
if (ret != TINYEXR_SUCCESS)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
LOG_STR(Warning, String(err));
|
||||
FreeEXRErrorMessage(err);
|
||||
}
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Setup image
|
||||
DirectX::Image img;
|
||||
img.format = DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
img.width = width;
|
||||
img.height = height;
|
||||
img.rowPitch = width * sizeof(Float4);
|
||||
img.slicePitch = img.rowPitch * height;
|
||||
|
||||
// Link data
|
||||
img.pixels = (uint8_t*)pixels;
|
||||
|
||||
// Init
|
||||
HRESULT result = image.InitializeFromImage(img);
|
||||
free(pixels);
|
||||
return result;
|
||||
#else
|
||||
LOG(Warning, "EXR format is not supported.");
|
||||
return S_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha)
|
||||
{
|
||||
// Load image data
|
||||
@@ -302,6 +356,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
|
||||
case ImageType::RAW:
|
||||
result = LoadFromRAWFile(path, image);
|
||||
break;
|
||||
case ImageType::EXR:
|
||||
result = LoadFromEXRFile(path, image);
|
||||
break;
|
||||
default:
|
||||
result = DXGI_ERROR_INVALID_CALL;
|
||||
break;
|
||||
@@ -518,6 +575,9 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
|
||||
case ImageType::RAW:
|
||||
result = LoadFromRAWFile(path, image1);
|
||||
break;
|
||||
case ImageType::EXR:
|
||||
result = LoadFromEXRFile(path, image1);
|
||||
break;
|
||||
case ImageType::Internal:
|
||||
{
|
||||
if (options.InternalLoad.IsBinded())
|
||||
@@ -688,7 +748,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
|
||||
if (!keepAsIs && options.FlipY)
|
||||
{
|
||||
auto& tmpImg = GET_TMP_IMG();
|
||||
DWORD flags = DirectX::TEX_FR_FLIP_VERTICAL;
|
||||
DirectX::TEX_FR_FLAGS flags = DirectX::TEX_FR_FLIP_VERTICAL;
|
||||
result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg);
|
||||
if (FAILED(result))
|
||||
{
|
||||
@@ -698,7 +758,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
|
||||
SET_CURRENT_IMG(tmpImg);
|
||||
}
|
||||
|
||||
// Check if it invert green channel
|
||||
// Check if invert green channel
|
||||
if (!keepAsIs && options.InvertGreenChannel)
|
||||
{
|
||||
auto& timage = GET_TMP_IMG();
|
||||
|
||||
@@ -767,6 +767,10 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type)
|
||||
{
|
||||
type = ImageType::RAW;
|
||||
}
|
||||
else if (extension == TEXT("exr"))
|
||||
{
|
||||
type = ImageType::EXR;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(Warning, "Unknown file type.");
|
||||
|
||||
@@ -252,6 +252,7 @@ private:
|
||||
JPEG,
|
||||
HDR,
|
||||
RAW,
|
||||
EXR,
|
||||
Internal,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Utilities/AnsiPathTempFile.h"
|
||||
#include "Engine/Platform/File.h"
|
||||
|
||||
#define STBI_ASSERT(x) ASSERT(x)
|
||||
@@ -48,6 +49,18 @@
|
||||
// Compression libs for Editor
|
||||
#include <ThirdParty/detex/detex.h>
|
||||
#include <ThirdParty/bc7enc16/bc7enc16.h>
|
||||
|
||||
// Import tinyexr library
|
||||
// Source: https://github.com/syoyo/tinyexr
|
||||
#define TINYEXR_IMPLEMENTATION
|
||||
#define TINYEXR_USE_MINIZ 1
|
||||
#define TINYEXR_USE_STB_ZLIB 0
|
||||
#define TINYEXR_USE_THREAD 0
|
||||
#define TINYEXR_USE_OPENMP 0
|
||||
#undef min
|
||||
#undef max
|
||||
#include <ThirdParty/tinyexr/tinyexr.h>
|
||||
|
||||
#endif
|
||||
|
||||
static void stbWrite(void* context, void* data, int size)
|
||||
@@ -173,7 +186,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
{
|
||||
if (textureData.GetArraySize() != 1)
|
||||
{
|
||||
LOG(Warning, "Exporting texture arrays and cubemaps is not supported by stb library.");
|
||||
LOG(Warning, "Exporting texture arrays and cubemaps is not supported.");
|
||||
}
|
||||
TextureData const* texture = &textureData;
|
||||
|
||||
@@ -189,7 +202,7 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
const auto sampler = GetSampler(texture->Format);
|
||||
if (sampler == nullptr)
|
||||
{
|
||||
LOG(Warning, "Texture data format {0} is not supported by stb library.", (int32)textureData.Format);
|
||||
LOG(Warning, "Texture data format {0} is not supported.", (int32)textureData.Format);
|
||||
return true;
|
||||
}
|
||||
const auto srcData = texture->GetData(0, 0);
|
||||
@@ -272,16 +285,19 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
|
||||
break;
|
||||
}
|
||||
case ImageType::GIF:
|
||||
LOG(Warning, "GIF format is not supported by stb library.");
|
||||
LOG(Warning, "GIF format is not supported.");
|
||||
break;
|
||||
case ImageType::TIFF:
|
||||
LOG(Warning, "GIF format is not supported by stb library.");
|
||||
LOG(Warning, "GIF format is not supported.");
|
||||
break;
|
||||
case ImageType::DDS:
|
||||
LOG(Warning, "DDS format is not supported by stb library.");
|
||||
LOG(Warning, "DDS format is not supported.");
|
||||
break;
|
||||
case ImageType::RAW:
|
||||
LOG(Warning, "RAW format is not supported by stb library.");
|
||||
LOG(Warning, "RAW format is not supported.");
|
||||
break;
|
||||
case ImageType::EXR:
|
||||
LOG(Warning, "EXR format is not supported.");
|
||||
break;
|
||||
default:
|
||||
LOG(Warning, "Unknown format.");
|
||||
@@ -383,11 +399,49 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
|
||||
|
||||
break;
|
||||
}
|
||||
case ImageType::EXR:
|
||||
{
|
||||
#if USE_EDITOR
|
||||
// Load exr file
|
||||
AnsiPathTempFile tempFile(path);
|
||||
float* pixels;
|
||||
int width, height;
|
||||
const char* err = nullptr;
|
||||
int ret = LoadEXR(&pixels, &width, &height, tempFile.Path.Get(), &err);
|
||||
if (ret != TINYEXR_SUCCESS)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
LOG_STR(Warning, String(err));
|
||||
FreeEXRErrorMessage(err);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Setup texture data
|
||||
textureData.Width = width;
|
||||
textureData.Height = height;
|
||||
textureData.Depth = 1;
|
||||
textureData.Format = PixelFormat::R32G32B32A32_Float;
|
||||
textureData.Items.Resize(1);
|
||||
textureData.Items[0].Mips.Resize(1);
|
||||
auto& mip = textureData.Items[0].Mips[0];
|
||||
mip.RowPitch = width * sizeof(Float4);
|
||||
mip.DepthPitch = mip.RowPitch * height;
|
||||
mip.Lines = height;
|
||||
mip.Data.Copy((const byte*)pixels, mip.DepthPitch);
|
||||
|
||||
free(pixels);
|
||||
#else
|
||||
LOG(Warning, "EXR format is not supported.");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case ImageType::DDS:
|
||||
LOG(Warning, "DDS format is not supported by stb library.");
|
||||
LOG(Warning, "DDS format is not supported.");
|
||||
break;
|
||||
case ImageType::TIFF:
|
||||
LOG(Warning, "TIFF format is not supported by stb library.");
|
||||
LOG(Warning, "TIFF format is not supported.");
|
||||
break;
|
||||
default:
|
||||
LOG(Warning, "Unknown format.");
|
||||
|
||||
@@ -290,7 +290,7 @@ namespace FlaxEngine.GUI
|
||||
{
|
||||
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex);
|
||||
var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex);
|
||||
float height = font.Height / DpiScale;
|
||||
float height = font.Height;
|
||||
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
|
||||
alpha *= alpha;
|
||||
Color selectionColor = Color.White * alpha;
|
||||
@@ -340,7 +340,7 @@ namespace FlaxEngine.GUI
|
||||
if (textBlock.Style.UnderlineBrush != null)
|
||||
{
|
||||
var underLineHeight = 2.0f;
|
||||
var height = font.Height / DpiScale;
|
||||
var height = font.Height;
|
||||
var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight);
|
||||
textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color);
|
||||
}
|
||||
|
||||
37
Source/Engine/Utilities/AnsiPathTempFile.h
Normal file
37
Source/Engine/Utilities/AnsiPathTempFile.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
|
||||
// Small utility that uses temporary file to properly handle non-ANSI paths for 3rd party libs.
|
||||
struct AnsiPathTempFile
|
||||
{
|
||||
StringAnsi Path;
|
||||
String TempPath;
|
||||
bool Temp;
|
||||
|
||||
AnsiPathTempFile(const String& path)
|
||||
{
|
||||
if (path.IsANSI() == false)
|
||||
{
|
||||
// Use temporary file
|
||||
FileSystem::GetTempFilePath(TempPath);
|
||||
if (TempPath.IsANSI() && !FileSystem::CopyFile(TempPath, path))
|
||||
{
|
||||
Path = TempPath.ToStringAnsi();
|
||||
return;
|
||||
}
|
||||
TempPath.Clear();
|
||||
}
|
||||
Path = path.ToStringAnsi();
|
||||
}
|
||||
|
||||
~AnsiPathTempFile()
|
||||
{
|
||||
// Cleanup temporary file after use
|
||||
if (TempPath.HasChars())
|
||||
FileSystem::DeleteFile(TempPath);
|
||||
}
|
||||
};
|
||||
@@ -47,6 +47,12 @@ namespace FlaxEngine.Utilities
|
||||
/// True if this tag contained a leading or trailing forward slash.
|
||||
/// </summary>
|
||||
public bool IsSlash => IsLeadingSlash || IsEndingSlash;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -231,6 +237,9 @@ namespace FlaxEngine.Utilities
|
||||
tag.Attributes[s] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (EOF)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip over closing '>'
|
||||
@@ -264,8 +273,13 @@ namespace FlaxEngine.Utilities
|
||||
private string ParseAttributeName()
|
||||
{
|
||||
int start = _pos;
|
||||
while (!EOF && char.IsLetterOrDigit(Peek()))
|
||||
while (!EOF)
|
||||
{
|
||||
var c = Peek();
|
||||
if (!char.IsLetterOrDigit(c) && c != '-')
|
||||
break;
|
||||
Move();
|
||||
}
|
||||
return _html.Substring(start, _pos - start);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user