You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
8.7 KiB
C++
174 lines
8.7 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <Assets/HairAsset.h>
|
|
#include <Builders/HairAssetBuilder.h>
|
|
#include <AssetBuilderSDK/SerializationDependencies.h>
|
|
|
|
#include <AzCore/IO/FileIO.h>
|
|
|
|
namespace AZ
|
|
{
|
|
namespace Render
|
|
{
|
|
namespace Hair
|
|
{
|
|
void HairAssetBuilder::RegisterBuilder()
|
|
{
|
|
// Setup the builder descriptor
|
|
AssetBuilderSDK::AssetBuilderDesc builderDesc;
|
|
builderDesc.m_name = "HairAssetBuilder";
|
|
|
|
builderDesc.m_patterns.emplace_back(AssetBuilderSDK::AssetBuilderPattern(
|
|
AZStd::string::format("*.%s", AMD::TFXFileExtension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
|
|
builderDesc.m_busId = azrtti_typeid<HairAssetBuilder>();
|
|
builderDesc.m_version = 3;
|
|
builderDesc.m_createJobFunction =
|
|
AZStd::bind(&HairAssetBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
|
builderDesc.m_processJobFunction =
|
|
AZStd::bind(&HairAssetBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
|
|
|
BusConnect(builderDesc.m_busId);
|
|
|
|
AssetBuilderSDK::AssetBuilderBus::Broadcast(
|
|
&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDesc);
|
|
}
|
|
|
|
void HairAssetBuilder::ShutDown()
|
|
{
|
|
m_isShuttingDown = true;
|
|
}
|
|
|
|
void HairAssetBuilder::CreateJobs(
|
|
const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
|
|
{
|
|
if (m_isShuttingDown)
|
|
{
|
|
response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
|
|
return;
|
|
}
|
|
|
|
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
|
|
{
|
|
AssetBuilderSDK::JobDescriptor descriptor;
|
|
descriptor.m_jobKey = AMD::TFXFileExtension;
|
|
descriptor.m_critical = false;
|
|
descriptor.SetPlatformIdentifier(info.m_identifier.c_str());
|
|
response.m_createJobOutputs.push_back(descriptor);
|
|
}
|
|
|
|
// Set the tfx bone and tfx mesh file as source dependency. This way when tfxbone or tfxmesh file reloaded it will
|
|
// also trigger the rebuild of the hair asset.
|
|
AssetBuilderSDK::SourceFileDependency sourceFileDependency;
|
|
sourceFileDependency.m_sourceFileDependencyPath = request.m_sourceFile;
|
|
AZ::StringFunc::Path::ReplaceExtension(sourceFileDependency.m_sourceFileDependencyPath, AMD::TFXBoneFileExtension);
|
|
response.m_sourceFileDependencyList.push_back(sourceFileDependency);
|
|
|
|
sourceFileDependency.m_sourceFileDependencyPath = request.m_sourceFile;
|
|
AZ::StringFunc::Path::ReplaceExtension(sourceFileDependency.m_sourceFileDependencyPath, AMD::TFXMeshFileExtension);
|
|
response.m_sourceFileDependencyList.push_back(sourceFileDependency);
|
|
|
|
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
|
|
}
|
|
|
|
void HairAssetBuilder::ProcessJob(
|
|
const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
|
|
{
|
|
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "HairAssetBuilder Starting Job for %s.\n", request.m_fullPath.c_str());
|
|
|
|
if (m_isShuttingDown)
|
|
{
|
|
AZ_TracePrintf(
|
|
AssetBuilderSDK::WarningWindow, "Cancelled job %s because shutdown was requested.\n", request.m_fullPath.c_str());
|
|
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
|
|
return;
|
|
}
|
|
|
|
// There are 3 source files for TressFX asset - .tfx, .tfxbone and .tfxmesh.
|
|
// We read all the 3 source files and combine them to 1 output file .tfxhair in the cache.
|
|
AZStd::string tfxFileName;
|
|
AzFramework::StringFunc::Path::GetFullFileName(request.m_fullPath.c_str(), tfxFileName);
|
|
|
|
// Create the path to the result .tfxhair file inside the tempDirPath.
|
|
AZStd::string destPath;
|
|
AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), tfxFileName.c_str(), destPath, true);
|
|
AZ::StringFunc::Path::ReplaceExtension(destPath, AMD::TFXCombinedFileExtension);
|
|
|
|
// Create and open the .tfxhair we are writing to.
|
|
AZ::IO::FileIOStream outStream(destPath.data(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath);
|
|
if (!outStream.IsOpen())
|
|
{
|
|
AZ_TracePrintf(
|
|
AssetBuilderSDK::ErrorWindow, "Error: Failed job %s because .tfxhair file cannot be created.\n", request.m_fullPath.c_str());
|
|
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
|
|
return;
|
|
}
|
|
|
|
// Write the .tfxhair file header as a placeholder
|
|
AMD::TressFXCombinedHairFileHeader header;
|
|
outStream.Write(sizeof(AMD::TressFXCombinedHairFileHeader), &header);
|
|
|
|
// Combine .tfx, .tfxbone, .tfxmesh file into .tfxhair file
|
|
auto writeToStream = [](const AZStd::string& fullpath, AZ::IO::FileIOStream& outStream, bool required) {
|
|
IO::FileIOStream inStream;
|
|
if (!inStream.Open(fullpath.c_str(), IO::OpenMode::ModeRead))
|
|
{
|
|
if (required)
|
|
{
|
|
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow,
|
|
"Error: Failed job %s because the file is either missing or cannot be opened.\n", fullpath.c_str());
|
|
}
|
|
return (AZ::IO::SizeType)0;
|
|
}
|
|
const AZ::IO::SizeType dataSize = inStream.GetLength();
|
|
AZStd::vector<AZ::u8> fileBuffer(dataSize);
|
|
inStream.Read(dataSize, fileBuffer.data());
|
|
return outStream.Write(dataSize, fileBuffer.data());
|
|
};
|
|
|
|
// Write .tfx file to the combined .tfxhair file.
|
|
AZStd::string sourcePath = request.m_fullPath;
|
|
const AZ::IO::SizeType tfxSize = writeToStream(sourcePath, outStream, true);
|
|
|
|
// Move on to .tfxbone file.
|
|
AZ::StringFunc::Path::ReplaceExtension(sourcePath, AMD::TFXBoneFileExtension);
|
|
const AZ::IO::SizeType tfxBoneSize = writeToStream(sourcePath, outStream, true);
|
|
|
|
// Move on to .tfxmesh file.
|
|
AZ::StringFunc::Path::ReplaceExtension(sourcePath, AMD::TFXMeshFileExtension);
|
|
writeToStream(sourcePath, outStream, false);
|
|
|
|
if (tfxSize == 0 || tfxBoneSize == 0)
|
|
{
|
|
// Fail the job if the .tfx file or the .tfxbone file is missing.
|
|
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow,
|
|
"Error: Failed job %s because tfxSize=%d or tfxBoneSize=%d.\n",
|
|
request.m_fullPath.c_str(), tfxSize, tfxBoneSize);
|
|
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
|
|
return;
|
|
}
|
|
|
|
// Write the header file with correct data
|
|
header.offsetTFX = sizeof(AMD::TressFXCombinedHairFileHeader);
|
|
header.offsetTFXBone = header.offsetTFX + tfxSize;
|
|
header.offsetTFXMesh = header.offsetTFXBone + tfxBoneSize;
|
|
outStream.Seek(0, AZ::IO::FileIOStream::SeekMode::ST_SEEK_BEGIN);
|
|
outStream.Write(sizeof(AMD::TressFXCombinedHairFileHeader), &header);
|
|
|
|
// Send the .tfxhair as the final job product product.
|
|
AssetBuilderSDK::JobProduct jobProduct(destPath, azrtti_typeid<HairAsset>(), 0);
|
|
jobProduct.m_dependenciesHandled = true;
|
|
response.m_outputProducts.push_back(jobProduct);
|
|
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
|
|
|
|
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "HairAssetBuilder successfully finished Job for %s.\n", request.m_fullPath.c_str());
|
|
}
|
|
}
|
|
} // namespace Render
|
|
} // namespace AZ
|