/** * Executes async DDC gets for chunks stored in the derived data cache. * @param Chunks - Chunks to retrieve. * @param FirstChunkToLoad - Index of the first chunk to retrieve. * @param OutHandles - Handles to the asynchronous DDC gets. */ static void BeginLoadDerivedChunks(TIndirectArray<FStreamedAudioChunk>& Chunks, int32 FirstChunkToLoad, TArray<uint32>& OutHandles) { FDerivedDataCacheInterface& DDC = GetDerivedDataCacheRef(); OutHandles.AddZeroed(Chunks.Num()); for (int32 ChunkIndex = FirstChunkToLoad; ChunkIndex < Chunks.Num(); ++ChunkIndex) { const FStreamedAudioChunk& Chunk = Chunks[ChunkIndex]; if (!Chunk.DerivedDataKey.IsEmpty()) { OutHandles[ChunkIndex] = DDC.GetAsynchronous(*Chunk.DerivedDataKey); } } }
int8 ComputeLODForMeshes( const TIndirectArray<class FStaticMesh>& StaticMeshes, const FSceneView& View, const FVector4& Origin, float SphereRadius, int32 ForcedLODLevel, float ScreenSizeScale ) { int8 LODToRender = 0; // Handle forced LOD level first if(ForcedLODLevel >= 0) { int8 MaxLOD = 0; for(int32 MeshIndex = 0 ; MeshIndex < StaticMeshes.Num() ; ++MeshIndex) { const FStaticMesh& Mesh = StaticMeshes[MeshIndex]; MaxLOD = FMath::Max(MaxLOD, Mesh.LODIndex); } LODToRender = FMath::Clamp<int8>(ForcedLODLevel, 0, MaxLOD); } else if (View.Family->EngineShowFlags.LOD) { int32 MinLODFound = INT_MAX; bool bFoundLOD = false; int32 NumMeshes = StaticMeshes.Num(); const float ScreenSize = ComputeBoundsScreenSize(Origin, SphereRadius, View); for(int32 MeshIndex = NumMeshes-1 ; MeshIndex >= 0 ; --MeshIndex) { const FStaticMesh& Mesh = StaticMeshes[MeshIndex]; float MeshScreenSize = Mesh.ScreenSize * ScreenSizeScale; if(MeshScreenSize >= ScreenSize) { LODToRender = Mesh.LODIndex; bFoundLOD = true; break; } MinLODFound = FMath::Min<int32>(MinLODFound, Mesh.LODIndex); } // If no LOD was found matching the screen size, use the lowest in the array instead of LOD 0, to handle non-zero MinLOD if (!bFoundLOD) { LODToRender = MinLODFound; } } return LODToRender; }
int8 ComputeLODForMeshes( const TIndirectArray<class FStaticMesh>& StaticMeshes, const FSceneView& View, const FVector4& Origin, float SphereRadius, int32 ForcedLODLevel, float ScreenSizeScale ) { int8 LODToRender = 0; // Handle forced LOD level first if(ForcedLODLevel >= 0) { int8 MaxLOD = 0; for(int32 MeshIndex = 0 ; MeshIndex < StaticMeshes.Num() ; ++MeshIndex) { const FStaticMesh& Mesh = StaticMeshes[MeshIndex]; MaxLOD = FMath::Max(MaxLOD, Mesh.LODIndex); } LODToRender = FMath::Clamp<int8>(ForcedLODLevel, 0, MaxLOD); } else { int32 NumMeshes = StaticMeshes.Num(); const float ScreenSize = ComputeBoundsScreenSize(Origin, SphereRadius, View); for(int32 MeshIndex = NumMeshes-1 ; MeshIndex >= 0 ; --MeshIndex) { const FStaticMesh& Mesh = StaticMeshes[MeshIndex]; if(View.Family->EngineShowFlags.LOD == 1) { float MeshScreenSize = Mesh.ScreenSize * ScreenSizeScale; if(MeshScreenSize >= ScreenSize) { LODToRender = Mesh.LODIndex; break; } } } } return LODToRender; }
bool FDesktopPlatformWindows::UpdateFileAssociations() { TIndirectArray<FRegistryRootedKey> Keys; GetRequiredRegistrySettings(Keys); for (int32 Idx = 0; Idx < Keys.Num(); Idx++) { if (!Keys[Idx].Write(true)) { return false; } } return true; }
/** * Executes all pending shadow-map encoding requests. * @param InWorld World in which the textures exist * @param bLightingSuccessful Whether the lighting build was successful or not. */ void FShadowMap2D::EncodeTextures(UWorld* InWorld , bool bLightingSuccessful ) { if ( bLightingSuccessful ) { GWarn->BeginSlowTask( NSLOCTEXT("ShadowMap2D", "BeginEncodingShadowMapsTask", "Encoding shadow-maps"), false ); const int32 PackedLightAndShadowMapTextureSize = InWorld->GetWorldSettings()->PackedLightAndShadowMapTextureSize; // Reset the pending shadow-map size. PendingShadowMapSize = 0; Sort(PendingShadowMaps.GetData(), PendingShadowMaps.Num(), FCompareShadowMaps()); // Allocate texture space for each light-map. TIndirectArray<FShadowMapPendingTexture> PendingTextures; for(int32 ShadowMapIndex = 0;ShadowMapIndex < PendingShadowMaps.Num();ShadowMapIndex++) { FShadowMapAllocation& Allocation = PendingShadowMaps[ShadowMapIndex]; // Find an existing texture which the light-map can be stored in. FShadowMapPendingTexture* Texture = NULL; for(int32 TextureIndex = 0;TextureIndex < PendingTextures.Num();TextureIndex++) { FShadowMapPendingTexture& ExistingTexture = PendingTextures[TextureIndex]; // See if the new one will fit in the existing texture if ( ExistingTexture.AddElement( Allocation ) ) { Texture = &ExistingTexture; break; } } // If there is no appropriate texture, create a new one. if(!Texture) { int32 NewTextureSizeX = PackedLightAndShadowMapTextureSize; int32 NewTextureSizeY = PackedLightAndShadowMapTextureSize; if(Allocation.MappedRect.Width() > NewTextureSizeX || Allocation.MappedRect.Height() > NewTextureSizeY) { NewTextureSizeX = FMath::RoundUpToPowerOfTwo(Allocation.MappedRect.Width()); NewTextureSizeY = FMath::RoundUpToPowerOfTwo(Allocation.MappedRect.Height()); } // If there is no existing appropriate texture, create a new one. Texture = ::new(PendingTextures) FShadowMapPendingTexture(NewTextureSizeX,NewTextureSizeY); Texture->Outer = Allocation.TextureOuter; Texture->Bounds = Allocation.Bounds; Texture->ShadowmapFlags = Allocation.ShadowmapFlags; verify( Texture->AddElement( Allocation, true ) ); } } for(int32 TextureIndex = 0;TextureIndex < PendingTextures.Num();TextureIndex++) { if (bUpdateStatus && (TextureIndex % 20) == 0) { GWarn->UpdateProgress(TextureIndex, PendingTextures.Num()); } FShadowMapPendingTexture& PendingTexture = PendingTextures[TextureIndex]; PendingTexture.StartEncoding(InWorld); } PendingTextures.Empty(); PendingShadowMaps.Empty(); GWarn->EndSlowTask(); } else { PendingShadowMaps.Empty(); } }
/** Add a new statistic to the internal map (or update an existing one) from the supplied component */ UPrimitiveStats* Add(UPrimitiveComponent* InPrimitiveComponent, EPrimitiveObjectSets InObjectSet) { // Objects in transient package or transient objects are not part of level. if( InPrimitiveComponent->GetOutermost() == GetTransientPackage() || InPrimitiveComponent->HasAnyFlags( RF_Transient ) ) { return NULL; } // Owned by a default object? Not part of a level either. if(InPrimitiveComponent->GetOuter() && InPrimitiveComponent->GetOuter()->IsDefaultSubobject() ) { return NULL; } UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(InPrimitiveComponent); UModelComponent* ModelComponent = Cast<UModelComponent>(InPrimitiveComponent); USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(InPrimitiveComponent); ULandscapeComponent* LandscapeComponent = Cast<ULandscapeComponent>(InPrimitiveComponent); UObject* Resource = NULL; AActor* ActorOuter = Cast<AActor>(InPrimitiveComponent->GetOuter()); int32 VertexColorMem = 0; int32 InstVertexColorMem = 0; // Calculate number of direct and other lights relevant to this component. int32 LightsLMCount = 0; int32 LightsOtherCount = 0; bool bUsesOnlyUnlitMaterials = InPrimitiveComponent->UsesOnlyUnlitMaterials(); // The static mesh is a static mesh component's resource. if( StaticMeshComponent ) { UStaticMesh* Mesh = StaticMeshComponent->StaticMesh; Resource = Mesh; // Calculate vertex color memory on the actual mesh. if( Mesh && Mesh->RenderData ) { // Accumulate memory for each LOD for( int32 LODIndex = 0; LODIndex < Mesh->RenderData->LODResources.Num(); ++LODIndex ) { VertexColorMem += Mesh->RenderData->LODResources[LODIndex].ColorVertexBuffer.GetAllocatedSize(); } } // Calculate instanced vertex color memory used on the component. for( int32 LODIndex = 0; LODIndex < StaticMeshComponent->LODData.Num(); ++LODIndex ) { // Accumulate memory for each LOD const FStaticMeshComponentLODInfo& LODInfo = StaticMeshComponent->LODData[ LODIndex ]; if( LODInfo.OverrideVertexColors ) { InstVertexColorMem += LODInfo.OverrideVertexColors->GetAllocatedSize(); } } // Calculate the number of lightmap and shadow map lights if( !bUsesOnlyUnlitMaterials ) { if( StaticMeshComponent->LODData.Num() > 0 ) { FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[0]; if( ComponentLODInfo.LightMap ) { LightsLMCount = ComponentLODInfo.LightMap->LightGuids.Num(); } } } } // A model component is its own resource. else if( ModelComponent ) { // Make sure model component is referenced by level. ULevel* Level = CastChecked<ULevel>(ModelComponent->GetOuter()); if( Level->ModelComponents.Find( ModelComponent ) != INDEX_NONE ) { Resource = ModelComponent->GetModel(); // Calculate the number of lightmap and shadow map lights if( !bUsesOnlyUnlitMaterials ) { const TIndirectArray<FModelElement> Elements = ModelComponent->GetElements(); if( Elements.Num() > 0 ) { if( Elements[0].LightMap ) { LightsLMCount = Elements[0].LightMap->LightGuids.Num(); } } } } } // The skeletal mesh of a skeletal mesh component is its resource. else if( SkeletalMeshComponent ) { USkeletalMesh* Mesh = SkeletalMeshComponent->SkeletalMesh; Resource = Mesh; // Calculate vertex color usage for skeletal meshes if( Mesh ) { FSkeletalMeshResource* SkelMeshResource = Mesh->GetResourceForRendering(); for( int32 LODIndex = 0; LODIndex < SkelMeshResource->LODModels.Num(); ++LODIndex ) { const FStaticLODModel& LODModel = SkelMeshResource->LODModels[ LODIndex ]; VertexColorMem += LODModel.ColorVertexBuffer.GetVertexDataSize(); } } } // The landscape of a landscape component is its resource. else if (LandscapeComponent) { Resource = LandscapeComponent->GetLandscapeProxy(); if (LandscapeComponent->LightMap) { LightsLMCount = LandscapeComponent->LightMap->LightGuids.Num(); } } UWorld* World = InPrimitiveComponent->GetWorld(); // check(World); // @todo: re-instate this check once the GWorld migration has completed /// If we should skip the actor. Skip if the actor has no outer or if we are only showing selected actors and the actor isn't selected const bool bShouldSkip = World == NULL || ActorOuter == NULL || (ActorOuter != NULL && InObjectSet == PrimitiveObjectSets_SelectedObjects && ActorOuter->IsSelected() == false ); // Dont' care about components without a resource. if( Resource // Require actor association for selection and to disregard mesh emitter components. The exception being model components. && (!bShouldSkip || (ModelComponent && InObjectSet != PrimitiveObjectSets_SelectedObjects ) ) // Only list primitives in visible levels && IsInVisibleLevel( InPrimitiveComponent, World ) // Don't list pending kill components. && !InPrimitiveComponent->IsPendingKill() ) { // Retrieve relevant lights. TArray<const ULightComponent*> RelevantLights; World->Scene->GetRelevantLights( InPrimitiveComponent, &RelevantLights ); // Only look for relevant lights if we aren't unlit. if( !bUsesOnlyUnlitMaterials ) { // Lightmap and shadow map lights are calculated above, per component type, infer the "other" light count here LightsOtherCount = RelevantLights.Num() >= LightsLMCount ? RelevantLights.Num() - LightsLMCount : 0; } // Figure out memory used by light and shadow maps and light/ shadow map resolution. int32 LightMapWidth = 0; int32 LightMapHeight = 0; InPrimitiveComponent->GetLightMapResolution( LightMapWidth, LightMapHeight ); int32 LMSMResolution = FMath::Sqrt( LightMapHeight * LightMapWidth ); int32 LightMapData = 0; int32 LegacyShadowMapData = 0; InPrimitiveComponent->GetLightAndShadowMapMemoryUsage( LightMapData, LegacyShadowMapData ); // Check whether we already have an entry for the associated static mesh. UPrimitiveStats** StatsEntryPtr = ResourceToStatsMap.Find( Resource ); if( StatsEntryPtr ) { check(*StatsEntryPtr); UPrimitiveStats* StatsEntry = *StatsEntryPtr; // We do. Update existing entry. StatsEntry->Count++; StatsEntry->Actors.AddUnique(ActorOuter); StatsEntry->RadiusMin = FMath::Min( StatsEntry->RadiusMin, InPrimitiveComponent->Bounds.SphereRadius ); StatsEntry->RadiusMax = FMath::Max( StatsEntry->RadiusMax, InPrimitiveComponent->Bounds.SphereRadius ); StatsEntry->RadiusAvg += InPrimitiveComponent->Bounds.SphereRadius; StatsEntry->LightsLM += LightsLMCount; StatsEntry->LightsOther += LightsOtherCount; StatsEntry->LightMapData += (float)LightMapData / 1024.0f; StatsEntry->LMSMResolution += LMSMResolution; StatsEntry->UpdateNames(); if ( !ModelComponent && !LandscapeComponent ) { // Count instanced sections StatsEntry->InstSections += StatsEntry->Sections; StatsEntry->InstTriangles += StatsEntry->Triangles; } // ... in the case of a model component (aka BSP). if( ModelComponent ) { // If Count represents the Model itself, we do NOT want to increment it now. StatsEntry->Count--; for (const auto& Element : ModelComponent->GetElements()) { StatsEntry->Triangles += Element.NumTriangles; StatsEntry->Sections++; } StatsEntry->InstSections = StatsEntry->Sections; StatsEntry->InstTriangles = StatsEntry->Triangles; } else if( StaticMeshComponent ) { // This stat is used by multiple components so accumulate instanced vertex color memory. StatsEntry->InstVertexColorMem += (float)InstVertexColorMem / 1024.0f; } else if (LandscapeComponent) { // If Count represents the Landscape itself, we do NOT want to increment it now. StatsEntry->Count--; } } else { // We don't. Create new base entry. UPrimitiveStats* NewStatsEntry = NewObject<UPrimitiveStats>(); NewStatsEntry->AddToRoot(); NewStatsEntry->Object = Resource; NewStatsEntry->Actors.AddUnique(ActorOuter); NewStatsEntry->Count = 1; NewStatsEntry->Triangles = 0; NewStatsEntry->InstTriangles = 0; NewStatsEntry->ResourceSize = (float)(FArchiveCountMem(Resource).GetNum() + Resource->GetResourceSize(EResourceSizeMode::Exclusive)) / 1024.0f; NewStatsEntry->Sections = 0; NewStatsEntry->InstSections = 0; NewStatsEntry->RadiusMin = InPrimitiveComponent->Bounds.SphereRadius; NewStatsEntry->RadiusAvg = InPrimitiveComponent->Bounds.SphereRadius; NewStatsEntry->RadiusMax = InPrimitiveComponent->Bounds.SphereRadius; NewStatsEntry->LightsLM = LightsLMCount; NewStatsEntry->LightsOther = (float)LightsOtherCount; NewStatsEntry->LightMapData = (float)LightMapData / 1024.0f; NewStatsEntry->LMSMResolution = LMSMResolution; NewStatsEntry->VertexColorMem = (float)VertexColorMem / 1024.0f; NewStatsEntry->InstVertexColorMem = (float)InstVertexColorMem / 1024.0f; NewStatsEntry->UpdateNames(); // Fix up triangle and section count... // ... in the case of a static mesh component. if( StaticMeshComponent ) { UStaticMesh* StaticMesh = StaticMeshComponent->StaticMesh; if( StaticMesh && StaticMesh->RenderData ) { for( int32 SectionIndex=0; SectionIndex<StaticMesh->RenderData->LODResources[0].Sections.Num(); SectionIndex++ ) { const FStaticMeshSection& StaticMeshSection = StaticMesh->RenderData->LODResources[0].Sections[SectionIndex]; NewStatsEntry->Triangles += StaticMeshSection.NumTriangles; NewStatsEntry->Sections++; } } } // ... in the case of a model component (aka BSP). else if( ModelComponent ) { TIndirectArray<FModelElement> Elements = ModelComponent->GetElements(); for( int32 ElementIndex=0; ElementIndex<Elements.Num(); ElementIndex++ ) { const FModelElement& Element = Elements[ElementIndex]; NewStatsEntry->Triangles += Element.NumTriangles; NewStatsEntry->Sections++; } } // ... in the case of skeletal mesh component. else if( SkeletalMeshComponent ) { USkeletalMesh* SkeletalMesh = SkeletalMeshComponent->SkeletalMesh; if( SkeletalMesh ) { FSkeletalMeshResource* SkelMeshResource = SkeletalMesh->GetResourceForRendering(); if (SkelMeshResource->LODModels.Num()) { const FStaticLODModel& BaseLOD = SkelMeshResource->LODModels[0]; for( int32 SectionIndex=0; SectionIndex<BaseLOD.Sections.Num(); SectionIndex++ ) { const FSkelMeshSection& Section = BaseLOD.Sections[SectionIndex]; NewStatsEntry->Triangles += Section.NumTriangles; NewStatsEntry->Sections++; } } } } else if (LandscapeComponent) { TSet<UTexture2D*> UniqueTextures; for (auto ItComponents = LandscapeComponent->GetLandscapeProxy()->LandscapeComponents.CreateConstIterator(); ItComponents; ++ItComponents) { const ULandscapeComponent* CurrentComponent = *ItComponents; // count triangles and sections in the landscape NewStatsEntry->Triangles += FMath::Square(CurrentComponent->ComponentSizeQuads) * 2; NewStatsEntry->Sections += FMath::Square(CurrentComponent->NumSubsections); // count resource usage of landscape bool bNotUnique = false; UniqueTextures.Add(CurrentComponent->HeightmapTexture, &bNotUnique); if (!bNotUnique) { NewStatsEntry->ResourceSize += CurrentComponent->HeightmapTexture->GetResourceSize(EResourceSizeMode::Exclusive); } if (CurrentComponent->XYOffsetmapTexture) { UniqueTextures.Add(CurrentComponent->XYOffsetmapTexture, &bNotUnique); if (!bNotUnique) { NewStatsEntry->ResourceSize += CurrentComponent->XYOffsetmapTexture->GetResourceSize(EResourceSizeMode::Exclusive); } } for (auto ItWeightmaps = CurrentComponent->WeightmapTextures.CreateConstIterator(); ItWeightmaps; ++ItWeightmaps) { UniqueTextures.Add((*ItWeightmaps), &bNotUnique); if (!bNotUnique) { NewStatsEntry->ResourceSize += (*ItWeightmaps)->GetResourceSize(EResourceSizeMode::Exclusive); } } } } NewStatsEntry->InstTriangles = NewStatsEntry->Triangles; NewStatsEntry->InstSections = NewStatsEntry->Sections; // Add to map. ResourceToStatsMap.Add( Resource, NewStatsEntry ); return NewStatsEntry; } } return NULL; }
void FPaperAtlasGenerator::HandleAssetChangedEvent(UPaperSpriteAtlas* Atlas) { Atlas->MaxWidth = FMath::Clamp(Atlas->MaxWidth, 16, 4096); Atlas->MaxHeight = FMath::Clamp(Atlas->MaxHeight, 16, 4096); // Find all sprites that reference the atlas and force them loaded FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")); TArray<FAssetData> AssetList; TMultiMap<FName, FString> TagsAndValues; TagsAndValues.Add(TEXT("AtlasGroupGUID"), Atlas->AtlasGUID.ToString(EGuidFormats::Digits)); AssetRegistryModule.Get().GetAssetsByTagValues(TagsAndValues, AssetList); TIndirectArray<FSingleTexturePaperAtlas> AtlasGenerators; // Start off with one page new (AtlasGenerators) FSingleTexturePaperAtlas(Atlas->MaxWidth, Atlas->MaxHeight); for (const FAssetData& SpriteRef : AssetList) { if (UPaperSprite* Sprite = Cast<UPaperSprite>(SpriteRef.GetAsset())) { //@TODO: Use the tight bounds instead of the source bounds const FVector2D SpriteSizeFloat = Sprite->GetSourceSize(); const FIntPoint SpriteSize(FMath::TruncToInt(SpriteSizeFloat.X), FMath::TruncToInt(SpriteSizeFloat.Y)); if (Sprite->GetSourceTexture() == nullptr) { UE_LOG(LogPaper2DEditor, Error, TEXT("Sprite %s has no source texture and cannot be packed"), *(Sprite->GetPathName())); continue; } //@TODO: Take padding into account (push this into the generator) if ((SpriteSize.X > Atlas->MaxWidth) || (SpriteSize.Y >= Atlas->MaxHeight)) { // This sprite cannot ever fit into an atlas page UE_LOG(LogPaper2DEditor, Error, TEXT("Sprite %s (%d x %d) can never fit into atlas %s (%d x %d) due to maximum page size restrictions"), *(Sprite->GetPathName()), SpriteSize.X, SpriteSize.Y, *(Atlas->GetPathName()), Atlas->MaxWidth, Atlas->MaxHeight); } else { const FAtlasedTextureSlot* Slot = nullptr; // Does it fit in any existing pages? for (auto& Generator : AtlasGenerators) { Slot = Generator.AddSprite(Sprite); if (Slot != nullptr) { break; } } if (Slot == nullptr) { // Doesn't fit in any current pages, make a new one FSingleTexturePaperAtlas* NewGenerator = new (AtlasGenerators) FSingleTexturePaperAtlas(Atlas->MaxWidth, Atlas->MaxHeight); Slot = NewGenerator->AddSprite(Sprite); } if (Slot != nullptr) { UE_LOG(LogPaper2DEditor, Warning, TEXT("Allocated %s to (%d,%d)"), *(Sprite->GetPathName()), Slot->X, Slot->Y); } else { // ERROR: Didn't fit into a brand new page, maybe padding was wrong UE_LOG(LogPaper2DEditor, Error, TEXT("Failed to allocate %s into a brand new page"), *(Sprite->GetPathName())); } } } } // Turn the generators back into textures Atlas->GeneratedTextures.Empty(AtlasGenerators.Num()); for (int32 GeneratorIndex = 0; GeneratorIndex < AtlasGenerators.Num(); ++GeneratorIndex) { FSingleTexturePaperAtlas& AtlasPage = AtlasGenerators[GeneratorIndex]; UTexture2D* Texture = NewNamedObject<UTexture2D>(Atlas, NAME_None, RF_Public); Atlas->GeneratedTextures.Add(Texture); AtlasPage.CopyToTexture(Texture); // Now update the baked data for all the sprites to point there for (auto& SpriteToSlot : AtlasPage.SpriteToSlotMap) { UPaperSprite* Sprite = SpriteToSlot.Key; Sprite->Modify(); const FAtlasedTextureSlot* Slot = SpriteToSlot.Value; Sprite->BakedSourceTexture = Texture; Sprite->BakedSourceUV = FVector2D(Slot->X, Slot->Y); Sprite->RebuildRenderData(); } } //@TODO: Adjust the atlas rebuild code so that it tries to preserve existing sprites (optimize for the 'just added one to the end' case, preventing dirtying lots of assets unnecessarily) //@TODO: invoke this code when a sprite has the atlas group property modified }
void UStaticMesh::SerializeLegacySouceData(FArchive& Ar, const FBoxSphereBounds& LegacyBounds) { int32 InternalVersion = 0; bool bHaveSourceData = false; TArray<FStaticMeshOptimizationSettings> OptimizationSettings; bool bHasBeenSimplified = false; Ar << InternalVersion; Ar << bHaveSourceData; if (bHaveSourceData) { FStaticMeshSourceModel& SrcModel = *new(SourceModels) FStaticMeshSourceModel(); // Serialize in the old source render data. FLegacyStaticMeshRenderData TempRenderData; TempRenderData.Serialize(Ar, this, INDEX_NONE); // Convert it to a raw mesh. FRawMesh RawMesh; BuildRawMeshFromRenderData(RawMesh, SrcModel.BuildSettings, TempRenderData, *GetPathName()); // Store the raw mesh as our source model. SrcModel.RawMeshBulkData->SaveRawMesh(RawMesh); } Ar << OptimizationSettings; Ar << bHasBeenSimplified; // Convert any old optimization settings to the source model's reduction settings. for (int32 i = 0; i < OptimizationSettings.Num(); ++i) { if (!SourceModels.IsValidIndex(i)) { new(SourceModels) FStaticMeshSourceModel(); } FStaticMeshSourceModel& SrcModel = SourceModels[i]; FMeshReductionSettings& NewSettings = SrcModel.ReductionSettings; switch (OptimizationSettings[i].ReductionMethod) { case OT_NumOfTriangles: NewSettings.PercentTriangles = FMath::Clamp(OptimizationSettings[i].NumOfTrianglesPercentage / 100.0f, 0.0f, 1.0f); NewSettings.MaxDeviation = 0.0f; break; case OT_MaxDeviation: NewSettings.PercentTriangles = 1.0f; NewSettings.MaxDeviation = OptimizationSettings[i].MaxDeviationPercentage * LegacyBounds.SphereRadius; break; default: NewSettings.PercentTriangles = 1.0f; NewSettings.MaxDeviation = 0.0f; break; } NewSettings.WeldingThreshold = OptimizationSettings[i].WeldingThreshold; NewSettings.HardAngleThreshold = OptimizationSettings[i].NormalsThreshold; NewSettings.SilhouetteImportance = (EMeshFeatureImportance::Type)OptimizationSettings[i].SilhouetteImportance; NewSettings.TextureImportance = (EMeshFeatureImportance::Type)OptimizationSettings[i].TextureImportance; NewSettings.ShadingImportance = (EMeshFeatureImportance::Type)OptimizationSettings[i].ShadingImportance; } // Serialize in any legacy LOD models. TIndirectArray<FLegacyStaticMeshRenderData> LegacyLODModels; LegacyLODModels.Serialize(Ar, this); TArray<FLegacyStaticMeshLODInfo> LegacyLODInfo; Ar << LegacyLODInfo; for (int32 LODIndex = 0; LODIndex < LegacyLODModels.Num(); ++LODIndex) { if (!SourceModels.IsValidIndex(LODIndex)) { new(SourceModels) FStaticMeshSourceModel(); } FStaticMeshSourceModel& SrcModel = SourceModels[LODIndex]; // Really we want to use LOD0 only if the mesh has /not/ been simplified. // Unfortunately some data seems to be corrupted in source meshes but not LOD0, so just use LOD0. //if (SrcModel.RawMeshBulkData->IsEmpty() && (!bHasBeenSimplified || LODIndex == 0)) { // Store the raw mesh for this LOD. FRawMesh RawMesh; BuildRawMeshFromRenderData(RawMesh, SrcModel.BuildSettings, LegacyLODModels[LODIndex], *GetPathName()); SrcModel.RawMeshBulkData->SaveRawMesh(RawMesh); } // And make sure to clear reduction settings for LOD0. if (LODIndex == 0) { FMeshReductionSettings DefaultSettings; SrcModel.ReductionSettings = DefaultSettings; } // Always use a hash as the guid for legacy models. SrcModel.RawMeshBulkData->UseHashAsGuid(this); // Setup the material map for this LOD model. if (LODIndex == 0) { for (int32 SectionIndex = 0; SectionIndex < LegacyLODModels[LODIndex].Elements.Num(); ++SectionIndex) { FMeshSectionInfo Info; Info.MaterialIndex = Materials.Add(LegacyLODModels[LODIndex].Elements[SectionIndex].Material); Info.bEnableCollision = LegacyLODModels[LODIndex].Elements[SectionIndex].EnableCollision; Info.bCastShadow = LegacyLODModels[LODIndex].Elements[SectionIndex].bEnableShadowCasting; SectionInfoMap.Set(LODIndex, SectionIndex, Info); } } else { for (int32 SectionIndex = 0; SectionIndex < LegacyLODModels[LODIndex].Elements.Num(); ++SectionIndex) { FMeshSectionInfo Info; Info.MaterialIndex = Materials.AddUnique(LegacyLODModels[LODIndex].Elements[SectionIndex].Material); Info.bEnableCollision = LegacyLODModels[LODIndex].Elements[SectionIndex].EnableCollision; Info.bCastShadow = LegacyLODModels[LODIndex].Elements[SectionIndex].bEnableShadowCasting; SectionInfoMap.Set(LODIndex, SectionIndex, Info); } } } }
/** * Executes all pending shadow-map encoding requests. * @param InWorld World in which the textures exist * @param bLightingSuccessful Whether the lighting build was successful or not. */ void FShadowMap2D::EncodeTextures(UWorld* InWorld , bool bLightingSuccessful) { if ( bLightingSuccessful ) { GWarn->BeginSlowTask( NSLOCTEXT("ShadowMap2D", "BeginEncodingShadowMapsTask", "Encoding shadow-maps"), false ); const int32 PackedLightAndShadowMapTextureSize = InWorld->GetWorldSettings()->PackedLightAndShadowMapTextureSize; // Reset the pending shadow-map size. PendingShadowMapSize = 0; Sort(PendingShadowMaps.GetData(), PendingShadowMaps.Num(), FCompareShadowMaps()); // Allocate texture space for each shadow-map. TIndirectArray<FShadowMapPendingTexture> PendingTextures; for (FShadowMapAllocationGroup& PendingGroup : PendingShadowMaps) { if (!ensure(PendingGroup.Allocations.Num() >= 1)) { continue; } int32 MaxWidth = 0; int32 MaxHeight = 0; for (auto& Allocation : PendingGroup.Allocations) { MaxWidth = FMath::Max(MaxWidth, Allocation->MappedRect.Width()); MaxHeight = FMath::Max(MaxHeight, Allocation->MappedRect.Height()); } FShadowMapPendingTexture* Texture = nullptr; // Find an existing texture which the shadow-map can be stored in. // Shadowmaps will always be 4-pixel aligned... for (FShadowMapPendingTexture& ExistingTexture : PendingTextures) { if (ExistingTexture.AddElement(PendingGroup)) { Texture = &ExistingTexture; break; } } if (!Texture) { int32 NewTextureSizeX = PackedLightAndShadowMapTextureSize; int32 NewTextureSizeY = PackedLightAndShadowMapTextureSize; // Assumes identically-sized allocations, fit into the smallest square const int32 AllocationCountX = FMath::CeilToInt(FMath::Sqrt(FMath::DivideAndRoundUp(PendingGroup.Allocations.Num() * MaxHeight, MaxWidth))); const int32 AllocationCountY = FMath::DivideAndRoundUp(PendingGroup.Allocations.Num(), AllocationCountX); const int32 AllocationSizeX = AllocationCountX * MaxWidth; const int32 AllocationSizeY = AllocationCountY * MaxHeight; if (AllocationSizeX > NewTextureSizeX || AllocationSizeY > NewTextureSizeY) { NewTextureSizeX = FMath::RoundUpToPowerOfTwo(AllocationSizeX); NewTextureSizeY = FMath::RoundUpToPowerOfTwo(AllocationSizeY); } // If there is no existing appropriate texture, create a new one. Texture = new FShadowMapPendingTexture(NewTextureSizeX, NewTextureSizeY); PendingTextures.Add(Texture); Texture->Outer = PendingGroup.TextureOuter; Texture->Bounds = PendingGroup.Bounds; Texture->ShadowmapFlags = PendingGroup.ShadowmapFlags; verify(Texture->AddElement(PendingGroup)); } // Give the texture ownership of the allocations for (auto& Allocation : PendingGroup.Allocations) { Texture->Allocations.Add(Allocation.Release()); } } PendingShadowMaps.Empty(); // Encode all the pending textures. for (int32 TextureIndex = 0; TextureIndex < PendingTextures.Num(); TextureIndex++) { if (bUpdateStatus && (TextureIndex % 20) == 0) { GWarn->UpdateProgress(TextureIndex, PendingTextures.Num()); } FShadowMapPendingTexture& PendingTexture = PendingTextures[TextureIndex]; PendingTexture.StartEncoding(InWorld); } PendingTextures.Empty(); GWarn->EndSlowTask(); } else { PendingShadowMaps.Empty(); } }
virtual bool CompressImage( const FImage& InImage, const struct FTextureBuildSettings& BuildSettings, bool bImageHasAlphaChannel, FCompressedImage2D& OutCompressedImage ) const override { bool bCompressionSucceeded = false; const int iWidthInBlocks = ((InImage.SizeX + 3) & ~ 3) / 4; const int iHeightInBlocks = ((InImage.SizeY + 3) & ~ 3) / 4; const int iOutputBytes = iWidthInBlocks * iHeightInBlocks * 16; OutCompressedImage.RawData.AddUninitialized(iOutputBytes); // When we allow async tasks to execute we do so with 4 lines of the image per task // This isn't optimal for long thin textures, but works well with how ISPC works const int iScansPerTask = 4; const int iNumTasks = FMath::Max((InImage.SizeY / iScansPerTask) - 1, 0); const bool bUseTasks = true; EPixelFormat CompressedPixelFormat = PF_Unknown; if ( BuildSettings.TextureFormatName == GTextureFormatNameBC6H ) { FImage Image; InImage.CopyTo(Image, ERawImageFormat::RGBA16F, false); bc6h_enc_settings settings; GetProfile_bc6h_basic(&settings); if ( bUseTasks ) { class FIntelCompressWorker : public FNonAbandonableTask { public: FIntelCompressWorker(bc6h_enc_settings* pEncSettings, FImage* pInImage, FCompressedImage2D* pOutImage, int yStart, int yEnd) : mpEncSettings(pEncSettings) , mpInImage(pInImage) , mpOutImage(pOutImage) , mYStart(yStart) , mYEnd(yEnd) { } void DoWork() { IntelBC6HCompressScans(mpEncSettings, mpInImage, mpOutImage, mYStart, mYEnd); } FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FIntelCompressWorker, STATGROUP_ThreadPoolAsyncTasks); } bc6h_enc_settings* mpEncSettings; FImage* mpInImage; FCompressedImage2D* mpOutImage; int mYStart; int mYEnd; }; typedef FAsyncTask<FIntelCompressWorker> FIntelCompressTask; // One less task because we'll do the final + non multiple of 4 inside this task TIndirectArray<FIntelCompressTask> CompressionTasks; CompressionTasks.Reserve(iNumTasks); for ( int iTask=0; iTask < iNumTasks; ++iTask ) { auto* AsyncTask = new(CompressionTasks) FIntelCompressTask(&settings, &Image, &OutCompressedImage, iTask * iScansPerTask, (iTask + 1) * iScansPerTask); AsyncTask->StartBackgroundTask(); } IntelBC6HCompressScans(&settings, &Image, &OutCompressedImage, iScansPerTask * iNumTasks, InImage.SizeY); // Wait completion for (int32 TaskIndex = 0; TaskIndex < CompressionTasks.Num(); ++TaskIndex) { CompressionTasks[TaskIndex].EnsureCompletion(); } } else { IntelBC6HCompressScans(&settings, &Image, &OutCompressedImage, 0, InImage.SizeY); } CompressedPixelFormat = PF_BC6H; bCompressionSucceeded = true; } else if ( BuildSettings.TextureFormatName == GTextureFormatNameBC7 ) { FImage Image; InImage.CopyTo(Image, ERawImageFormat::BGRA8, BuildSettings.bSRGB); bc7_enc_settings settings; if ( bImageHasAlphaChannel ) { GetProfile_alpha_basic(&settings); } else { GetProfile_basic(&settings); } if ( bUseTasks ) { class FIntelCompressWorker : public FNonAbandonableTask { public: FIntelCompressWorker(bc7_enc_settings* pEncSettings, FImage* pInImage, FCompressedImage2D* pOutImage, int yStart, int yEnd) : mpEncSettings(pEncSettings) , mpInImage(pInImage) , mpOutImage(pOutImage) , mYStart(yStart) , mYEnd(yEnd) { } void DoWork() { IntelBC7CompressScans(mpEncSettings, mpInImage, mpOutImage, mYStart, mYEnd); } FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FIntelCompressWorker, STATGROUP_ThreadPoolAsyncTasks); } bc7_enc_settings* mpEncSettings; FImage* mpInImage; FCompressedImage2D* mpOutImage; int mYStart; int mYEnd; }; typedef FAsyncTask<FIntelCompressWorker> FIntelCompressTask; // One less task because we'll do the final + non multiple of 4 inside this task TIndirectArray<FIntelCompressTask> CompressionTasks; CompressionTasks.Reserve(iNumTasks); for ( int iTask=0; iTask < iNumTasks; ++iTask ) { auto* AsyncTask = new(CompressionTasks) FIntelCompressTask(&settings, &Image, &OutCompressedImage, iTask * iScansPerTask, (iTask + 1) * iScansPerTask); AsyncTask->StartBackgroundTask(); } IntelBC7CompressScans(&settings, &Image, &OutCompressedImage, iScansPerTask * iNumTasks, InImage.SizeY); // Wait completion for (int32 TaskIndex = 0; TaskIndex < CompressionTasks.Num(); ++TaskIndex) { CompressionTasks[TaskIndex].EnsureCompletion(); } } else { IntelBC7CompressScans(&settings, &Image, &OutCompressedImage, 0, InImage.SizeY); } CompressedPixelFormat = PF_BC7; bCompressionSucceeded = true; } if ( bCompressionSucceeded ) { OutCompressedImage.SizeX = FMath::Max(InImage.SizeX, 4); OutCompressedImage.SizeY = FMath::Max(InImage.SizeY, 4); OutCompressedImage.PixelFormat = CompressedPixelFormat; } return bCompressionSucceeded; }
FLODMask ComputeLODForMeshes( const TIndirectArray<class FStaticMesh>& StaticMeshes, const FSceneView& View, const FVector4& Origin, float SphereRadius, int32 ForcedLODLevel, float ScreenSizeScale ) { FLODMask LODToRender; // Handle forced LOD level first if(ForcedLODLevel >= 0) { // Note: starting at -1 which is the default LODIndex, for cases where LODIndex didn't get set int8 MaxLOD = -1; for(int32 MeshIndex = 0 ; MeshIndex < StaticMeshes.Num() ; ++MeshIndex) { const FStaticMesh& Mesh = StaticMeshes[MeshIndex]; MaxLOD = FMath::Max(MaxLOD, Mesh.LODIndex); } LODToRender.SetLOD(FMath::Clamp<int8>(ForcedLODLevel, 0, MaxLOD)); } else if (View.Family->EngineShowFlags.LOD) { int32 NumMeshes = StaticMeshes.Num(); if (NumMeshes && StaticMeshes[0].bDitheredLODTransition) { for (int32 SampleIndex = 0; SampleIndex < 2; SampleIndex++) { int32 MinLODFound = INT_MAX; bool bFoundLOD = false; const float ScreenSize = ComputeTemporalLODBoundsScreenSize(Origin, SphereRadius, View, SampleIndex); for(int32 MeshIndex = NumMeshes-1 ; MeshIndex >= 0 ; --MeshIndex) { const FStaticMesh& Mesh = StaticMeshes[MeshIndex]; float MeshScreenSize = Mesh.ScreenSize * ScreenSizeScale; if(MeshScreenSize >= ScreenSize) { LODToRender.SetLODSample(Mesh.LODIndex, SampleIndex); bFoundLOD = true; break; } MinLODFound = FMath::Min<int32>(MinLODFound, Mesh.LODIndex); } // If no LOD was found matching the screen size, use the lowest in the array instead of LOD 0, to handle non-zero MinLOD if (!bFoundLOD) { LODToRender.SetLODSample(MinLODFound, SampleIndex); } } } else { int32 MinLODFound = INT_MAX; bool bFoundLOD = false; const float ScreenSize = ComputeBoundsScreenSize(Origin, SphereRadius, View); for(int32 MeshIndex = NumMeshes-1 ; MeshIndex >= 0 ; --MeshIndex) { const FStaticMesh& Mesh = StaticMeshes[MeshIndex]; float MeshScreenSize = Mesh.ScreenSize * ScreenSizeScale; if(MeshScreenSize >= ScreenSize) { LODToRender.SetLOD(Mesh.LODIndex); bFoundLOD = true; break; } MinLODFound = FMath::Min<int32>(MinLODFound, Mesh.LODIndex); } // If no LOD was found matching the screen size, use the lowest in the array instead of LOD 0, to handle non-zero MinLOD if (!bFoundLOD) { LODToRender.SetLOD(MinLODFound); } } } return LODToRender; }