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.
o3de/Code/Tools/RC/ResourceCompilerScene/SceneCompiler.cpp

596 lines
30 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <IRCLog.h>
#include <ISceneConfig.h>
#include <ISystem.h>
#include <SceneCompiler.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Memory/AllocatorManager.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <RC/ResourceCompilerScene/Cgf/CgfExporter.h>
#include <RC/ResourceCompilerScene/Cgf/CgfGroupExporter.h>
#include <RC/ResourceCompilerScene/Cgf/CgfLodExporter.h>
#include <RC/ResourceCompilerScene/Common/CommonExportContexts.h>
#include <RC/ResourceCompilerScene/Common/ContainerSettingsExporter.h>
#include <RC/ResourceCompilerScene/Common/MeshExporter.h>
#include <RC/ResourceCompilerScene/Common/MaterialExporter.h>
#include <RC/ResourceCompilerScene/Common/ColorStreamExporter.h>
#include <RC/ResourceCompilerScene/Common/UVStreamExporter.h>
#include <RC/ResourceCompilerScene/Common/SkeletonExporter.h>
#include <RC/ResourceCompilerScene/Common/SkinWeightExporter.h>
#include <RC/ResourceCompilerScene/Common/BlendShapeExporter.h>
#include <RC/ResourceCompilerScene/SceneSerializationHandler.h>
#include <SceneAPI/SceneCore/Components/ExportingComponent.h>
#include <SceneAPI/SceneCore/Components/GenerationComponent.h>
#include <SceneAPI/SceneCore/Components/RCExportingComponent.h>
#include <SceneAPI/SceneCore/Components/SceneSystemComponent.h>
#include <SceneAPI/SceneCore/Components/Utilities/EntityConstructor.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
#include <SceneAPI/SceneCore/Events/GenerateEventContext.h>
#include <SceneAPI/SceneCore/Events/ExportProductList.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
namespace AZ
{
namespace RC
{
//! This function returns the build system target name
AZStd::string_view GetAssetBuilderTargetName()
{
#if !defined (LY_CMAKE_TARGET)
#error "LY_CMAKE_TARGET must be defined to the build target of the AssetBuilder application which is currently \"AssetBuilder\" via the source file properties for the SceneCompiler.cpp"
#endif
return AZStd::string_view{ LY_CMAKE_TARGET };
}
static const u32 s_maxLegacyCrcClashRetries = 255;
namespace SceneEvents = AZ::SceneAPI::Events;
namespace SceneContainers = AZ::SceneAPI::Containers;
RCToolApplication::RCToolApplication()
{
}
void RCToolApplication::RegisterDescriptors()
{
RegisterComponentDescriptor(SceneSerializationHandler::CreateDescriptor());
RegisterComponentDescriptor(BlendShapeExporter::CreateDescriptor());
RegisterComponentDescriptor(ColorStreamExporter::CreateDescriptor());
RegisterComponentDescriptor(ContainerSettingsExporter::CreateDescriptor());
RegisterComponentDescriptor(MaterialExporter::CreateDescriptor());
RegisterComponentDescriptor(MeshExporter::CreateDescriptor());
RegisterComponentDescriptor(SkeletonExporter::CreateDescriptor());
RegisterComponentDescriptor(SkinWeightExporter::CreateDescriptor());
RegisterComponentDescriptor(UVStreamExporter::CreateDescriptor());
}
AZ::ComponentTypeList RCToolApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList components = AzToolsFramework::ToolsApplication::GetRequiredSystemComponents();
auto removed = AZStd::remove_if(components.begin(), components.end(),
[](const Uuid& id) -> bool
{
return id == azrtti_typeid<AzFramework::TargetManagementComponent>()
|| id == azrtti_typeid<AzToolsFramework::PerforceComponent>()
|| id == azrtti_typeid<AZ::UserSettingsComponent>();
});
components.erase(removed, components.end());
return components;
}
void RCToolApplication::SetSettingsRegistrySpecializations(AZ::SettingsRegistryInterface::Specializations& specializations)
{
AzToolsFramework::ToolsApplication::SetSettingsRegistrySpecializations(specializations);
specializations.Append("scenecompiler");
}
SceneCompiler::SceneCompiler(const AZStd::shared_ptr<ISceneConfig>& config, const char* appRoot)
: m_config(config)
, m_appRoot(appRoot)
{
}
void SceneCompiler::Release()
{
delete this;
}
void SceneCompiler::BeginProcessing(const IConfig* /*config*/)
{
}
bool SceneCompiler::Process()
{
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Starting scene processing.\n");
AssetBuilderSDK::ProcessJobResponse response;
RCToolApplication application;
// Add the Build Target name as a specialization to the settings registry
AZ::SettingsRegistryInterface& registry = *AZ::SettingsRegistry::Get();
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
registry, AZ::RC::GetAssetBuilderTargetName());
AZ::CommandLine* commandLine = application.GetAzCommandLine();
AZStd::string overrideProjectPath = m_context.m_config->GetAsString("project-path", "", "").c_str();
if (!overrideProjectPath.empty())
{
auto overrideArgs = AZStd::string::format(
"--regset=%s/project_path=%s", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, overrideProjectPath.c_str());
AZ::CommandLine::ParamContainer commandLineArgs;
commandLine->Dump(commandLineArgs);
commandLineArgs.emplace_back(overrideArgs.c_str(), overrideArgs.size());
commandLine->Parse(commandLineArgs);
}
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, *commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
bool connectedToAP;
if (!PrepareForExporting(application, m_appRoot, connectedToAP))
{
bool result = WriteResponse(m_context.GetOutputFolder().c_str(), response, connectedToAP ? AssetBuilderSDK::ProcessJobResult_Failed : AssetBuilderSDK::ProcessJobResult_NetworkIssue);
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystemRequestBus::Events::StartDisconnectingAssetProcessor);
return result;
}
// Do this after PrepareForExporting is called so the types are registered for reading the request and writing a response.
AZStd::unique_ptr<AssetBuilderSDK::ProcessJobRequest> request = ReadJobRequest(m_context.GetOutputFolder().c_str());
if (!request)
{
bool result = WriteResponse(m_context.GetOutputFolder().c_str(), response, AssetBuilderSDK::ProcessJobResult_Failed);
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystemRequestBus::Events::StartDisconnectingAssetProcessor);
return result;
}
bool result = false;
// Active components, load the scene then process and export it.
{
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Creating scene system modules.\n");
AZ::Entity* systemEntity = AZ::SceneAPI::SceneCore::EntityConstructor::BuildSceneSystemEntity();
if (systemEntity)
{
constexpr const char PythonMarshalComponentTypeId[] = "{C733E1AD-9FDD-484E-A8D9-3EAB944B7841}";
constexpr const char PythonReflectionComponentTypeId[] = "{CBF32BE1-292C-4988-9E64-25127A8525A7}";
constexpr const char PythonSystemComponentTypeId[] = "{97F88B0F-CF68-4623-9541-549E59EE5F0C}";
systemEntity->CreateComponentIfReady({PythonSystemComponentTypeId});
systemEntity->CreateComponentIfReady({PythonMarshalComponentTypeId});
systemEntity->CreateComponentIfReady({PythonReflectionComponentTypeId});
systemEntity->Init();
systemEntity->Activate();
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Processing scene file.\n");
result = LoadAndExportScene(*request, response);
systemEntity->Deactivate();
delete systemEntity;
}
else
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Unable to create a system component for the SceneAPI.\n");
result = false;
}
}
if (!result || m_config->GetErrorCount() > 0)
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "During processing one or more problems were found.\n");
result = false;
}
// Manually disconnect from the Asset Processor before the application goes out of scope to avoid
// a potential serialization issue due to deficiencies in the order of teardown operations.
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystemRequestBus::Events::StartDisconnectingAssetProcessor);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Finished scene processing.\n");
return WriteResponse(m_context.GetOutputFolder().c_str(), response, result ? AssetBuilderSDK::ProcessJobResult_Success : AssetBuilderSDK::ProcessJobResult_Failed);
}
void SceneCompiler::EndProcessing()
{
}
IConvertContext* SceneCompiler::GetConvertContext()
{
return &m_context;
}
bool SceneCompiler::PrepareForExporting(RCToolApplication& application, [[maybe_unused]] const AZStd::string& appRoot, bool& connectedToAssetProcessor)
{
// Not all Gems shutdown properly and leak memory, but this shouldn't
// prevent this builder from completing.
AZ::AllocatorManager::Instance().SetAllocatorLeaking(true);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Initializing tools application environment.\n");
AZ::ComponentApplication::Descriptor descriptor;
descriptor.m_useExistingAllocator = true;
descriptor.m_enableScriptReflection = false;
AZ::ComponentApplication::StartupParameters startupParam;
startupParam.m_loadDynamicModules = false;
application.Start(descriptor, startupParam);
// Load Dynamic Modules after the Application::Start has been called to avoid creating
// creating SystemComponents automatically
application.LoadDynamicModules();
application.RegisterDescriptors();
// Register the AssetBuilderSDK structures needed later on.
AssetBuilderSDK::InitializeSerializationContext();
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Connecting to asset processor.\n");
// Retrieve the asset processor connection params from the settings registry
AzFramework::AssetSystem::ConnectionSettings connectionSettings;
bool succeeded = AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
if(!succeeded)
{
AZ_Error("RC Scene Compiler", false, "Getting bootstrap params failed");
return false;
}
//override bootstrap params
//the branch token can be overridden, check it
AZStd::string overrideBranchToken;
overrideBranchToken = m_context.m_config->GetAsString("branchtoken", "", "");
if (!overrideBranchToken.empty())
{
connectionSettings.m_branchToken = overrideBranchToken;
}
//the port can be overridden, check it
AZ::u16 overridePort = 0;
overridePort = aznumeric_cast<AZ::u16>(m_context.m_config->GetAsInt("port", 0, 0));
if (overridePort)
{
connectionSettings.m_assetProcessorPort = overridePort;
}
//the project name can be overridden, check it
AZStd::string overrideProjectName;
overrideProjectName = m_context.m_config->GetAsString("project-name", "", "");
if (!overrideProjectName.empty())
{
connectionSettings.m_projectName = overrideProjectName;
}
connectionSettings.m_connectionIdentifier = "RC Scene Compiler";
connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
connectionSettings.m_launchAssetProcessorOnFailedConnection = false; // builders shouldn't launch the AssetProcessor
connectionSettings.m_waitUntilAssetProcessorIsReady = false; // builders are what make the AssetProcessor ready, so the cannot wait until the AssetProcessor is ready
connectionSettings.m_waitForConnect = true; // application is a builder so it needs to wait for a connection
// connect to Asset Processor.
AzFramework::AssetSystemRequestBus::BroadcastResult(connectedToAssetProcessor,
&AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
return connectedToAssetProcessor;
}
bool SceneCompiler::LoadAndExportScene(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
{
const string platformName = m_context.m_config->GetAsString("platform", "<unknown>", "<invalid>");
AZ_TraceContext("Platform", platformName.c_str());
if (platformName == "<unknown>")
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "No target platform provided - this compiler requires the --platform=platformIdentifier option\n");
return false;
}
if (platformName == "<invalid>")
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Invalid target platform provided (Parse error reading command line)\n");
return false;
}
AZStd::string sourcePath = m_context.GetSourcePath().c_str();
AZ_TraceContext("Source", sourcePath);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Loading source files.\n");
AZStd::shared_ptr<SceneContainers::Scene> scene;
SceneEvents::SceneSerializationBus::BroadcastResult(scene, &SceneEvents::SceneSerializationBus::Events::LoadScene, sourcePath, request.m_sourceFileUUID);
if (!scene)
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Failed to load scene file.\n");
return false;
}
AZ_TraceContext("Manifest", scene->GetManifestFilename());
if (scene->GetManifest().IsEmpty())
{
AZ_TracePrintf(SceneAPI::Utilities::WarningWindow, "No manifest loaded and not enough information to create a default manifest.\n");
return true;
}
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Generating data into scene.\n");
if (!GenerateScene(*scene, platformName.c_str()))
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Failed to run data generation for scene.\n");
return false;
}
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Exporting loaded data to engine specific formats.\n");
if (!ExportScene(request, response, *scene, platformName.c_str()))
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Failed to convert and export scene\n");
return false;
}
return true;
}
bool SceneCompiler::GenerateScene(AZ::SceneAPI::Containers::Scene& scene, const char* platformIdentifier)
{
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Creating generation entities.\n");
AZ::SceneAPI::SceneCore::EntityConstructor::EntityPointer rcExporter = AZ::SceneAPI::SceneCore::EntityConstructor::BuildEntity(
"Scene Generators", AZ::SceneAPI::SceneCore::GenerationComponent::TYPEINFO_Uuid());
SceneEvents::ProcessingResultCombiner result;
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Preparing for generation.\n");
result += SceneEvents::Process<SceneEvents::PreGenerateEventContext>(scene, platformIdentifier);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Generating...\n");
result += SceneEvents::Process<SceneEvents::GenerateEventContext>(scene, platformIdentifier);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Generating LODs...\n");
result += SceneEvents::Process<SceneEvents::GenerateLODEventContext>(scene, platformIdentifier);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Generating additions...\n");
result += SceneEvents::Process<SceneEvents::GenerateAdditionEventContext>(scene, platformIdentifier);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Simplifing scene...\n");
result += SceneEvents::Process<SceneEvents::GenerateSimplificationEventContext>(scene, platformIdentifier);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Finalizing generation process.\n");
result += SceneEvents::Process<SceneEvents::PostGenerateEventContext>(scene, platformIdentifier);
switch (result.GetResult())
{
case SceneEvents::ProcessingResult::Success:
case SceneEvents::ProcessingResult::Ignored:
return true;
case SceneEvents::ProcessingResult::Failure:
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Failure during conversion and exporting.\n");
return false;
}
return false;
}
bool SceneCompiler::ExportScene(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response, const AZ::SceneAPI::Containers::Scene& scene, const char* platformIdentifier)
{
AZ_TraceContext("Output folder", m_context.GetOutputFolder().c_str());
AZ_Assert(m_context.m_pRC->GetAssetWriter() != nullptr, "Invalid IAssetWriter initialization.");
if (!m_context.m_pRC->GetAssetWriter())
{
return false;
}
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Creating export entities.\n");
AZ::SceneAPI::SceneCore::EntityConstructor::EntityPointer rcExporter = AZ::SceneAPI::SceneCore::EntityConstructor::BuildEntity(
"Scene RC Exporters", AZ::SceneAPI::SceneCore::RCExportingComponent::TYPEINFO_Uuid());
// Register additional processors. Will be automatically unregistered when leaving scope.
// These have not yet been converted to components as they need special attention due
// to the arguments they currently need.
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Registering export processors.\n");
CgfExporter cgfProcessor(&m_context);
CgfGroupExporter meshGroupExporter(m_context.m_pRC->GetAssetWriter());
CgfLodExporter meshLodExporter(m_context.m_pRC->GetAssetWriter());
SceneAPI::Events::ExportProductList productList;
SceneEvents::ProcessingResultCombiner result;
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Preparing for export.\n");
result += SceneEvents::Process<SceneEvents::PreExportEventContext>(productList, m_context.GetOutputFolder().c_str(), scene, platformIdentifier);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Exporting...\n");
result += SceneEvents::Process<SceneEvents::ExportEventContext>(productList, m_context.GetOutputFolder().c_str(), scene, platformIdentifier);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Finalizing export process.\n");
result += SceneEvents::Process<SceneEvents::PostExportEventContext>(productList, m_context.GetOutputFolder().c_str(), platformIdentifier);
AZStd::map<AZStd::string, size_t> preSubIdFiles;
for (const auto& it : productList.GetProducts())
{
size_t index = response.m_outputProducts.size();
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Listed product: %s+0x%08x - %s (type %s)\n", it.m_id.ToString<AZStd::string>().c_str(),
BuildSubId(it), it.m_filename.c_str(), it.m_assetType.ToString<AZStd::string>().c_str());
response.m_outputProducts.emplace_back(AZStd::move(it.m_filename), it.m_assetType, BuildSubId(it));
if (IsPreSubIdFile(it.m_filename))
{
preSubIdFiles[it.m_filename] = index;
}
for (const AZStd::string& legacyIt : it.m_legacyFileNames)
{
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, " -> Legacy name: %s\n", legacyIt.c_str());
preSubIdFiles[legacyIt] = index;
}
// Add our relative path dependencies the exporters may have generated
AssetBuilderSDK::JobProduct& currentProduct = response.m_outputProducts.back();
for (const AZStd::string& pathDep : it.m_legacyPathDependencies)
{
// For now, we assume the relative path dependencies are simply file names.
// Append the source product path to these dependencies to generate the proper path dependency
AZStd::string relativePath;
AzFramework::StringFunc::Path::GetFolderPath(request.m_sourceFile.c_str(), relativePath);
AzFramework::StringFunc::AssetDatabasePath::Join(relativePath.c_str(), pathDep.c_str(), relativePath);
currentProduct.m_pathDependencies.emplace(relativePath, AssetBuilderSDK::ProductPathDependencyType::SourceFile);
}
// If we have any output products that are a dependency of this product, add them here.
// This will include adding LODs as dependencies of the base CGFs
for (auto& exportProduct : it.m_productDependencies)
{
AZ::Data::AssetId productAssetId(request.m_sourceFileUUID, BuildSubId(exportProduct));
currentProduct.m_dependencies.push_back(AssetBuilderSDK::ProductDependency(productAssetId, exportProduct.m_dependencyFlags));
}
currentProduct.m_dependenciesHandled = true; // We've populated the dependencies immediately above so it's OK to tell the AP we've handled dependencies
}
ResolvePreSubIds(response, preSubIdFiles);
switch (result.GetResult())
{
case SceneEvents::ProcessingResult::Success:
return true;
case SceneEvents::ProcessingResult::Ignored:
return true;
case SceneEvents::ProcessingResult::Failure:
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Failure during conversion and exporting.\n");
return false;
default:
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow,
"Unexpected result from conversion and exporting (%i).\n", result.GetResult());
return false;
}
}
bool SceneCompiler::IsPreSubIdFile(const AZStd::string& file) const
{
AZStd::string extension;
if (!AzFramework::StringFunc::Path::GetExtension(file.c_str(), extension))
{
return false;
}
return extension == ".caf" || extension == ".cgf" || extension == ".chr" || extension == ".mtl" || extension == ".skin";
}
// BuildSubId has an equivalent counterpart in SceneBuilder. Both need to remain the same to avoid problems with sub ids.
u32 SceneCompiler::BuildSubId(const SceneAPI::Events::ExportProduct& product) const
{
// Instead of the just the lower 16-bits, use the full 32-bits that are available. There are production examples of
// uber-fbx files that contain hundreds of meshes that need to be split into individual mesh objects as an example.
u32 id = static_cast<u32>(product.m_id.GetHash());
if (product.m_lod.has_value())
{
AZ::u8 lod = product.m_lod.value();
if (lod > 0xF)
{
AZ_TracePrintf(SceneAPI::Utilities::WarningWindow, "%i is too large to fit in the allotted bits for LOD.\n", static_cast<u32>(lod));
lod = 0xF;
}
// The product uses lods so mask out the lod bits and set them appropriately.
id &= ~AssetBuilderSDK::SUBID_MASK_LOD_LEVEL;
id |= lod << AssetBuilderSDK::SUBID_LOD_LEVEL_SHIFT;
}
return id;
}
void SceneCompiler::ResolvePreSubIds(AssetBuilderSDK::ProcessJobResponse& response, const AZStd::map<AZStd::string, size_t>& preSubIdFiles) const
{
if (!preSubIdFiles.empty())
{
// Start by compiling a list of known sub ids. Include sub ids from non-legacy files as well because sub ids created
// here are not allowed to clash with any sub id not matter if it's legacy or not.
AZStd::unordered_set<u32> assignedSubIds;
for (const auto& it : response.m_outputProducts)
{
if (assignedSubIds.find(it.m_productSubID) == assignedSubIds.end())
{
assignedSubIds.insert(it.m_productSubID);
}
else
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Sub id collision found (0x%04x).\n", it.m_productSubID);
}
}
// First legacy product always had sub id 0. Also add the hashed version in the loop though as there might be a file in
// front of it that the RCScene doesn't know about.
response.m_outputProducts[preSubIdFiles.begin()->second].m_legacySubIDs.push_back(0);
AZStd::string filename;
for (const auto& it : preSubIdFiles)
{
AZ_TraceContext("Legacy file name", it.first);
if (!AzFramework::StringFunc::Path::GetFullFileName(it.first.c_str(), filename))
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Unable to extract filename for legacy sub id.\n");
continue;
}
// Modified version of the algorithm in RCBuilder.
for (u32 seedValue = 0; seedValue < s_maxLegacyCrcClashRetries; ++seedValue)
{
u32 fullCrc = AZ::Crc32(filename.c_str());
u32 maskedCrc = (fullCrc + seedValue) & AssetBuilderSDK::SUBID_MASK_ID;
if (assignedSubIds.find(maskedCrc) == assignedSubIds.end())
{
response.m_outputProducts[it.second].m_legacySubIDs.push_back(maskedCrc);
assignedSubIds.insert(maskedCrc);
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "Added legacy sub id 0x%04x - %s\n", maskedCrc, filename.c_str());
break;
}
}
filename.clear();
}
}
}
AZStd::unique_ptr<AssetBuilderSDK::ProcessJobRequest> SceneCompiler::ReadJobRequest(const char* cacheFolder) const
{
AZStd::string requestFilePath;
AzFramework::StringFunc::Path::ConstructFull(cacheFolder, AssetBuilderSDK::s_processJobRequestFileName, requestFilePath);
AssetBuilderSDK::ProcessJobRequest* result = AZ::Utils::LoadObjectFromFile<AssetBuilderSDK::ProcessJobRequest>(requestFilePath);
if (!result)
{
AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Unable to load ProcessJobRequest. Not enough information to process this file %s.\n", requestFilePath.c_str());
}
return AZStd::unique_ptr<AssetBuilderSDK::ProcessJobRequest>(result);
}
bool SceneCompiler::WriteResponse(const char* cacheFolder, AssetBuilderSDK::ProcessJobResponse& response, AssetBuilderSDK::ProcessJobResultCode jobResult) const
{
AZStd::string responseFilePath;
AzFramework::StringFunc::Path::ConstructFull(cacheFolder, AssetBuilderSDK::s_processJobResponseFileName, responseFilePath);
response.m_requiresSubIdGeneration = false;
response.m_resultCode = jobResult;
bool result = AZ::Utils::SaveObjectToFile(responseFilePath, AZ::DataStream::StreamType::ST_XML, &response);
return result && jobResult == AssetBuilderSDK::ProcessJobResult_Success;
}
} // namespace RC
} // namespace AZ