UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
MetalBaseShader.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3/*=============================================================================
4 MetalBaseShader.h: Metal RHI Base Shader Class Template.
5=============================================================================*/
6
7#pragma once
8
9#include "Misc/Paths.h"
10#include "HAL/FileManager.h"
12#include "MetalCommandQueue.h"
13#include "MetalDevice.h"
14#include "MetalProfiler.h"
16#include "Misc/FileHelper.h"
17#include "Misc/ScopeExit.h"
22#include "RHICoreShader.h"
23
24//------------------------------------------------------------------------------
25
26#pragma mark - Metal RHI Base Shader Class Support Routines
27
28
29extern MTL::LanguageVersion ValidateVersion(uint32 Version);
30
31
32//------------------------------------------------------------------------------
33
34#pragma mark - Metal RHI Base Shader Class Template Defines
35
36
38#define DEBUG_METAL_SHADERS (UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT)
39
40
41//------------------------------------------------------------------------------
42
43#pragma mark - Metal RHI Base Shader Class Template
44
46{
49
50 /* Argument encoders for shader IABs */
52
53 /* Tier1 Argument buffer bitmasks */
55
56 /* Uniform buffer static slots */
58
61
65
68
69 // this is the compiler shader
71 // This is the MTLLibrary for the shader so we can dynamically refine the MTLFunction
73
75 NS::String* GlslCodeNSString = nullptr;
76
79
82
83 // Function constant states
86
89};
90
91template<typename BaseResourceType, int32 ShaderType>
92class TMetalBaseShader : public BaseResourceType, public FMetalShaderData
93{
94public:
95 enum
96 {
97 StaticFrequency = ShaderType
98 };
99
100 TMetalBaseShader(FMetalDevice& MetalDevice) : Device(MetalDevice)
101 {
102 // void
103 }
104
106 {
107 this->Destroy();
108 }
109
111 void Destroy();
112
118 NS::String* GetSourceCode();
119
120protected:
122 MTLFunctionPtr GetCompiledFunction(bool const bAsync = false, const int32 FunctionIndex = -1);
123};
124
125
126//------------------------------------------------------------------------------
127
128#pragma mark - Metal RHI Base Shader Class Template Member Functions
129
130
131template<typename BaseResourceType, int32 ShaderType>
133{
135
137
138 Ar.SetLimitSize(ShaderCode.GetActualShaderCodeSize());
139
140 // was the shader already compiled offline?
144
145 // get the header
146 Header.Serialize(Ar, BaseResourceType::ShaderResourceTable);
147
148 ValidateVersion(Header.Version);
149
150 SourceLen = Header.SourceLen;
151 SourceCRC = Header.SourceCRC;
152
153 // If this triggers than a level above us has failed to provide valid shader data and the cook is probably bogus
154 UE_CLOG(Header.SourceLen == 0 || Header.SourceCRC == 0, LogMetal, Fatal, TEXT("Invalid Shader Bytecode provided."));
155
156 bDeviceFunctionConstants = (Header.bDeviceFunctionConstants == 0 ? false : true);
157
158 // remember where the header ended and code (precompiled or source) begins
159 int32 CodeOffset = Ar.Tell();
160 uint32 BufferSize = ShaderCode.GetActualShaderCodeSize() - CodeOffset;
161 const ANSICHAR* SourceCode = (ANSICHAR*)InShaderCode.GetData() + CodeOffset;
162
163 // Only archived shaders should be in here.
164 UE_CLOG(InLibrary && !(Header.CompileFlags & (1 << CFLAG_Archive)), LogMetal, Warning, TEXT("Shader being loaded wasn't marked for archiving but a MTLLibrary was provided - this is unsupported."));
165
167 {
168 UE_LOG(LogMetal, Display, TEXT("Loaded a text shader (will be slower to load)"));
169 }
170
172
174
176 bool bHasShaderSource = false;
177 static bool bForceTextShaders = false;
178
179 static const auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Shaders.Symbols"));
180
182 {
183 bForceTextShaders = CVar->GetInt() == 1 || FParse::Param(FCommandLine::Get(),TEXT("metalshaderdebug"));
185 }
186
187 if (!bHasShaderSource)
188 {
190 int32 SourceSize = 0;
193 if (LZMASource && LZMASourceSize > 0 && UnSourceLen && SourceSize == sizeof(uint32))
194 {
195 CompressedSource.Append(LZMASource, LZMASourceSize);
196 memcpy(&CodeSize, UnSourceLen, sizeof(uint32));
197 bHasShaderSource = false;
198 }
199#if !UE_BUILD_SHIPPING
200 else if(bForceTextShaders)
201 {
202 GlslCodeNSString = FMetalShaderDebugCache::Get().GetShaderCode(SourceLen, SourceCRC);
203 if(GlslCodeNSString)
204 {
205 GlslCodeNSString->retain();
206 }
207 }
208#endif
209 if (bForceTextShaders && CodeSize && CompressedSource.Num())
210 {
211 bHasShaderSource = (GetSourceCode() != nullptr);
212 }
213 }
215 {
216 GlslCodeNSString = NS::String::string(ShaderSource, NS::UTF8StringEncoding);
217 check(GlslCodeNSString);
218
219 GlslCodeNSString->retain();
220 }
221
222 bHasFunctionConstants = (Header.bDeviceFunctionConstants == 0 ? false : true);
223
224 ConstantValueHash = 0;
225
226 Library = InLibrary;
227
228 bool bNeedsCompiling = false;
229
230 // Find the existing compiled shader in the cache.
231 uint32 FunctionConstantHash = ConstantValueHash;
233
235 if (!Library && Function)
236 {
238 }
239 else
240 {
241 bNeedsCompiling = true;
242 }
243
244 Bindings = Header.Bindings;
245 if (bNeedsCompiling || !Library)
246 {
248 {
249 if (InLibrary)
250 {
251 Library = InLibrary;
252 }
253 else
254 {
255 METAL_GPUPROFILE(FScopedMetalCPUStats CPUStat(FString::Printf(TEXT("NewLibraryBinary: %d_%d"), SourceLen, SourceCRC)));
256
257 // Archived shaders should never get in here.
258 check(!(Header.CompileFlags & (1 << CFLAG_Archive)) || BufferSize > 0);
259
260 // allow GCD to copy the data into its own buffer
261 // dispatch_data_t GCDBuffer = dispatch_data_create(InShaderCode.GetTypedData() + CodeOffset, ShaderCode.GetActualShaderCodeSize() - CodeOffset, nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
262 NS::Error* AError;
263 void* Buffer = FMemory::Malloc( BufferSize );
264 FMemory::Memcpy( Buffer, InShaderCode.GetData() + CodeOffset, BufferSize );
266
267 // load up the already compiled shader
268 Library = NS::TransferPtr(Device.GetDevice()->newLibrary(GCDBuffer, &AError));
270
271 if (!Library)
272 {
273 UE_LOG(LogMetal, Display, TEXT("Failed to create library: %s"), *NSStringToFString(AError->description()));
274 }
275 }
276 }
277 else
278 {
280
281 METAL_GPUPROFILE(FScopedMetalCPUStats CPUStat(FString::Printf(TEXT("NewLibrarySource: %d_%d"), SourceLen, SourceCRC)));
282 NS::String* ShaderString = ((OfflineCompiledFlag == 0) ? NS::String::string(SourceCode, NS::UTF8StringEncoding) : GlslCodeNSString);
283
284 FString FinalShaderString(TEXT(""));
285 const FString ShaderName = ShaderCode.FindOptionalData(FShaderCodeName::Key);
286 if(ShaderName.Len())
287 {
288 FinalShaderString = FString::Printf(TEXT("// %s\n"), *ShaderName);
289 }
290
292 FinalShaderString.Replace(TEXT("#pragma once"), TEXT(""));
294
295 MTL::CompileOptions* CompileOptions = MTL::CompileOptions::alloc()->init();
297
298#if DEBUG_METAL_SHADERS
299 static bool bForceFastMath = FParse::Param(FCommandLine::Get(), TEXT("metalfastmath"));
300 static bool bForceNoFastMath = FParse::Param(FCommandLine::Get(), TEXT("metalnofastmath"));
302 {
303 CompileOptions->setFastMathEnabled(NO);
304 }
305 else if (bForceFastMath)
306 {
307 CompileOptions->setFastMathEnabled(YES);
308 }
309 else
310#endif
311 {
312 CompileOptions->setFastMathEnabled((BOOL)(!(Header.CompileFlags & (1 << CFLAG_NoFastMath))));
313 }
314
315#if !PLATFORM_MAC || DEBUG_METAL_SHADERS
316 NS::Dictionary* PreprocessorMacros = nullptr;;
317#if !PLATFORM_MAC // Pretty sure that as_type-casts work on macOS, but they don't for half2<->uint on older versions of the iOS runtime compiler.
318 PreprocessorMacros = NS::Dictionary::dictionary(NS::String::string("1", NS::UTF8StringEncoding),
319 NS::String::string("METAL_RUNTIME_COMPILER", NS::UTF8StringEncoding));
320#endif
321#if DEBUG_METAL_SHADERS
322 PreprocessorMacros = NS::Dictionary::dictionary(NS::String::string("1", NS::UTF8StringEncoding),
323 NS::String::string("MTLSL_ENABLE_DEBUG_INFO", NS::UTF8StringEncoding));
324#endif
326 {
327 CompileOptions->setPreprocessorMacros(PreprocessorMacros);
328 PreprocessorMacros->release();
329 }
330#endif
331
332 MTL::LanguageVersion MetalVersion;
333 switch (Header.Version)
334 {
335 case 8:
336 MetalVersion = MTL::LanguageVersion3_0;
337 break;
338 case 7:
339 MetalVersion = MTL::LanguageVersion2_4;
340 break;
341#if PLATFORM_MAC
342 case 6:
343 MetalVersion = MTL::LanguageVersion2_3;
344 break;
345 case 5:
346 // Fall through
347 case 0:
348 // Fall through
349 default:
350 MetalVersion = MTL::LanguageVersion2_2;
351 break;
352#else
353 case 0:
354 MetalVersion = MTL::LanguageVersion2_4;
355 break;
356 case 9:
357 MetalVersion = MTL::LanguageVersion3_1;
358 break;
359 default:
360 UE_LOG(LogRHI, Fatal, TEXT("Failed to create shader with unknown version %d: %s"), Header.Version, *NSStringToFString(NewShaderString));
361 MetalVersion = MTL::LanguageVersion2_4;
362 break;
363#endif
364 }
365 CompileOptions->setLanguageVersion(MetalVersion);
366
367 if(ShaderType == SF_Vertex && MetalVersion > MTL::LanguageVersion2_2)
368 {
369 CompileOptions->setPreserveInvariance(YES);
370 }
371
372 NS::Error* Error = nullptr;
373 Library = NS::TransferPtr(Device.GetDevice()->newLibrary(NewShaderString, CompileOptions, &Error));
374 if (Library.get() == nullptr)
375 {
376 UE_LOG(LogRHI, Error, TEXT("*********** Error\n%s"), *NSStringToFString(NewShaderString));
377 UE_LOG(LogRHI, Fatal, TEXT("Failed to create shader: %s"), *NSStringToFString(Error->description()));
378 }
379 else if (Error != nullptr)
380 {
381 // Warning...
382 UE_LOG(LogRHI, Warning, TEXT("*********** Warning\n%s"), *NSStringToFString(NewShaderString));
383 UE_LOG(LogRHI, Warning, TEXT("Created shader with warnings: %s"), *NSStringToFString(Error->description()));
384 }
385
386 GlslCodeNSString = NewShaderString;
387 GlslCodeNSString->retain();
388 }
389
390 GetCompiledFunction(true);
391 }
392 SideTableBinding = Header.SideTable;
393
395
396#if RHI_INCLUDE_SHADER_DEBUG_DATA
397 this->Debug.ShaderName = FString::Printf(TEXT("Main_%0.8x_%0.8x"), Header.SourceLen, Header.SourceCRC);
398#endif
399}
400
401template<typename BaseResourceType, int32 ShaderType>
403{
404 if(GlslCodeNSString)
405 {
406 GlslCodeNSString->release();
407 GlslCodeNSString = nullptr;
408 }
409}
410
411template<typename BaseResourceType, int32 ShaderType>
413{
415
416 if (!GlslCodeNSString && CodeSize && CompressedSource.Num())
417 {
418 GlslCodeNSString = DecodeMetalSourceCode(CodeSize, CompressedSource);
419 }
420 if (!GlslCodeNSString)
421 {
422 FString ShaderString = FString::Printf(TEXT("Hash: %s, Name: Main_%0.8x_%0.8x"), *BaseResourceType::GetHash().ToString(), SourceLen, SourceCRC);
423 GlslCodeNSString = FStringToNSString(ShaderString);
424 GlslCodeNSString->retain();
425 }
426 return GlslCodeNSString;
427}
428
429template<typename BaseResourceType, int32 ShaderType>
431{
433
435
436 bool bNeedToRecreateFunction = (LibraryFunctionIndex != FunctionIndex);
437 if (!Func || bNeedToRecreateFunction)
438 {
439 // Find the existing compiled shader in the cache.
440 uint32 FunctionConstantHash = ConstantValueHash;
441 FMetalCompiledShaderKey Key(SourceLen, SourceCRC, FunctionConstantHash);
443
445 {
447 Func = MTLFunctionPtr();
448 LibraryFunctionIndex = FunctionIndex;
449 }
450
451 if (!Func)
452 {
453 // Get the function from the library - the function name is "Main" followed by the CRC32 of the source MTLSL as 0-padded hex.
454 // This ensures that even if we move to a unified library that the function names will be unique - duplicates will only have one entry in the library.
455 NS::String* Name = (LibraryFunctionIndex != -1)
456 ? (NS::String*)Library->functionNames()->object(LibraryFunctionIndex) : FStringToNSString(FString::Printf(TEXT("Main_%0.8x_%0.8x"), SourceLen, SourceCRC));
457 MTL::FunctionConstantValues* ConstantValues = nullptr;
458 ON_SCOPE_EXIT { if (ConstantValues){ ConstantValues->release(); } };
459 if (bHasFunctionConstants)
460 {
461 ConstantValues = MTL::FunctionConstantValues::alloc()->init();
463
464 if (bDeviceFunctionConstants)
465 {
466 // Index 33 is the device vendor id constant
467 ConstantValues->setConstantValue((void*)&GRHIVendorId, MTL::DataTypeUInt, NS::String::string("GMetalDeviceManufacturer", NS::UTF8StringEncoding));
468 }
469 }
470
471#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS
472 // TODO: Make those constants optional?
473 static constexpr bool bEnableTessellation = false;
474
475 ConstantValues = MTL::FunctionConstantValues::alloc()->init();
476 bHasFunctionConstants = true;
477
478 ConstantValues->setConstantValue(&bEnableTessellation, MTL::DataTypeBool,
479 NS::String::string("tessellationEnabled", NS::UTF8StringEncoding));
480 ConstantValues->setConstantValue(&Bindings.OutputSizeVS, MTL::DataTypeInt,
481 NS::String::string("vertex_shader_output_size_fc", NS::UTF8StringEncoding));
482#endif
483
484 if (!bHasFunctionConstants || !bAsync)
485 {
486 METAL_GPUPROFILE(FScopedMetalCPUStats CPUStat(FString::Printf(TEXT("NewFunction: %s"), *NSStringToFString(Name))));
487 if (!bHasFunctionConstants)
488 {
489 Function = NS::TransferPtr(Library->newFunction(Name));
490 }
491 else
492 {
493 NS::Error* Error;
494 Function = NS::TransferPtr(Library->newFunction(Name, ConstantValues, &Error));
495 UE_CLOG(Function.get() == nullptr, LogMetal, Error, TEXT("Failed to create function: %s"), *NSStringToFString(Error->description()));
496 UE_CLOG(Function.get() == nullptr, LogMetal, Fatal, TEXT("*********** Error\n%s"), *NSStringToFString(GetSourceCode()));
497 }
498
501
502 Func = Function;
503 }
504 else
505 {
506 METAL_GPUPROFILE(FScopedMetalCPUStats CPUStat(FString::Printf(TEXT("NewFunctionAsync: %s"), *NSStringToFString(Name))));
507 METAL_GPUPROFILE(uint64 CPUStart = CPUStat.Stats ? CPUStat.Stats->CPUStartTime : 0);
508#if ENABLE_METAL_GPUPROFILE && RHI_NEW_GPU_PROFILER == 0
509 NS::String* nsName = Name;
510 const std::function<void(MTL::Function* pFunction, NS::Error* pError)> Handler = [Key, this, CPUStart, nsName](MTL::Function* NewFunction, NS::Error* Error){
511#else
512 const std::function<void(MTL::Function* pFunction, NS::Error* pError)> Handler = [Key, this](MTL::Function* NewFunction, NS::Error* Error){
513#endif
514 METAL_GPUPROFILE(FScopedMetalCPUStats CompletionStat(FString::Printf(TEXT("NewFunctionCompletion: %s"), *NSStringToFString(nsName))));
515 UE_CLOG(NewFunction == nullptr, LogMetal, Error, TEXT("Failed to create function: %s"), *NSStringToFString(Error->description()));
516 UE_CLOG(NewFunction == nullptr, LogMetal, Fatal, TEXT("*********** Error\n%s"), *NSStringToFString(GetSourceCode()));
517
518 GetMetalCompiledShaderCache().Add(Key, Library, NS::RetainPtr(NewFunction));
519#if ENABLE_METAL_GPUPROFILE && RHI_NEW_GPU_PROFILER == 0
520 if (CompletionStat.Stats)
521 {
522 CompletionStat.Stats->CPUStartTime = CPUStart;
523 }
524#endif
525 };
526
527 Library->newFunction(Name, ConstantValues, Handler);
528
529 return MTLFunctionPtr();
530 }
531 }
532 }
533
534 check(Func);
535 return Func;
536}
OODEFFUNC typedef void(OODLE_CALLBACK t_fp_OodleCore_Plugin_Free)(void *ptr)
#define check(expr)
Definition AssertionMacros.h:314
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::ANSICHAR ANSICHAR
An ANSI character. Normally a signed type.
Definition Platform.h:1131
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
DIRECTLINK_API Display
Definition DirectLinkLog.h:8
constexpr bool EnumHasAnyFlags(Enum Flags, Enum Contains)
Definition EnumClassFlags.h:35
#define UE_CLOG(Condition, CategoryName, Verbosity, Format,...)
Definition LogMacros.h:298
#define UE_LOG(CategoryName, Verbosity, Format,...)
Definition LogMacros.h:270
MTL::LanguageVersion ValidateVersion(uint32 Version)
Definition MetalShaders.cpp:49
FMetalCompiledShaderCache & GetMetalCompiledShaderCache()
Definition MetalCompiledShaderCache.cpp:11
#define METAL_DEBUG_OPTION(Code)
Definition MetalRHIPrivate.h:112
FORCEINLINE NS::String * FStringToNSString(const FString &InputString)
Definition MetalRHIPrivate.h:292
FORCEINLINE FString NSStringToFString(NS::String *InputString)
Definition MetalRHIPrivate.h:287
#define METAL_GPUPROFILE(Code)
Definition MetalRHIPrivate.h:129
NS::String * DecodeMetalSourceCode(uint32 CodeSize, TArray< uint8 > const &CompressedSource)
Definition MetalShaders.cpp:32
@ SF_Vertex
Definition RHIDefinitions.h:203
#define GRHIVendorId
Definition RHIGlobals.h:772
#define ON_SCOPE_EXIT
Definition ScopeExit.h:73
memcpy(InputBufferBase, BinkBlocksData, BinkBlocksSize)
uint8_t uint8
Definition binka_ue_file_header.h:8
uint32_t uint32
Definition binka_ue_file_header.h:6
int64 Tell() final
Definition MemoryArchive.h:29
Definition MemoryReader.h:75
void SetLimitSize(int64 NewLimitSize)
Definition MemoryReader.h:124
Definition MetalDevice.h:102
Definition ShaderCore.h:983
virtual int32 GetInt() const =0
Definition ArrayView.h:139
Definition Array.h:670
Definition UnrealString.h.inl:34
Definition MetalBaseShader.h:93
@ StaticFrequency
Definition MetalBaseShader.h:97
FMetalDevice & Device
Definition MetalBaseShader.h:121
void Destroy()
Definition MetalBaseShader.h:402
void Init(TArrayView< const uint8 > InCode, FMetalCodeHeader &Header, MTLLibraryPtr InLibrary)
Definition MetalBaseShader.h:132
TMetalBaseShader(FMetalDevice &MetalDevice)
Definition MetalBaseShader.h:100
virtual ~TMetalBaseShader()
Definition MetalBaseShader.h:105
NS::String * GetSourceCode()
Definition MetalBaseShader.h:412
MTLFunctionPtr GetCompiledFunction(bool const bAsync=false, const int32 FunctionIndex=-1)
Definition MetalBaseShader.h:430
void InitStaticUniformBufferSlots(FRHIShaderData *ShaderData)
Definition RHICoreShader.h:44
@ false
Definition radaudio_common.h:23
static CORE_API const TCHAR * Get()
Definition CommandLine.cpp:61
static FORCENOINLINE CORE_API void Free(void *Original)
Definition UnrealMemory.cpp:685
static UE_FORCEINLINE_HINT void * Memcpy(void *Dest, const void *Src, SIZE_T Count)
Definition UnrealMemory.h:160
Definition MetalShaderResources.h:217
MTLFunctionPtr FindRef(FMetalCompiledShaderKey const &Key)
Definition MetalCompiledShaderCache.h:26
void Add(FMetalCompiledShaderKey Key, MTLLibraryPtr Lib, MTLFunctionPtr Function)
Definition MetalCompiledShaderCache.h:40
MTLLibraryPtr FindLibrary(MTLFunctionPtr Function)
Definition MetalCompiledShaderCache.h:33
Definition MetalCompiledShaderKey.h:13
Definition MetalShaderResources.h:97
Definition MetalBaseShader.h:46
MTLLibraryPtr Library
Definition MetalBaseShader.h:72
uint32 SourceCRC
Definition MetalBaseShader.h:64
uint32 CodeSize
Definition MetalBaseShader.h:81
TMap< uint32, TBitArray<> > ArgumentBitmasks
Definition MetalBaseShader.h:54
TArray< uint8 > CompressedSource
Definition MetalBaseShader.h:78
TMap< uint32, MTL::ArgumentEncoder * > ArgumentEncoders
Definition MetalBaseShader.h:51
TArray< FUniformBufferStaticSlot > StaticSlots
Definition MetalBaseShader.h:57
uint32 ConstantValueHash
Definition MetalBaseShader.h:67
MTLFunctionPtr Function
Definition MetalBaseShader.h:70
uint32 LibraryFunctionIndex
Definition MetalBaseShader.h:88
bool bDeviceFunctionConstants
Definition MetalBaseShader.h:85
NS::String * GlslCodeNSString
Definition MetalBaseShader.h:75
bool bHasFunctionConstants
Definition MetalBaseShader.h:84
FMetalShaderBindings Bindings
Definition MetalBaseShader.h:48
uint32 SourceLen
Definition MetalBaseShader.h:63
int32 SideTableBinding
Definition MetalBaseShader.h:60
static FMetalShaderDebugCache & Get()
Definition MetalShaderDebugCache.h:17
NS::String * GetShaderCode(uint32 ShaderSrcLen, uint32 ShaderSrcCRC)
Definition MetalShaderDebugCache.cpp:31
static CORE_API bool Param(const TCHAR *Stream, const TCHAR *Param)
Definition Parse.cpp:325
Definition MetalProfiler.h:368
static const EShaderOptionalDataKey Key
Definition ShaderCore.h:857
virtual IConsoleVariable * FindConsoleVariable(const TCHAR *Name, bool bTrackFrequentCalls=true) const =0
static IConsoleManager & Get()
Definition IConsoleManager.h:1270
static int32 Strlen(const CharType *String)
Definition CString.h:1047