/* * 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 #include #include #include #include #include #include #include #include AZ_DECLARE_BUDGET(AtomSampleViewer); namespace AtomSampleViewer { using namespace AZ; void AssetLoadTestComponent::Reflect(AZ::ReflectContext* context) { if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(0) ; } } AssetLoadTestComponent::AssetLoadTestComponent() : m_materialBrowser("@user@/AssetLoadTestComponent/material_browser.xml") , m_modelBrowser("@user@/AssetLoadTestComponent/model_browser.xml") , m_imguiSidebar("@user@/AssetLoadTestComponent/sidebar.xml") { m_sampleName = "AssetLoadTestComponent"; m_materialBrowser.SetFilter([](const AZ::Data::AssetInfo& assetInfo) { return assetInfo.m_assetType == azrtti_typeid(); }); m_modelBrowser.SetFilter([](const AZ::Data::AssetInfo& assetInfo) { return assetInfo.m_assetType == azrtti_typeid(); }); const AZStd::vector defaultMaterialAllowlist = { "materials/defaultpbr.azmaterial", "materials/presets/pbr/metal_aluminum_polished.azmaterial", "materials/basic_grey.azmaterial" }; m_materialBrowser.SetDefaultPinnedAssets(defaultMaterialAllowlist); m_pinnedMaterialCount = static_cast(m_materialBrowser.GetPinnedAssets().size()); const AZStd::vector defaultModelAllowist = { "Objects/bunny.fbx.azmodel", "Objects/Shaderball_simple.fbx.azmodel", "Objects/suzanne.fbx.azmodel", }; m_modelBrowser.SetDefaultPinnedAssets(defaultModelAllowist); } void AssetLoadTestComponent::Activate() { AZ::TickBus::Handler::BusConnect(); m_imguiSidebar.Activate(); m_materialBrowser.Activate(); m_modelBrowser.Activate(); if (!m_materialBrowser.IsConfigFileLoaded()) { AZ_TracePrintf("AssetLoadTestComponent", "Material allow list not loaded. Defaulting to built in list.\n"); m_materialBrowser.ResetPinnedAssetsToDefault(); } if (!m_modelBrowser.IsConfigFileLoaded()) { AZ_TracePrintf("AssetLoadTestComponent", "Model allow list not loaded. Defaulting to built in list.\n"); m_modelBrowser.ResetPinnedAssetsToDefault(); } // 25 was the original max before some changes that increased ENTITY_LATTEST_TEST_COMPONENT_MAX to 100. // AssetLoadTest was crashing (out of descriptors) at 50x50x42 so we put the limit back to 25^3. // Note that limiting to 40^3 will avoid the crash, but the sample doesn't work well at that scale, the UI // doesn't even show up because of a combination of low frame rate, the reload timers, and the time it takes // to load the models, and the fact that the UI is hidden while the models are loading. // So it would be good if we could work on increasing this limit. // (It would also be good if we could improve the design of this sample to make the UI persistent and more // responsive while the assets keep reloading). Base::SetLatticeMaxDimension(25); Base::Activate(); } void AssetLoadTestComponent::Deactivate() { m_materialBrowser.Deactivate(); m_modelBrowser.Deactivate(); AZ::TickBus::Handler::BusDisconnect(); m_imguiSidebar.Deactivate(); Base::Deactivate(); } void AssetLoadTestComponent::PrepareCreateLatticeInstances(uint32_t instanceCount) { m_modelInstanceData.reserve(instanceCount); } void AssetLoadTestComponent::CreateLatticeInstance(const AZ::Transform& transform) { m_modelInstanceData.emplace_back({}); ModelInstanceData& data = m_modelInstanceData.back(); data.m_modelAssetId = GetRandomModelId(); data.m_materialAssetId = GetRandomMaterialId(); data.m_transform = transform; } void AssetLoadTestComponent::FinalizeLatticeInstances() { AZStd::set assetIds; for (ModelInstanceData& instanceData : m_modelInstanceData) { if (instanceData.m_materialAssetId.IsValid()) { assetIds.insert(instanceData.m_materialAssetId); } if (instanceData.m_modelAssetId.IsValid()) { assetIds.insert(instanceData.m_modelAssetId); } } AZStd::vector assetList; for (auto& assetId : assetIds) { AZ::Data::AssetInfo assetInfo; AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, assetId); if (assetInfo.m_assetId.IsValid()) { AZ::AssetCollectionAsyncLoader::AssetToLoadInfo info; info.m_assetPath = assetInfo.m_relativePath; info.m_assetType = assetInfo.m_assetType; assetList.push_back(info); } } PreloadAssets(assetList); // pause script and tick until assets are ready ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::PauseScriptWithTimeout, 120.0f); AZ::TickBus::Handler::BusDisconnect(); } void AssetLoadTestComponent::OnAllAssetsReadyActivate() { for (ModelInstanceData& instanceData : m_modelInstanceData) { AZ::Data::Instance materialInstance; if (instanceData.m_materialAssetId.IsValid()) { AZ::Data::Asset materialAsset; materialAsset.Create(instanceData.m_materialAssetId, true); materialInstance = AZ::RPI::Material::FindOrCreate(materialAsset); // cache the material when its loaded m_cachedMaterials.insert(materialAsset); } if (instanceData.m_modelAssetId.IsValid()) { AZ::Render::MeshHandleDescriptor descriptor; descriptor.m_modelAsset.Create(instanceData.m_modelAssetId, true); descriptor.m_customMaterials[AZ::Render::DefaultCustomMaterialId].m_material = materialInstance; instanceData.m_meshHandle = GetMeshFeatureProcessor()->AcquireMesh(descriptor); GetMeshFeatureProcessor()->SetTransform(instanceData.m_meshHandle, instanceData.m_transform); } } ScriptRunnerRequestBus::Broadcast(&ScriptRunnerRequests::ResumeScript); AZ::TickBus::Handler::BusConnect(); } void AssetLoadTestComponent::DestroyLatticeInstances() { DestroyHandles(); m_modelInstanceData.clear(); } void AssetLoadTestComponent::DestroyHandles() { for (ModelInstanceData& instanceData : m_modelInstanceData) { GetMeshFeatureProcessor()->ReleaseMesh(instanceData.m_meshHandle); instanceData.m_meshHandle = {}; } } AZ::Data::AssetId AssetLoadTestComponent::GetRandomModelId() const { auto& modelAllowlist = m_modelBrowser.GetPinnedAssets(); if (modelAllowlist.size()) { const size_t randomModelIndex = rand() % modelAllowlist.size(); return modelAllowlist[randomModelIndex].m_assetId; } else { return AZ::RPI::AssetUtils::GetAssetIdForProductPath("testdata/objects/cube/cube.fbx.azmodel", AZ::RPI::AssetUtils::TraceLevel::Error); } } AZ::Data::AssetId AssetLoadTestComponent::GetRandomMaterialId() const { auto& materialAllowlist = m_materialBrowser.GetPinnedAssets(); if (materialAllowlist.size()) { const size_t randomMaterialIndex = rand() % materialAllowlist.size(); return materialAllowlist[randomMaterialIndex].m_assetId; } else { return AZ::RPI::AssetUtils::GetAssetIdForProductPath(DefaultPbrMaterialPath, AZ::RPI::AssetUtils::TraceLevel::Error); } } void AssetLoadTestComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint scriptTime) { AZ_PROFILE_FUNCTION(AtomSampleViewer); const float timeSeconds = static_cast(scriptTime.GetSeconds()); if (m_lastMaterialSwitchInSeconds == 0 || m_lastModelSwitchInSeconds == 0) { m_lastMaterialSwitchInSeconds = timeSeconds; m_lastModelSwitchInSeconds = timeSeconds; return; } bool materialSwitchRequested = m_materialSwitchEnabled && timeSeconds - m_lastMaterialSwitchInSeconds >= m_materialSwitchTimeInSeconds; bool modelSwitchRequested = m_modelSwitchEnabled && timeSeconds - m_lastModelSwitchInSeconds >= m_modelSwitchTimeInSeconds; if (m_updateTransformEnabled) { float radians = static_cast(fmod(scriptTime.GetSeconds(), AZ::Constants::TwoPi)); AZ::Vector3 rotation(radians, radians, radians); AZ::Transform rotationTransform; rotationTransform.SetFromEulerRadians(rotation); for (ModelInstanceData& instanceData : m_modelInstanceData) { GetMeshFeatureProcessor()->SetTransform(instanceData.m_meshHandle, instanceData.m_transform * rotationTransform); } } bool materialsChanged = false; bool modelsChanged = false; if (m_imguiSidebar.Begin()) { ImGui::Checkbox("Switch Materials Every N Seconds", &m_materialSwitchEnabled); ImGui::SliderFloat("##MaterialSwitchTime", &m_materialSwitchTimeInSeconds, 0.1f, 10.0f); ImGui::Spacing(); ImGui::Checkbox("Switch Models Every N Seconds", &m_modelSwitchEnabled); ImGui::SliderFloat("##ModelSwitchTime", &m_modelSwitchTimeInSeconds, 0.1f, 10.0f); ImGui::Spacing(); ImGui::Checkbox("Update Transforms Every Frame", &m_updateTransformEnabled); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); RenderImGuiLatticeControls(); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGuiAssetBrowser::WidgetSettings assetBrowserSettings; assetBrowserSettings.m_labels.m_pinnedAssetList = "Allow List"; assetBrowserSettings.m_labels.m_pinButton = "Add To Allow List"; assetBrowserSettings.m_labels.m_unpinButton = "Remove From Allow List"; assetBrowserSettings.m_labels.m_root = "Materials"; m_materialBrowser.Tick(assetBrowserSettings); auto& pinnedMaterials = m_materialBrowser.GetPinnedAssets(); materialsChanged = pinnedMaterials.size() != m_pinnedMaterialCount; if (materialsChanged) { m_pinnedMaterialCount = static_cast(pinnedMaterials.size()); // Keep the current m_cachedMaterials to avoid release-load the same material MaterialAssetSet newCache; // clean up cached material which refcount is 1 for (auto& pinnedMaterial : pinnedMaterials) { AZ::Data::AssetId materialAssetid = pinnedMaterial.m_assetId; // Cache the asset if it's loaded AZ::Data::Asset asset = AZ::Data::AssetManager::Instance().FindAsset(materialAssetid, AZ::Data::AssetLoadBehavior::PreLoad); if (asset.IsReady()) { newCache.insert(asset); } } m_cachedMaterials = newCache; } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); assetBrowserSettings.m_labels.m_root = "Models"; m_modelBrowser.Tick(assetBrowserSettings); modelsChanged = m_lastPinnedModelCount != m_modelBrowser.GetPinnedAssets().size(); m_imguiSidebar.End(); } if (materialSwitchRequested || materialsChanged) { for (ModelInstanceData& instanceData : m_modelInstanceData) { instanceData.m_materialAssetId = GetRandomMaterialId(); } m_lastMaterialSwitchInSeconds = timeSeconds; } if (modelSwitchRequested || modelsChanged) { m_lastPinnedModelCount = m_modelBrowser.GetPinnedAssets().size(); for (ModelInstanceData& instanceData : m_modelInstanceData) { instanceData.m_modelAssetId = GetRandomModelId(); } m_lastModelSwitchInSeconds = timeSeconds; } if (materialSwitchRequested || materialsChanged || modelSwitchRequested || modelsChanged) { DestroyHandles(); FinalizeLatticeInstances(); } } } // namespace AtomSampleViewer