// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= ShaderResource.cpp: ShaderResource implementation. =============================================================================*/ #include "Shader.h" #include "Compression/OodleDataCompression.h" #include "Containers/ArrayView.h" #include "DataDrivenShaderPlatformInfo.h" #include "Misc/Compression.h" #include "Misc/CoreMisc.h" #include "Misc/StringBuilder.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "Interfaces/IShaderFormat.h" #include "IO/IoHash.h" #include "Misc/MemStack.h" #include "Misc/ScopeLock.h" #include "RenderingThread.h" #include "RHI.h" #include "RHIResources.h" // Access to FRHIRayTracingShader::RayTracingPayloadType requires this #include "ShaderCompilerCore.h" #include "ShaderCompilerJobTypes.h" #include "ShaderCore.h" #include "ShaderSerialization.h" #include "UObject/RenderingObjectVersion.h" #if WITH_EDITORONLY_DATA #include "Interfaces/IShaderFormat.h" #endif DECLARE_LOG_CATEGORY_CLASS(LogShaderWarnings, Log, Log); #if (CSV_PROFILER_STATS && !UE_BUILD_SHIPPING) TCsvPersistentCustomStat* CsvStatNumShaderMapsUsedForRendering = nullptr; #endif static int32 GShaderCompilerEmitWarningsOnLoad = 0; static FAutoConsoleVariableRef CVarShaderCompilerEmitWarningsOnLoad( TEXT("r.ShaderCompiler.EmitWarningsOnLoad"), GShaderCompilerEmitWarningsOnLoad, TEXT("When 1, shader compiler warnings are emitted to the log for all shaders as they are loaded."), ECVF_Default ); FName GetShaderCompressionFormat() { // We always use oodle now. This was instituted because UnrealPak recompresses the shaders and doens't have // access to the INIs that drive the CVars and would always use default, resulting in mismatches for non // default encoder selection. return NAME_Oodle; } void GetShaderCompressionOodleSettings(FOodleDataCompression::ECompressor& OutCompressor, FOodleDataCompression::ECompressionLevel& OutLevel, const FName& ShaderFormat) { // support an older developer-only CVar for compatibility and make it preempt #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) // Since we always use Oodle, we make SkipCompression tell Oodle to not compress. static const IConsoleVariable* CVarSkipCompression = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.SkipCompression")); static bool bSkipCompression = (CVarSkipCompression && CVarSkipCompression->GetInt() != 0); if (UNLIKELY(bSkipCompression)) { OutCompressor = FOodleDataCompression::ECompressor::Selkie; OutLevel = FOodleDataCompression::ECompressionLevel::None; return; } #endif // We just use mermaid/normal here since these settings get overwritten in unrealpak, so this is just for non pak'd builds. OutCompressor = FOodleDataCompression::ECompressor::Mermaid; OutLevel = FOodleDataCompression::ECompressionLevel::Normal; } bool FShaderMapResource::ArePlatformsCompatible(EShaderPlatform CurrentPlatform, EShaderPlatform TargetPlatform) { bool bFeatureLevelCompatible = CurrentPlatform == TargetPlatform; if (!bFeatureLevelCompatible && IsPCPlatform(CurrentPlatform) && IsPCPlatform(TargetPlatform)) { bFeatureLevelCompatible = GetMaxSupportedFeatureLevel(CurrentPlatform) >= GetMaxSupportedFeatureLevel(TargetPlatform); bool const bIsTargetD3D = IsD3DPlatform(TargetPlatform); bool const bIsCurrentPlatformD3D = IsD3DPlatform(CurrentPlatform); // For Metal in Editor we can switch feature-levels, but not in cooked projects when using Metal shader librariss. bool const bIsCurrentMetal = IsMetalPlatform(CurrentPlatform); bool const bIsTargetMetal = IsMetalPlatform(TargetPlatform); bool const bIsMetalCompatible = (bIsCurrentMetal == bIsTargetMetal) #if !WITH_EDITOR // Static analysis doesn't like (|| WITH_EDITOR) && (!IsMetalPlatform(CurrentPlatform) || (CurrentPlatform == TargetPlatform)) #endif ; bool const bIsCurrentOpenGL = IsOpenGLPlatform(CurrentPlatform); bool const bIsTargetOpenGL = IsOpenGLPlatform(TargetPlatform); bFeatureLevelCompatible = bFeatureLevelCompatible && (bIsCurrentPlatformD3D == bIsTargetD3D && bIsMetalCompatible && bIsCurrentOpenGL == bIsTargetOpenGL); } return bFeatureLevelCompatible; } #if RHI_RAYTRACING class FRayTracingShaderLibrary { public: uint32 AddShader(EShaderPlatform ShaderPlatform, FRHIRayTracingShader* Shader) { const int32 PayloadIndex = FMath::CountTrailingZeros(Shader->RayTracingPayloadType); FScopeLock Lock(&CS); FShaderLibrary& ShaderLibrary = ShadersPerPlatform.FindOrAdd(ShaderPlatform); if (ShaderLibrary.UnusedIndicies[PayloadIndex].Num() != 0) { uint32 Index = ShaderLibrary.UnusedIndicies[PayloadIndex].Pop(EAllowShrinking::No); checkSlow(ShaderLibrary.Shaders[PayloadIndex][Index] == nullptr); ShaderLibrary.Shaders[PayloadIndex][Index] = Shader; return Index; } else { return ShaderLibrary.Shaders[PayloadIndex].Add(Shader); } } void RemoveShader(EShaderPlatform ShaderPlatform, uint32 Index, FRHIRayTracingShader* Shader) { if (Index != ~0u) { const int32 PayloadIndex = FMath::CountTrailingZeros(Shader->RayTracingPayloadType); FScopeLock Lock(&CS); FShaderLibrary* ShaderLibrary = ShadersPerPlatform.Find(ShaderPlatform); check(ShaderLibrary); checkSlow(ShaderLibrary->Shaders[PayloadIndex][Index] == Shader); ShaderLibrary->UnusedIndicies[PayloadIndex].Push(Index); ShaderLibrary->Shaders[PayloadIndex][Index] = nullptr; } } void GetShaders(EShaderPlatform ShaderPlatform, TArray& OutShaders, FRHIRayTracingShader* DefaultShader) { const int32 PayloadIndex = FMath::CountTrailingZeros(DefaultShader->RayTracingPayloadType); const int32 BaseOutIndex = OutShaders.Num(); FScopeLock Lock(&CS); FShaderLibrary* ShaderLibrary = ShadersPerPlatform.Find(ShaderPlatform); if (ShaderLibrary) { OutShaders.Append(ShaderLibrary->Shaders[PayloadIndex]); for (uint32 Index : ShaderLibrary->UnusedIndicies[PayloadIndex]) { OutShaders[BaseOutIndex + Index] = DefaultShader; } } } private: struct FShaderLibrary { TArray UnusedIndicies[32]; TArray Shaders[32]; }; TMap ShadersPerPlatform; FCriticalSection CS; }; static FRayTracingShaderLibrary GlobalRayTracingHitGroupLibrary; static FRayTracingShaderLibrary GlobalRayTracingCallableShaderLibrary; static FRayTracingShaderLibrary GlobalRayTracingMissShaderLibrary; void FShaderMapResource::GetRayTracingHitGroupLibrary(EShaderPlatform ShaderPlatform, TArray& RayTracingShaders, FRHIRayTracingShader* DefaultShader) { GlobalRayTracingHitGroupLibrary.GetShaders(ShaderPlatform, RayTracingShaders, DefaultShader); } void FShaderMapResource::GetRayTracingCallableShaderLibrary(EShaderPlatform ShaderPlatform, TArray& RayTracingCallableShaders, FRHIRayTracingShader* DefaultShader) { GlobalRayTracingCallableShaderLibrary.GetShaders(ShaderPlatform, RayTracingCallableShaders, DefaultShader); } void FShaderMapResource::GetRayTracingMissShaderLibrary(EShaderPlatform ShaderPlatform, TArray& RayTracingMissShaders, FRHIRayTracingShader* DefaultShader) { GlobalRayTracingMissShaderLibrary.GetShaders(ShaderPlatform, RayTracingMissShaders, DefaultShader); } #endif // RHI_RAYTRACING static void ApplyResourceStats(FShaderMapResourceCode& Resource) { #if STATS INC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, Resource.GetSizeBytes()); for (const FShaderCodeResource& Shader : Resource.ShaderCodeResources) { INC_DWORD_STAT_BY_FName(GetMemoryStatType(Shader.GetFrequency()).GetName(), Shader.GetCodeBuffer().GetSize()); } #endif // STATS } static void RemoveResourceStats(FShaderMapResourceCode& Resource) { #if STATS DEC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, Resource.GetSizeBytes()); for (const FShaderCodeResource& Shader : Resource.ShaderCodeResources) { DEC_DWORD_STAT_BY_FName(GetMemoryStatType(Shader.GetFrequency()).GetName(), Shader.GetCodeBuffer().GetSize()); } #endif // STATS } FShaderMapResourceCode::FShaderMapResourceCode(const FShaderMapResourceCode& Other) { ResourceHash = Other.ResourceHash; ShaderHashes = Other.ShaderHashes; ShaderCodeResources = Other.ShaderCodeResources; #if WITH_EDITORONLY_DATA ShaderEditorOnlyDataEntries = Other.ShaderEditorOnlyDataEntries; #endif // WITH_EDITORONLY_DATA } PRAGMA_DISABLE_DEPRECATION_WARNINGS FShaderMapResourceCode::~FShaderMapResourceCode() { RemoveResourceStats(*this); } PRAGMA_ENABLE_DEPRECATION_WARNINGS void FShaderMapResourceCode::Finalize() { FSHA1 Hasher; Hasher.Update((uint8*)ShaderHashes.GetData(), ShaderHashes.Num() * sizeof(FSHAHash)); Hasher.Final(); Hasher.GetHash(ResourceHash.Hash); ApplyResourceStats(*this); #if WITH_EDITORONLY_DATA LogShaderCompilerWarnings(); #endif } uint32 FShaderMapResourceCode::GetSizeBytes() const { uint64 Size = sizeof(*this) + ShaderHashes.GetAllocatedSize() + ShaderCodeResources.GetAllocatedSize(); for (const FShaderCodeResource& Entry : ShaderCodeResources) { Size += Entry.GetCacheBuffer().GetSize(); } check(Size <= TNumericLimits::Max()); return static_cast(Size); } int32 FShaderMapResourceCode::FindShaderIndex(const FSHAHash& InHash) const { return Algo::BinarySearch(ShaderHashes, InHash); } #if WITH_EDITORONLY_DATA void FShaderMapResourceCode::FShaderEditorOnlyDataEntry::ConditionalSetSymbolBuffer(FCompressedBuffer InSymbols) { // Intentional truncation of hash; 20 bytes is overkill for this purpose so we just take the first 8 const uint64 InSymbolHash = reinterpret_cast(InSymbols.GetRawHash().GetBytes())[0]; if (SymbolBuffer.IsNull() || InSymbolHash < SymbolHash) { SymbolBuffer = InSymbols; SymbolHash = InSymbolHash; } } #endif void FShaderMapResourceCode::AddShaderCompilerOutput(const FShaderCompilerOutput& Output, const FShaderCompileJobKey& Key, FString DebugInfo) { TRACE_CPUPROFILER_EVENT_SCOPE(FShaderMapResourceCode::AddShaderCode); const FSHAHash& InHash = Output.OutputHash; FString DebugName = Key.ToString(); uint64 ShaderTypeHash = Key.ShaderType->GetHashedName().GetHash(); const int32 Index = Algo::LowerBound(ShaderHashes, InHash); const FShaderCodeResource& CodeResource = Output.GetFinalizedCodeResource(); #if WITH_EDITORONLY_DATA TArray ShaderStatsSorted(Output.ShaderStatistics); ShaderStatsSorted.StableSort(); #endif if (Index >= ShaderHashes.Num() || ShaderHashes[Index] != InHash) { ShaderHashes.Insert(InHash, Index); #if WITH_EDITORONLY_DATA // Output.Errors contains warnings in the case any exist (no errors since if there were the job would have failed) AddEditorOnlyData(Index, DebugName, ShaderTypeHash, CodeResource.GetSymbolsBuffer(), Output.Errors, MoveTemp(ShaderStatsSorted), DebugInfo); #endif ShaderCodeResources.Insert(CodeResource, Index); } #if WITH_EDITORONLY_DATA else { // Output.Errors contains warnings in the case any exist (no errors since if there were the job would have failed) // We append the warnings and deduplicate other data like DebugInfo for any additional jobs which resulted in the // same bytecode for the sake of determinism in the results saved to DDC. UpdateEditorOnlyData(Index, DebugName, ShaderTypeHash, CodeResource.GetSymbolsBuffer(), Output.Errors, DebugInfo); ValidateShaderStatisticsEditorOnlyData(Index, ShaderStatsSorted); } #endif } #if WITH_EDITORONLY_DATA void FShaderMapResourceCode::AddEditorOnlyData(int32 Index, const FString& DebugName, uint64 ShaderTypeHash, FCompressedBuffer InSymbols, TConstArrayView InCompilerWarnings, TArray&& ShaderStatistics, const FString& DebugInfo) { FShaderEditorOnlyDataEntry& Entry = ShaderEditorOnlyDataEntries.InsertDefaulted_GetRef(Index); // This should be a newly created shader entry. check(Entry.ShaderStatistics.Num() == 0); Entry.ShaderStatistics = MoveTemp(ShaderStatistics); UpdateEditorOnlyData(Index, DebugName, ShaderTypeHash, InSymbols, InCompilerWarnings, DebugInfo); } void FShaderMapResourceCode::UpdateEditorOnlyData(int32 Index, const FString& DebugName, uint64 ShaderTypeHash, FCompressedBuffer InSymbols, TConstArrayView InCompilerWarnings, const FString& DebugInfo) { FShaderEditorOnlyDataEntry& Entry = ShaderEditorOnlyDataEntries[Index]; // Keep a single DebugInfo as it doesn't matter which one we use, but make sure it is the same one for determinism if (!DebugInfo.IsEmpty() && (Entry.DebugInfo.IsEmpty() || (DebugInfo < Entry.DebugInfo))) { Entry.DebugInfo = DebugInfo; } Entry.ConditionalSetSymbolBuffer(InSymbols); for (const FShaderCompilerError& Warning : InCompilerWarnings) { FString ModifiedWarning = !DebugName.IsEmpty() ? FString::Printf(TEXT("%s [%s]"), *Warning.GetErrorString(), *DebugName) : Warning.GetErrorString(); // Maintain sorted order in Entry.CompilerWarnings & deduplicate const int32 WarningIndex = Algo::LowerBound(Entry.CompilerWarnings, ModifiedWarning); if (WarningIndex >= Entry.CompilerWarnings.Num() || Entry.CompilerWarnings[WarningIndex] != ModifiedWarning) { Entry.CompilerWarnings.Insert(ModifiedWarning, WarningIndex); } } const int32 HashIndex = Algo::LowerBound(Entry.ShaderTypeHashes, ShaderTypeHash); if (HashIndex >= Entry.ShaderTypeHashes.Num() || Entry.ShaderTypeHashes[HashIndex] != ShaderTypeHash) { Entry.ShaderTypeHashes.Insert(ShaderTypeHash, HashIndex); } } void FShaderMapResourceCode::ValidateShaderStatisticsEditorOnlyData(int32 Index, const TArray& ShaderStatistics) { check(ShaderEditorOnlyDataEntries.IsValidIndex(Index)); FShaderEditorOnlyDataEntry& Entry = ShaderEditorOnlyDataEntries[Index]; auto PrintMismatchWarning = [](FShaderEditorOnlyDataEntry& Entry, const TArray& ShaderStatistics) { TStringBuilder<512> WarningMessage; WarningMessage << TEXT("Non-determinism detected in shader statistics; existing entry contains:\n"); WarningMessage.Join(Entry.ShaderStatistics, TEXT(", ")); WarningMessage << TEXT("\nNew entry contains:\n"); WarningMessage.Join(ShaderStatistics, TEXT(", ")); WarningMessage << TEXT("\nShader statistics are assumed to be a property of the bytecode and so must match when bytecode matches."); UE_LOG(LogShaders, Warning, TEXT("%s"), WarningMessage.ToString()); }; if (Entry.ShaderStatistics.Num() != ShaderStatistics.Num()) { PrintMismatchWarning(Entry, ShaderStatistics); return; } for (int i = 0; i < ShaderStatistics.Num(); ++i) { const FGenericShaderStat& StatA = Entry.ShaderStatistics[i]; const FGenericShaderStat& StatB = ShaderStatistics[i]; if (!(StatA == StatB)) { PrintMismatchWarning(Entry, ShaderStatistics); return; } } } void FShaderMapResourceCode::LogShaderCompilerWarnings() { if (ShaderEditorOnlyDataEntries.Num() > 0 && GShaderCompilerEmitWarningsOnLoad != 0) { // Emit all the compiler warnings seen whilst serializing/loading this shader to the log. // Since successfully compiled shaders are stored in the DDC, we'll get the compiler warnings // even if we didn't compile the shader this run. for (const FShaderEditorOnlyDataEntry& Entry : ShaderEditorOnlyDataEntries) { for (const FString& CompilerWarning : Entry.CompilerWarnings) { UE_LOG(LogShaderWarnings, Warning, TEXT("%s"), *CompilerWarning); } } } } #endif // WITH_EDITORONLY_DATA void FShaderMapResourceCode::ToString(FStringBuilderBase& OutString) const { OutString.Appendf(TEXT("Shaders: Num=%d\n"), ShaderHashes.Num()); for (int32 i = 0; i < ShaderHashes.Num(); ++i) { const FShaderCodeResource& Res = ShaderCodeResources[i]; OutString.Appendf(TEXT(" [%d]: { Hash: %s, Freq: %s, Size: %llu, UncompressedSize: %d }\n"), i, *ShaderHashes[i].ToString(), GetShaderFrequencyString(Res.GetFrequency()), Res.GetCodeBuffer().GetSize(), Res.GetUncompressedSize()); } } void FShaderMapResourceCode::Serialize(FShaderSerializeContext& Ctx) { FArchive& Ar = Ctx.GetMainArchive(); Ar << ResourceHash; Ar << ShaderHashes; if (!Ctx.EnableCustomCodeSerialize()) { Ar << ShaderCodeResources; } else { if (Ar.IsLoading()) { ShaderCodeResources.SetNum(ShaderHashes.Num()); } Ctx.ReserveCode(ShaderCodeResources.Num()); for (int32 CodeIndex = 0; CodeIndex < ShaderCodeResources.Num(); ++CodeIndex) { Ctx.SerializeCode(ShaderCodeResources[CodeIndex], CodeIndex); } } check(ShaderCodeResources.Num() == ShaderHashes.Num()); #if WITH_EDITORONLY_DATA const bool bSerializeEditorOnlyData = !Ctx.bLoadingCooked && (!Ar.IsCooking() || Ar.CookingTarget()->HasEditorOnlyData()); if (bSerializeEditorOnlyData) { Ar << ShaderEditorOnlyDataEntries; } if (Ar.IsLoading()) { // only need to set SymbolBuffer when loading, saving is handled automatically by the serialization of ShaderCodeResources // (this just sets the FSharedBuffer reference in a second place to avoid needing to fix up a bunch of code which retrieves // symbols from the editoronly data) for (int32 CodeIndex = 0; CodeIndex < ShaderCodeResources.Num(); ++CodeIndex) { ShaderEditorOnlyDataEntries[CodeIndex].ConditionalSetSymbolBuffer(ShaderCodeResources[CodeIndex].GetSymbolsBuffer()); } } #endif // WITH_EDITORONLY_DATA ApplyResourceStats(*this); #if WITH_EDITORONLY_DATA if (Ar.IsLoading()) { LogShaderCompilerWarnings(); } #endif } #if WITH_EDITORONLY_DATA void FShaderMapResourceCode::NotifyShadersCompiled(FName FormatName) { #if WITH_ENGINE // Notify the platform shader format that this particular shader is being used in the cook. // We discard this data in cooked builds unless Ar.CookingTarget()->HasEditorOnlyData() is true. if (ShaderEditorOnlyDataEntries.Num()) { if (const IShaderFormat* ShaderFormat = GetTargetPlatformManagerRef().FindShaderFormat(FormatName)) { for (const FShaderEditorOnlyDataEntry& Entry : ShaderEditorOnlyDataEntries) { ShaderFormat->NotifyShaderCompiled(Entry.SymbolBuffer, FormatName, Entry.DebugInfo); } } } #endif // WITH_ENGINE } #endif // WITH_EDITORONLY_DATA FShaderMapResource::FShaderMapResource(EShaderPlatform InPlatform, int32 NumShaders) : NumRHIShaders(static_cast(NumShaders)) , bAtLeastOneRHIShaderCreated(0) , Platform(InPlatform) , NumRefs(0) { RHIShaders = MakeUnique[]>(NumRHIShaders); // this MakeUnique() zero-initializes the array #if RHI_RAYTRACING if (GRHISupportsRayTracing && GRHISupportsRayTracingShaders) { RayTracingLibraryIndices.AddUninitialized(NumShaders); FMemory::Memset(RayTracingLibraryIndices.GetData(), 0xff, NumShaders * RayTracingLibraryIndices.GetTypeSize()); } #endif // RHI_RAYTRACING } FShaderMapResource::~FShaderMapResource() { ReleaseShaders(); check(NumRefs.load(std::memory_order_relaxed) == 0); } void FShaderMapResource::AddRef() { NumRefs.fetch_add(1, std::memory_order_relaxed); } uint32 FShaderMapResource::GetShaderSizeBytes(int32 ShaderIndex) const { // default FShaderMapResource does not contain inline shader bytecode return 0; } void FShaderMapResource::Release() { check(NumRefs.load(std::memory_order_relaxed) > 0); if (NumRefs.fetch_sub(1, std::memory_order_release) - 1 == 0 && TryRelease()) { //check https://www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html for explanation std::atomic_thread_fence(std::memory_order_acquire); // Send a release message to the rendering thread when the shader loses its last reference. BeginReleaseResource(this); BeginCleanup(this); DEC_DWORD_STAT_BY(STAT_Shaders_ShaderResourceMemory, GetSizeBytes()); } } void FShaderMapResource::ReleaseShaders() { if (RHIShaders) { FScopeLock ScopeLock(&RHIShadersCreationGuard); int NumReleaseShaders = 0; for (uint32 Idx = 0; Idx < NumRHIShaders; ++Idx) { if (FRHIShader* Shader = RHIShaders[Idx].load(std::memory_order_acquire)) { Shader->Release(); NumReleaseShaders++; DEC_DWORD_STAT(STAT_Shaders_NumShadersCreated); } } #if (CSV_PROFILER_STATS && !UE_BUILD_SHIPPING) TCsvPersistentCustomStat* CsvStatNumShadersCreated = FCsvProfiler::Get()->GetOrCreatePersistentCustomStatInt(TEXT("NumShadersCreated"), CSV_CATEGORY_INDEX(Shaders)); CsvStatNumShadersCreated->Sub(NumReleaseShaders); #endif RHIShaders = nullptr; NumRHIShaders = 0; if (bAtLeastOneRHIShaderCreated) { DEC_DWORD_STAT(STAT_Shaders_NumShaderMapsUsedForRendering); #if (CSV_PROFILER_STATS && !UE_BUILD_SHIPPING) if(CsvStatNumShaderMapsUsedForRendering == nullptr) { CsvStatNumShaderMapsUsedForRendering = FCsvProfiler::Get()->GetOrCreatePersistentCustomStatInt(TEXT("NumShaderMapsUsedForRendering"), CSV_CATEGORY_INDEX(Shaders)); } CsvStatNumShaderMapsUsedForRendering->Sub(1); #endif } bAtLeastOneRHIShaderCreated = false; } } void FShaderMapResource::ReleaseRHI() { #if RHI_RAYTRACING if (GRHISupportsRayTracing && GRHISupportsRayTracingShaders) { check(NumRHIShaders == static_cast(RayTracingLibraryIndices.Num())); for (uint32 Idx = 0; Idx < NumRHIShaders; ++Idx) { if (FRHIShader* Shader = RHIShaders[Idx].load(std::memory_order_acquire)) { int32 IndexInLibrary = RayTracingLibraryIndices[Idx]; switch (Shader->GetFrequency()) { case SF_RayHitGroup: GlobalRayTracingHitGroupLibrary.RemoveShader(Platform, IndexInLibrary, static_cast(Shader)); break; case SF_RayCallable: GlobalRayTracingCallableShaderLibrary.RemoveShader(Platform, IndexInLibrary, static_cast(Shader)); break; case SF_RayMiss: GlobalRayTracingMissShaderLibrary.RemoveShader(Platform, IndexInLibrary, static_cast(Shader)); break; default: break; } } } } RayTracingLibraryIndices.Empty(); #endif // RHI_RAYTRACING ReleaseShaders(); } void FShaderMapResource::BeginCreateAllShaders() { FShaderMapResource* Resource = this; ENQUEUE_RENDER_COMMAND(InitCommand)( [Resource](FRHICommandListImmediate& RHICmdList) { for (int32 ShaderIndex = 0; ShaderIndex < Resource->GetNumShaders(); ++ShaderIndex) { Resource->GetShader(ShaderIndex, true /*bRequired*/); } }); } FRHIShader* FShaderMapResource::CreateShaderOrCrash(int32 ShaderIndex, bool bRequired) { FRHIShader* Shader = nullptr; // create before taking the lock. This may cause multiple creations, but it's better // than a potential oversubscription deadlock, since CreateShader can spawn async tasks FRHIShader* CreatedShader = CreateRHIShaderOrCrash(ShaderIndex, bRequired); // guaranteed to return non-null if required is set if (CreatedShader == nullptr) { check(!bRequired); return nullptr; } { // Most shadermaps have <100 shaders, and less than a half of them can be created. // However, if this path is often contended, you can slice this lock (but remember to take care of STAT_Shaders_NumShaderMapsUsedForRendering!) FScopeLock ScopeLock(&RHIShadersCreationGuard); Shader = RHIShaders[ShaderIndex].load(std::memory_order_relaxed); if (UNLIKELY(Shader == nullptr)) { Shader = CreatedShader; CreatedShader = nullptr; if (!bAtLeastOneRHIShaderCreated) { INC_DWORD_STAT(STAT_Shaders_NumShaderMapsUsedForRendering); #if (CSV_PROFILER_STATS && !UE_BUILD_SHIPPING) if (CsvStatNumShaderMapsUsedForRendering == nullptr) { CsvStatNumShaderMapsUsedForRendering = FCsvProfiler::Get()->GetOrCreatePersistentCustomStatInt(TEXT("NumShaderMapsUsedForRendering"), CSV_CATEGORY_INDEX(Shaders)); } CsvStatNumShaderMapsUsedForRendering->Add(1); #endif bAtLeastOneRHIShaderCreated = 1; } #if RHI_RAYTRACING // Registers RT shaders in global "libraries" that track all shaders potentially usable in a scene for adding to RTPSO EShaderFrequency Frequency = Shader->GetFrequency(); if (LIKELY(GRHISupportsRayTracing && GRHISupportsRayTracingShaders)) { switch (Frequency) { case SF_RayHitGroup: RayTracingLibraryIndices[ShaderIndex] = GlobalRayTracingHitGroupLibrary.AddShader(Platform, static_cast(Shader)); break; case SF_RayCallable: RayTracingLibraryIndices[ShaderIndex] = GlobalRayTracingCallableShaderLibrary.AddShader(Platform, static_cast(Shader)); break; case SF_RayMiss: RayTracingLibraryIndices[ShaderIndex] = GlobalRayTracingMissShaderLibrary.AddShader(Platform, static_cast(Shader)); break; case SF_RayGen: // NOTE: we do not maintain a library for raygen shaders since the list of rayshaders we care about is usually small and consistent break; default: break; } } #endif // RHI_RAYTRACING RHIShaders[ShaderIndex].store(Shader, std::memory_order_release); // When using shader library, shader code is usually preloaded during the material load. Release it // since we won't need it anymore for this shader. ReleasePreloadedShaderCode(ShaderIndex); } } if (LIKELY(CreatedShader)) { // free redundantly created shader checkSlow(Shader != nullptr); CreatedShader->Release(); } return Shader; } FSHAHash FShaderMapResource_InlineCode::GetShaderHash(int32 ShaderIndex) { return Code->ShaderHashes[ShaderIndex]; } FRHIShader* FShaderMapResource_InlineCode::CreateRHIShaderOrCrash(int32 ShaderIndex, bool bRequired) { TRACE_CPUPROFILER_EVENT_SCOPE(FShaderMapResource_InlineCode::CreateRHIShaderOrCrash); // we can't have this called on the wrong platform's shaders if (!ArePlatformsCompatible(GMaxRHIShaderPlatform, GetPlatform())) { UE_LOG(LogShaders, Fatal, TEXT("FShaderMapResource_InlineCode::InitRHI got platform %s but it is not compatible with %s"), *LegacyShaderPlatformToShaderFormat(GetPlatform()).ToString(), *LegacyShaderPlatformToShaderFormat(GMaxRHIShaderPlatform).ToString()); // unreachable return nullptr; } FMemStackBase& MemStack = FMemStack::Get(); const FShaderCodeResource& ShaderCodeResource = Code->ShaderCodeResources[ShaderIndex]; FSharedBuffer ShaderCode = ShaderCodeResource.GetCodeBuffer(); TConstArrayView ShaderCodeView = ShaderCodeResource.GetCodeView(); FMemMark Mark(MemStack); int32 UncompressedSize = ShaderCodeResource.GetUncompressedSize(); if (ShaderCode.GetSize() != UncompressedSize) { void* UncompressedCode = MemStack.Alloc(UncompressedSize, 16); bool bSucceed = FCompression::UncompressMemory(GetShaderCompressionFormat(), UncompressedCode, UncompressedSize, ShaderCode.GetData(), ShaderCode.GetSize()); check(bSucceed); ShaderCodeView = MakeArrayView(reinterpret_cast(UncompressedCode), UncompressedSize); } const FSHAHash& ShaderHash = Code->ShaderHashes[ShaderIndex]; const EShaderFrequency Frequency = ShaderCodeResource.GetFrequency(); TRefCountPtr RHIShader; switch (Frequency) { case SF_Vertex: RHIShader = RHICreateVertexShader(ShaderCodeView, ShaderHash); break; case SF_Mesh: RHIShader = RHICreateMeshShader(ShaderCodeView, ShaderHash); break; case SF_Amplification: RHIShader = RHICreateAmplificationShader(ShaderCodeView, ShaderHash); break; case SF_Pixel: RHIShader = RHICreatePixelShader(ShaderCodeView, ShaderHash); break; case SF_Geometry: RHIShader = RHICreateGeometryShader(ShaderCodeView, ShaderHash); break; case SF_Compute: RHIShader = RHICreateComputeShader(ShaderCodeView, ShaderHash); break; case SF_WorkGraphRoot: RHIShader = RHICreateWorkGraphShader(ShaderCodeView, ShaderHash, SF_WorkGraphRoot); break; case SF_WorkGraphComputeNode: RHIShader = RHICreateWorkGraphShader(ShaderCodeView, ShaderHash, SF_WorkGraphComputeNode); break; case SF_RayGen: case SF_RayMiss: case SF_RayHitGroup: case SF_RayCallable: #if RHI_RAYTRACING if (GRHISupportsRayTracing && GRHISupportsRayTracingShaders) { RHIShader = RHICreateRayTracingShader(ShaderCodeView, ShaderHash, Frequency); } #endif // RHI_RAYTRACING break; default: checkNoEntry(); break; } if (UNLIKELY(RHIShader == nullptr)) { if (bRequired) { UE_LOG(LogShaders, Fatal, TEXT("FShaderMapResource_InlineCode::InitRHI is unable to create a shader: frequency=%d, hash=%s."), static_cast(Frequency), *ShaderHash.ToString()); } return nullptr; } INC_DWORD_STAT(STAT_Shaders_NumShadersCreated); #if (CSV_PROFILER_STATS && !UE_BUILD_SHIPPING) TCsvPersistentCustomStat* CsvStatNumShadersCreated = FCsvProfiler::Get()->GetOrCreatePersistentCustomStatInt(TEXT("NumShadersCreated"), CSV_CATEGORY_INDEX(Shaders)); CsvStatNumShadersCreated->Add(1); #endif RHIShader->SetHash(ShaderHash); // contract of this function is to return a shader with an already held reference RHIShader->AddRef(); return RHIShader; } uint32 FShaderMapResource_InlineCode::GetSizeBytes() const { uint32 TotalSize = 0; if (Code) { TotalSize += Code->GetSizeBytes(); } TotalSize += sizeof(FShaderMapResource_InlineCode); TotalSize += GetAllocatedSize(); return TotalSize; } uint32 FShaderMapResource_InlineCode::GetShaderSizeBytes(int32 ShaderIndex) const { if (Code) { return Code->ShaderCodeResources[ShaderIndex].GetUncompressedSize(); } return 0u; }