/* * 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 "ShaderAssetBuilder.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AzslBuilder.h" #include "SrgLayoutBuilder.h" #include "ShaderVariantAssetBuilder.h" #include "ShaderBuilderUtility.h" #include "ShaderPlatformInterfaceRequest.h" #include "AtomShaderConfig.h" #include #include namespace AZ { namespace ShaderBuilder { static constexpr char ShaderAssetBuilderName[] = "ShaderAssetBuilder"; static constexpr uint32_t ShaderAssetBuildTimestampParam = 0; static void AddSrgLayoutJobDependency(AssetBuilderSDK::JobDescriptor& jobDescriptor, const AssetBuilderSDK::PlatformInfo& platformInfo, AZStd::string_view fullFilePath) { AssetBuilderSDK::SourceFileDependency fileDependency; fileDependency.m_sourceFileDependencyPath = fullFilePath; AssetBuilderSDK::JobDependency srgLayoutJobDependency; srgLayoutJobDependency.m_jobKey = SrgLayoutBuilder::SrgLayoutBuilderJobKey; srgLayoutJobDependency.m_platformIdentifier = platformInfo.m_identifier; srgLayoutJobDependency.m_sourceFile = fileDependency; srgLayoutJobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; jobDescriptor.m_jobDependencyList.emplace_back(srgLayoutJobDependency); } void ShaderAssetBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const { AZStd::string fullPath; AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true); AZ_TracePrintf(ShaderAssetBuilderName, "CreateJobs for Shader \"%s\"\n", fullPath.data()); // Used to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload. // Note it's probably important for this to be set once outside the platform loop so every platform's ShaderAsset // has the same value, because later the ShaderVariantTreeAsset job will fetch this value from the local ShaderAsset // which could cross platforms (i.e. building an android ShaderVariantTreeAsset on PC would fetch the tiemstamp from // the PC's ShaderAsset). AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond(); // *** block (remove all this when [ATOM-4225] addressed) AZStd::string azslFullPath; // Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job. // and the macro options to preprocess. auto descriptorParseOutput = ShaderBuilderUtility::LoadShaderDataJson(fullPath); if (!descriptorParseOutput.IsSuccess()) { AZ_Error(ShaderAssetBuilderName, false, "Failed to parse Shader Descriptor JSON: %s", descriptorParseOutput.GetError().c_str()); return; } ShaderBuilderUtility::GetAbsolutePathToAzslFile(fullPath, descriptorParseOutput.GetValue().m_source, azslFullPath); AZ_Warning(ShaderAssetBuilderName, IO::FileIOBase::GetInstance()->Exists(azslFullPath.c_str()), "Shader program listed as the source entry does not exist: %s.", azslFullPath.c_str()); GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilderName); // *** end block (remove when [Atom-4225]) for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms) { AZ_TraceContext("For platform", platformInfo.m_identifier.data()); // Get the platform interfaces to be able to access the prepend file AZStd::vector platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(platformInfo); AssetBuilderSDK::JobDescriptor jobDescriptor; jobDescriptor.m_priority = 2; // [GFX TODO][ATOM-2830] Set 'm_critical' back to 'false' once proper fix for Atom startup issues are in jobDescriptor.m_critical = true; jobDescriptor.m_jobKey = ShaderAssetBuilderJobKey; jobDescriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str()); // queue up AzslBuilder dependencies: for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces) { AddAzslBuilderJobDependency(jobDescriptor, platformInfo.m_identifier, shaderPlatformInterface->GetAPIName().GetCStr(), fullPath); {// *** block (remove all this when [ATOM-4225] addressed) // this is the same code as in AzslBuilder.cpp's CreateJobs. This is not supposed to be repeated here (temporary hack), so not factorized. refer to AzslBuilder.cpp for code comments AZStd::string prependedAzslSourceCode; RHI::PrependArguments args; args.m_sourceFile = fullPath.c_str(); args.m_prependFile = shaderPlatformInterface->GetAzslHeader(platformInfo); args.m_addSuffixToFileName = shaderPlatformInterface->GetAPIName().GetCStr(); args.m_destinationStringOpt = &prependedAzslSourceCode; if (RHI::PrependFile(args) == fullPath) { response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed; return; } AZStd::string originalLocation; AzFramework::StringFunc::Path::GetFullPath(fullPath.c_str(), originalLocation); AZStd::string prependedPath = ShaderBuilderUtility::DumpAzslPrependedCode( ShaderAssetBuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()), shaderPlatformInterface->GetAPIName().GetStringView()); PreprocessorData output; buildOptions.m_compilerArguments.Merge(descriptorParseOutput.GetValue().m_compiler); PreprocessFile(azslFullPath, output, buildOptions.m_preprocessorSettings, true, true); // srg layout builder job dependency on the azsl file itself. // If there's a ShaderResourceGroup defined in this azsl file // then its azsrg asset must be ready before We compile it. The azsrg requirement // is only for diagnostics, because a preprocessed (flattened) azsl file contains // all the SRG data it needs. AddSrgLayoutJobDependency(jobDescriptor, platformInfo, azslFullPath); for (auto included : output.includedPaths) { AddSrgLayoutJobDependency(jobDescriptor, platformInfo, included.c_str()); } AZ::IO::SystemFile::Delete(prependedPath.c_str()); // don't let that intermediate file dirty a folder under source version control. }// *** end block remove when [Atom-4225] } jobDescriptor.m_jobParameters.emplace(ShaderAssetBuildTimestampParam, AZStd::to_string(shaderAssetBuildTimestamp)); response.m_createJobOutputs.push_back(jobDescriptor); } // for all request.m_enabledPlatforms response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; } static AssetBuilderSDK::ProcessJobResultCode CompileForAPI( const ShaderBuilderUtility::AzslSubProducts::Paths& pathOfProductFiles, RPI::ShaderAssetCreator& shaderAssetCreator, RHI::ShaderPlatformInterface* shaderPlatformInterface, AssetBuilderSDK::ProcessJobResponse& response, AzslData& azslData, const RHI::ShaderCompilerArguments& shaderCompilerArguments, RPI::Ptr shaderOptionGroupLayout, const RPI::ShaderSourceData& shaderSourceDataDescriptor, AZStd::sys_time_t shaderAssetBuildTimestamp, const ShaderResourceGroupAssets& srgAssets, BindingDependencies& bindingDependencies, const RootConstantData& rootConstantData, const AssetBuilderSDK::ProcessJobRequest& request) { AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); const AZStd::string& tempDirPath = request.m_tempDirPath; // discover entry points MapOfStringToStageType shaderEntryPoints; if (shaderSourceDataDescriptor.m_programSettings.m_entryPoints.empty()) { AZ_TracePrintf(ShaderAssetBuilderName, "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n"); ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints); } else { for (auto& iter : shaderSourceDataDescriptor.m_programSettings.m_entryPoints) { shaderEntryPoints[iter.m_name] = iter.m_type; } } // Check if we were canceled before we do any heavy processing of // the shader data (compiling the shader kernels, processing SRG // and pipeline layout data, etc.). if (jobCancelListener.IsCancelled()) { return AssetBuilderSDK::ProcessJobResult_Cancelled; } // Signal the begin of shader data for an RHI API. shaderAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType()); RHI::Ptr pipelineLayoutDescriptor = ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi( ShaderAssetBuilderName, shaderPlatformInterface, bindingDependencies, srgAssets, shaderEntryPoints, shaderCompilerArguments, &rootConstantData); if (!pipelineLayoutDescriptor) { AZ_Error(ShaderAssetBuilderName, false, "Failed to build pipeline layout descriptor for api=[%s]", shaderPlatformInterface->GetAPIName().GetCStr()); AssetBuilderSDK::ProcessJobResult_Failed; } for (const auto& srgAsset : srgAssets) { shaderAssetCreator.AddShaderResourceGroupAsset(srgAsset); } shaderAssetCreator.SetPipelineLayout(pipelineLayoutDescriptor); // Generate shader source. AZStd::string hlslSourcePath = pathOfProductFiles[ShaderBuilderUtility::AzslSubProducts::hlsl]; Outcome hlslSourceContent = Utils::ReadFile(hlslSourcePath); if (!hlslSourceContent.IsSuccess()) { AZ_Error(ShaderAssetBuilderName, false, "Failed to obtain shader source from %s. [%s]", hlslSourcePath.c_str(), hlslSourceContent.TakeError().c_str()); return AssetBuilderSDK::ProcessJobResult_Failed; } // The root ShaderVariantAsset needs to be created with the known uuid of the source .shader asset because // the ShaderAsset owns a Data::Asset<> reference that gets serialized. It must have the correct uuid // so the root ShaderVariantAsset is found when the ShaderAsset is deserialized. AZStd::string fullSourcePath; AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullSourcePath, true); const uint32_t productSubID = RPI::ShaderAsset::MakeAssetProductSubId( shaderPlatformInterface->GetAPIUniqueIndex(), aznumeric_cast(RPI::ShaderAssetSubId::RootShaderVariantAsset)); auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(fullSourcePath, productSubID); AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", fullSourcePath.c_str()); const Data::AssetId variantAssetId = assetIdOutcome.TakeValue(); // We always include the root shader variant (all options are unspecified) in the ShaderAsset itself. ShaderVariantCreationContext variantCreationContext = { variantAssetId, hlslSourcePath, hlslSourceContent.GetValue(), shaderSourceDataDescriptor, tempDirPath, request.m_platformInfo, *shaderOptionGroupLayout, shaderEntryPoints, shaderAssetBuildTimestamp }; AZ::Outcome, AZStd::string> outcomeForShaderVariantAsset = ShaderVariantAssetBuilder::CreateShaderVariantAssetForAPI( RPI::ShaderVariantListSourceData::VariantInfo(), variantCreationContext, *shaderPlatformInterface, azslData, shaderCompilerArguments, pathOfProductFiles[ShaderBuilderUtility::AzslSubProducts::om], pathOfProductFiles[ShaderBuilderUtility::AzslSubProducts::ia]); if (!outcomeForShaderVariantAsset.IsSuccess()) { AZ_Error(ShaderAssetBuilderName, false, "Failed to serialize out the root shader variant for API [%s]: %s" , shaderPlatformInterface->GetAPIName().GetCStr(), outcomeForShaderVariantAsset.GetError().c_str()); return AssetBuilderSDK::ProcessJobResult_Failed; } // Time to save, for the given rhi::api, the root shader variant as an asset. Data::Asset shaderVariantAsset = outcomeForShaderVariantAsset.TakeValue(); AssetBuilderSDK::JobProduct variantAssetProduct; if (!ShaderVariantAssetBuilder::SerializeOutShaderVariantAsset(shaderVariantAsset, fullSourcePath, tempDirPath, *shaderPlatformInterface, productSubID, variantAssetProduct)) { AZ_Error(ShaderAssetBuilderName, false, "Failed to serialize out the root shader variant for API [%s]" , shaderPlatformInterface->GetAPIName().GetCStr()); return AssetBuilderSDK::ProcessJobResult_Failed; } response.m_outputProducts.push_back(variantAssetProduct); // add byproducts as job output products: if (variantCreationContext.m_outputByproducts) { uint32_t subProductType = aznumeric_cast(RPI::ShaderAssetSubId::GeneratedHlslSource) + 1; for (const AZStd::string& byproduct : variantCreationContext.m_outputByproducts->m_intermediatePaths) { AssetBuilderSDK::JobProduct jobProduct; jobProduct.m_productFileName = byproduct; jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt"); jobProduct.m_productSubID = RPI::ShaderAsset::MakeAssetProductSubId(shaderPlatformInterface->GetAPIUniqueIndex(), subProductType++); response.m_outputProducts.push_back(AZStd::move(jobProduct)); } } shaderAssetCreator.SetRootShaderVariantAsset(shaderVariantAsset); // Populate the shader asset with all entry stage attributes RHI::ShaderStageAttributeMapList attributeMaps; attributeMaps.resize(RHI::ShaderStageCount); for (const auto& shaderEntry : shaderSourceDataDescriptor.m_programSettings.m_entryPoints) { auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntry](const auto& func) { return func.m_name == shaderEntry.m_name; }); if (findId == azslData.m_functions.end()) { // shaderData.m_functions only contains Vertex, Fragment and Compute entries for now // Tessellation shaders will need to be handled too continue; } const auto shaderStage = ToRHIShaderStage(ShaderBuilderUtility::ToAssetBuilderShaderType(shaderEntry.m_type)); for (const auto& attr : findId->attributesList) { // Some stages like RHI::ShaderStage::Tessellation are compound and consist of two or more shader entries const Name& attributeName = attr.first; const RHI::ShaderStageAttributeArguments& args = attr.second; const auto stageIndex = static_cast(shaderStage); AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!"); attributeMaps[stageIndex][attributeName] = args; } } shaderAssetCreator.SetShaderStageAttributeMapList(attributeMaps); shaderAssetCreator.EndAPI(); return AssetBuilderSDK::ProcessJobResult_Success; } static bool SerializeOutShaderAsset(Data::Asset shaderAsset, const AZStd::string& tempDirPath, AssetBuilderSDK::ProcessJobResponse& response) { AZStd::string shaderAssetFileName = AZStd::string::format("%s.%s", shaderAsset->GetName().GetCStr(), RPI::ShaderAsset::Extension); AZStd::string shaderAssetOutputPath; AzFramework::StringFunc::Path::ConstructFull(tempDirPath.data(), shaderAssetFileName.data(), shaderAssetOutputPath, true); if (!Utils::SaveObjectToFile(shaderAssetOutputPath, DataStream::ST_BINARY, shaderAsset.Get())) { AZ_Error(ShaderAssetBuilderName, false, "Failed to output Shader Descriptor"); return false; } AssetBuilderSDK::JobProduct shaderJobProduct; if (!AssetBuilderSDK::OutputObject(shaderAsset.Get(), shaderAssetOutputPath, azrtti_typeid(), aznumeric_cast(RPI::ShaderAssetSubId::ShaderAsset), shaderJobProduct)) { AZ_Error(ShaderAssetBuilderName, false, "Failed to output product dependencies."); return false; } response.m_outputProducts.push_back(AZStd::move(shaderJobProduct)); return true; } void ShaderAssetBuilder::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const { const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks(); AZStd::string fullSourcePath; AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullSourcePath, true); RPI::ShaderAssetCreator shaderAssetCreator; shaderAssetCreator.Begin(Uuid::CreateRandom()); // read .shader -> access azsl path -> make absolute RPI::ShaderSourceData shaderAssetSource; AZStd::shared_ptr inputFiles = ShaderBuilderUtility::PrepareSourceInput(ShaderAssetBuilderName, fullSourcePath, shaderAssetSource); if (!inputFiles) { response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; return; } GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilderName); // Save .shader file name AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.data(), inputFiles->m_shaderFileName); // Request the list of valid shader platform interfaces for the target platform. AZStd::vector platformInterfaces; ShaderPlatformInterfaceRequestBus::BroadcastResult(platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, request.m_platformInfo); // Generate shaders for each of those ShaderPlatformInterfaces. uint32_t countOfCompiledPlatformInterfaces = 0; for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces) { ShaderResourceGroupAssets srgAssets; RPI::Ptr shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create(); if (!shaderPlatformInterface) { AZ_Error(ShaderAssetBuilderName, false, "ShaderPlatformInterface for [%s] is not registered, can't compile [%s]", request.m_platformInfo.m_identifier.c_str(), request.m_sourceFile.c_str()); response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; return; } if (shaderAssetSource.IsRhiBackendDisabled(shaderPlatformInterface->GetAPIName())) { // Gracefully do nothing and continue with the next shaderPlatformInterface. AZ_TracePrintf( ShaderAssetBuilderName, "Skipping shader compilation [%s] for API [%s]\n", inputFiles->m_shaderFileName.c_str(), shaderPlatformInterface->GetAPIName().GetCStr()); continue; } AZStd::sys_time_t shaderAssetBuildTimestamp = 0; auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam); if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end()) { shaderAssetBuildTimestamp = AZStd::stoull(shaderAssetBuildTimestampIterator->second); if (AZStd::to_string(shaderAssetBuildTimestamp) != shaderAssetBuildTimestampIterator->second) { AZ_Assert(false, "Incorrect conversion of ShaderAssetBuildTimestampParam"); return; } } AzslData azslData(inputFiles); AZ_TraceContext("Platform API", AZStd::string{ shaderPlatformInterface->GetAPIName().GetStringView() }); AZ_TracePrintf(ShaderAssetBuilderName, "Preprocessed AZSL File: %s \n", azslData.m_preprocessedFullPath.c_str()); AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId); if (jobCancelListener.IsCancelled()) { response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled; return; } // obtain the build artifacts from the azsl builder: auto azslArtifactsOutcome = ShaderBuilderUtility::ObtainBuildArtifactsFromAzslBuilder(ShaderAssetBuilderName, fullSourcePath, shaderPlatformInterface->GetAPIType(), request.m_platformInfo.m_identifier); if (!azslArtifactsOutcome.IsSuccess()) { // If it failed, it may be because the .shader source file created no products! (this happens when the build options are similar) // (the .azsl file *always* produces AzslBuilder artifacts. The.shader file *sometimes* produces AzslBuilder artifacts, when it has build options that have to be accounted for) // If there are no artifacts from the .shader, then we fall back to the ones from the .azsl: azslArtifactsOutcome = ShaderBuilderUtility::ObtainBuildArtifactsFromAzslBuilder(ShaderAssetBuilderName, inputFiles->m_azslSourceFullPath, shaderPlatformInterface->GetAPIType(), request.m_platformInfo.m_identifier); if (!azslArtifactsOutcome.IsSuccess()) { response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; return; } } shaderAssetCreator.SetName(Name(azslData.m_sources->m_shaderFileName)); shaderAssetCreator.SetDrawListName(Name(shaderAssetSource.m_drawListName)); shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp); BindingDependencies bindingDependencies; RootConstantData rootConstantData; AssetBuilderSDK::ProcessJobResultCode azslJsonReadResult = ShaderBuilderUtility::PopulateAzslDataFromJsonFiles( ShaderAssetBuilderName, azslArtifactsOutcome.GetValue(), azslData, srgAssets, shaderOptionGroupLayout, bindingDependencies, rootConstantData); if (azslJsonReadResult != AssetBuilderSDK::ProcessJobResult_Success) { response.m_resultCode = azslJsonReadResult; return; } shaderAssetCreator.SetName(Name(azslData.m_sources->m_shaderFileName)); shaderAssetCreator.SetDrawListName(Name(shaderAssetSource.m_drawListName)); shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp); // The ShaderOptionGroupLayout dictates what options/range can be used to create variants shaderAssetCreator.SetShaderOptionGroupLayout(shaderOptionGroupLayout); AZ_TracePrintf(ShaderAssetBuilderName, "Original AZSL File: %s \n", azslData.m_sources->m_azslSourceFullPath.c_str()); const uint32_t usedShaderOptionBits = shaderOptionGroupLayout->GetBitSize(); AZ_TracePrintf(ShaderAssetBuilderName, "Note: This shader uses %u of %u available shader variant key bits. \n", usedShaderOptionBits, RPI::ShaderVariantKeyBitCount); // The idea of this merge is that we have compiler options coming from 2 source: // global options (from project Config/), and .shader options. // We define a merge behavior that is: ".shader wins if set" RHI::ShaderCompilerArguments mergedArguments = buildOptions.m_compilerArguments; mergedArguments.Merge(shaderAssetSource.m_compiler); AssetBuilderSDK::ProcessJobResultCode compileResult = CompileForAPI( azslArtifactsOutcome.GetValue(), shaderAssetCreator, shaderPlatformInterface, response, azslData, mergedArguments, shaderOptionGroupLayout, shaderAssetSource, shaderAssetBuildTimestamp, srgAssets, bindingDependencies, rootConstantData, request); if (compileResult != AssetBuilderSDK::ProcessJobResult_Success) { response.m_resultCode = compileResult; return; } ++countOfCompiledPlatformInterfaces; } // end for all platforms if (!countOfCompiledPlatformInterfaces) { AZ_TracePrintf( ShaderAssetBuilderName, "No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n", request.m_sourceFile.c_str()); response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; return; } Data::Asset shaderAsset; if (!shaderAssetCreator.End(shaderAsset)) { response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; return; } if (!SerializeOutShaderAsset(shaderAsset, request.m_tempDirPath, response)) { response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; return; } response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; const AZStd::sys_time_t endTime = AZStd::GetTimeNowTicks(); const AZStd::sys_time_t deltaTime = endTime - startTime; const float elapsedTimeSeconds = (float)(deltaTime) / (float)AZStd::GetTimeTicksPerSecond(); AZ_TracePrintf(ShaderAssetBuilderName, "Finished processing %s in %.2f seconds\n", request.m_sourceFile.c_str(), elapsedTimeSeconds); ShaderBuilderUtility::LogProfilingData(ShaderAssetBuilderName, inputFiles->m_azslFileName); } } // ShaderBuilder } // AZ