/// <summary> /// Create guest shader cache entries from the runtime contexts. /// </summary> /// <param name="memoryManager">The GPU memory manager in use</param> /// <param name="shaderContexts">The runtime contexts</param> /// <returns>Guest shader cahe entries from the runtime contexts</returns> public static GuestShaderCacheEntry[] CreateShaderCacheEntries(MemoryManager memoryManager, ReadOnlySpan <TranslatorContext> shaderContexts) { int startIndex = shaderContexts.Length > 1 ? 1 : 0; GuestShaderCacheEntry[] entries = new GuestShaderCacheEntry[shaderContexts.Length - startIndex]; for (int i = startIndex; i < shaderContexts.Length; i++) { TranslatorContext context = shaderContexts[i]; if (context == null) { continue; } TranslatorContext translatorContext2 = i == 1 ? shaderContexts[0] : null; int sizeA = translatorContext2 != null ? translatorContext2.Size : 0; byte[] code = new byte[context.Size + sizeA]; memoryManager.GetSpan(context.Address, context.Size).CopyTo(code); if (translatorContext2 != null) { memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan().Slice(context.Size, sizeA)); } GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor); if (context.GpuAccessor is GpuAccessor) { gpuAccessorHeader.TextureDescriptorCount = context.TextureHandlesForCache.Count; } GuestShaderCacheEntryHeader header = new GuestShaderCacheEntryHeader(context.Stage, context.Size, sizeA, gpuAccessorHeader); GuestShaderCacheEntry entry = new GuestShaderCacheEntry(header, code); if (context.GpuAccessor is GpuAccessor gpuAccessor) { foreach (int textureHandle in context.TextureHandlesForCache) { GuestTextureDescriptor textureDescriptor = ((Image.TextureDescriptor)gpuAccessor.GetTextureDescriptor(textureHandle, -1)).ToCache(); textureDescriptor.Handle = (uint)textureHandle; entry.TextureDescriptors.Add(textureHandle, textureDescriptor); } } entries[i - startIndex] = entry; } return(entries); }
/// <summary> /// Initialize the cache. /// </summary> internal void Initialize() { if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null) { _cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion); bool isReadOnly = _cacheManager.IsReadOnly; HashSet <Hash128> invalidEntries = null; if (isReadOnly) { Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)"); } else { invalidEntries = new HashSet <Hash128>(); } ReadOnlySpan <Hash128> guestProgramList = _cacheManager.GetGuestProgramList(); using AutoResetEvent progressReportEvent = new AutoResetEvent(false); _shaderCount = 0; _totalShaderCount = guestProgramList.Length; ShaderCacheStateChanged?.Invoke(ShaderCacheState.Start, _shaderCount, _totalShaderCount); Thread progressReportThread = null; if (guestProgramList.Length > 0) { progressReportThread = new Thread(ReportProgress) { Name = "ShaderCache.ProgressReporter", Priority = ThreadPriority.Lowest, IsBackground = true }; progressReportThread.Start(progressReportEvent); } // Make sure these are initialized before doing compilation. Capabilities caps = _context.Capabilities; int maxTaskCount = Math.Min(Environment.ProcessorCount, 8); int programIndex = 0; List <ShaderCompileTask> activeTasks = new List <ShaderCompileTask>(); AutoResetEvent taskDoneEvent = new AutoResetEvent(false); // This thread dispatches tasks to do shader translation, and creates programs that OpenGL will link in the background. // The program link status is checked in a non-blocking manner so that multiple shaders can be compiled at once. while (programIndex < guestProgramList.Length || activeTasks.Count > 0) { if (activeTasks.Count < maxTaskCount && programIndex < guestProgramList.Length) { // Begin a new shader compilation. Hash128 key = guestProgramList[programIndex]; byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key); bool hasHostCache = hostProgramBinary != null; IProgram hostProgram = null; // If the program sources aren't in the cache, compile from saved guest program. byte[] guestProgram = _cacheManager.GetGuestProgramByHash(ref key); if (guestProgram == null) { Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)"); // Should not happen, but if someone messed with the cache it's better to catch it. invalidEntries?.Add(key); _shaderCount = ++programIndex; continue; } ReadOnlySpan <byte> guestProgramReadOnlySpan = guestProgram; ReadOnlySpan <GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader); if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute) { Debug.Assert(cachedShaderEntries.Length == 1); GuestShaderCacheEntry entry = cachedShaderEntries[0]; HostShaderCacheEntry[] hostShaderEntries = null; // Try loading host shader binary. if (hasHostCache) { hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan <byte> hostProgramBinarySpan); hostProgramBinary = hostProgramBinarySpan.ToArray(); hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary); } ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent); activeTasks.Add(task); task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) => { ShaderProgram program = null; ShaderProgramInfo shaderProgramInfo = null; if (isHostProgramValid) { // Reconstruct code holder. program = new ShaderProgram(entry.Header.Stage, ""); shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo(); ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader)); return(true); } else { // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. Task compileTask = Task.Run(() => { IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute); program = Translator.CreateContext(0, gpuAccessor, options).Translate(out shaderProgramInfo); }); task.OnTask(compileTask, (bool _, ShaderCompileTask task) => { ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); // Compile shader and create program as the shader program binary got invalidated. shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code); hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null); task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) => { // As the host program was invalidated, save the new entry in the cache. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader }); if (!isReadOnly) { if (hasHostCache) { _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); } else { Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); _cacheManager.AddHostProgram(ref key, hostProgramBinary); } } _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader)); return(true); }); return(false); // Not finished: still need to compile the host program. }); return(false); // Not finished: translating the program. } }); } else { Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages); ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length]; List <ShaderProgram> shaderPrograms = new List <ShaderProgram>(); TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader); TranslationFlags flags = DefaultFlags; if (tfd != null) { flags |= TranslationFlags.Feedback; } TranslationCounts counts = new TranslationCounts(); HostShaderCacheEntry[] hostShaderEntries = null; // Try loading host shader binary. if (hasHostCache) { hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan <byte> hostProgramBinarySpan); hostProgramBinary = hostProgramBinarySpan.ToArray(); hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary); } ShaderCompileTask task = new ShaderCompileTask(taskDoneEvent); activeTasks.Add(task); GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray(); task.OnCompiled(hostProgram, (bool isHostProgramValid, ShaderCompileTask task) => { Task compileTask = Task.Run(() => { TranslatorContext[] shaderContexts = null; if (!isHostProgramValid) { shaderContexts = new TranslatorContext[1 + entries.Length]; for (int i = 0; i < entries.Length; i++) { GuestShaderCacheEntry entry = entries[i]; if (entry == null) { continue; } IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags); shaderContexts[i + 1] = Translator.CreateContext(0, gpuAccessor, options, counts); if (entry.Header.SizeA != 0) { var options2 = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags | TranslationFlags.VertexA); shaderContexts[0] = Translator.CreateContext((ulong)entry.Header.Size, gpuAccessor, options2, counts); } } } // Reconstruct code holder. for (int i = 0; i < entries.Length; i++) { GuestShaderCacheEntry entry = entries[i]; if (entry == null) { continue; } ShaderProgram program; ShaderProgramInfo shaderProgramInfo; if (isHostProgramValid) { program = new ShaderProgram(entry.Header.Stage, ""); shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo(); } else { IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); int stageIndex = i + 1; TranslatorContext currentStage = shaderContexts[stageIndex]; TranslatorContext nextStage = GetNextStageContext(shaderContexts, stageIndex); TranslatorContext vertexA = stageIndex == 1 ? shaderContexts[0] : null; program = currentStage.Translate(out shaderProgramInfo, nextStage, vertexA); } // NOTE: Vertex B comes first in the shader cache. byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size).ToArray(); byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray() : null; shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2); shaderPrograms.Add(program); } }); task.OnTask(compileTask, (bool _, ShaderCompileTask task) => { // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. if (!isHostProgramValid) { Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); List <IShader> hostShaders = new List <IShader>(); // Compile shaders and create program as the shader program binary got invalidated. for (int stage = 0; stage < Constants.ShaderStages; stage++) { ShaderProgram program = shaders[stage]?.Program; if (program == null) { continue; } IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code); shaders[stage].HostShader = hostShader; hostShaders.Add(hostShader); } hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd); task.OnCompiled(hostProgram, (bool isNewProgramValid, ShaderCompileTask task) => { // As the host program was invalidated, save the new entry in the cache. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders); if (!isReadOnly) { if (hasHostCache) { _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); } else { Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); _cacheManager.AddHostProgram(ref key, hostProgramBinary); } } _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders)); return(true); }); return(false); // Not finished: still need to compile the host program. } else { _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders)); return(true); } }); return(false); // Not finished: translating the program. }); } _shaderCount = ++programIndex; } // Process the queue. for (int i = 0; i < activeTasks.Count; i++) { ShaderCompileTask task = activeTasks[i]; if (task.IsDone()) { activeTasks.RemoveAt(i--); } } if (activeTasks.Count == maxTaskCount) { // Wait for a task to be done, or for 1ms. // Host shader compilation cannot signal when it is done, // so the 1ms timeout is required to poll status. taskDoneEvent.WaitOne(1); } } if (!isReadOnly) { // Remove entries that are broken in the cache _cacheManager.RemoveManifestEntries(invalidEntries); _cacheManager.FlushToArchive(); _cacheManager.Synchronize(); } progressReportEvent.Set(); progressReportThread?.Join(); ShaderCacheStateChanged?.Invoke(ShaderCacheState.Loaded, _shaderCount, _totalShaderCount); Logger.Info?.Print(LogClass.Gpu, $"Shader cache loaded {_shaderCount} entries."); } }
/// <summary> /// Create guest shader cache entries from the runtime contexts. /// </summary> /// <param name="channel">The GPU channel in use</param> /// <param name="shaderContexts">The runtime contexts</param> /// <returns>Guest shader cahe entries from the runtime contexts</returns> public static GuestShaderCacheEntry[] CreateShaderCacheEntries(GpuChannel channel, ReadOnlySpan <TranslatorContext> shaderContexts) { MemoryManager memoryManager = channel.MemoryManager; int startIndex = shaderContexts.Length > 1 ? 1 : 0; GuestShaderCacheEntry[] entries = new GuestShaderCacheEntry[shaderContexts.Length - startIndex]; for (int i = startIndex; i < shaderContexts.Length; i++) { TranslatorContext context = shaderContexts[i]; if (context == null) { continue; } GpuAccessor gpuAccessor = context.GpuAccessor as GpuAccessor; ulong cb1DataAddress; int cb1DataSize = gpuAccessor?.Cb1DataSize ?? 0; if (context.Stage == ShaderStage.Compute) { cb1DataAddress = channel.BufferManager.GetComputeUniformBufferAddress(1); } else { int stageIndex = context.Stage switch { ShaderStage.TessellationControl => 1, ShaderStage.TessellationEvaluation => 2, ShaderStage.Geometry => 3, ShaderStage.Fragment => 4, _ => 0 }; cb1DataAddress = channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, 1); } int size = context.Size; TranslatorContext translatorContext2 = i == 1 ? shaderContexts[0] : null; int sizeA = translatorContext2 != null ? translatorContext2.Size : 0; byte[] code = new byte[size + cb1DataSize + sizeA]; memoryManager.GetSpan(context.Address, size).CopyTo(code); if (cb1DataAddress != 0 && cb1DataSize != 0) { memoryManager.Physical.GetSpan(cb1DataAddress, cb1DataSize).CopyTo(code.AsSpan(size, cb1DataSize)); } if (translatorContext2 != null) { memoryManager.GetSpan(translatorContext2.Address, sizeA).CopyTo(code.AsSpan(size + cb1DataSize, sizeA)); } GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor); if (gpuAccessor != null) { gpuAccessorHeader.TextureDescriptorCount = context.TextureHandlesForCache.Count; } GuestShaderCacheEntryHeader header = new GuestShaderCacheEntryHeader( context.Stage, size + cb1DataSize, sizeA, cb1DataSize, gpuAccessorHeader); GuestShaderCacheEntry entry = new GuestShaderCacheEntry(header, code); if (gpuAccessor != null) { foreach (int textureHandle in context.TextureHandlesForCache) { GuestTextureDescriptor textureDescriptor = ((Image.TextureDescriptor)gpuAccessor.GetTextureDescriptor(textureHandle, -1)).ToCache(); textureDescriptor.Handle = (uint)textureHandle; entry.TextureDescriptors.Add(textureHandle, textureDescriptor); } } entries[i - startIndex] = entry; } return(entries); }
/// <summary> /// Initialize the cache. /// </summary> internal void Initialize() { if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null) { _cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion); bool isReadOnly = _cacheManager.IsReadOnly; HashSet <Hash128> invalidEntries = null; if (isReadOnly) { Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)"); } else { invalidEntries = new HashSet <Hash128>(); } ReadOnlySpan <Hash128> guestProgramList = _cacheManager.GetGuestProgramList(); for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++) { Hash128 key = guestProgramList[programIndex]; Logger.Info?.Print(LogClass.Gpu, $"Compiling shader {key} ({programIndex + 1} / {guestProgramList.Length})"); byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key); bool hasHostCache = hostProgramBinary != null; IProgram hostProgram = null; // If the program sources aren't in the cache, compile from saved guest program. byte[] guestProgram = _cacheManager.GetGuestProgramByHash(ref key); if (guestProgram == null) { Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)"); // Should not happen, but if someone messed with the cache it's better to catch it. invalidEntries?.Add(key); continue; } ReadOnlySpan <byte> guestProgramReadOnlySpan = guestProgram; ReadOnlySpan <GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader); if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute) { Debug.Assert(cachedShaderEntries.Length == 1); GuestShaderCacheEntry entry = cachedShaderEntries[0]; HostShaderCacheEntry[] hostShaderEntries = null; // Try loading host shader binary. if (hasHostCache) { hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan <byte> hostProgramBinarySpan); hostProgramBinary = hostProgramBinarySpan.ToArray(); hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary); } bool isHostProgramValid = hostProgram != null; ShaderProgram program; ShaderProgramInfo shaderProgramInfo; // Reconstruct code holder. if (isHostProgramValid) { program = new ShaderProgram(entry.Header.Stage, "", entry.Header.Size, entry.Header.SizeA); shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo(); } else { IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); program = Translator.CreateContext(0, gpuAccessor, DefaultFlags | TranslationFlags.Compute).Translate(out shaderProgramInfo); } ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. if (hostProgram == null) { Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); // Compile shader and create program as the shader program binary got invalidated. shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code); hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null); // As the host program was invalidated, save the new entry in the cache. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader }); if (!isReadOnly) { if (hasHostCache) { _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); } else { Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); _cacheManager.AddHostProgram(ref key, hostProgramBinary); } } } _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader)); } else { Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages); ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length]; List <ShaderProgram> shaderPrograms = new List <ShaderProgram>(); TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformationFeedbackInformations(ref guestProgramReadOnlySpan, fileHeader); TranslationFlags flags = DefaultFlags; if (tfd != null) { flags = TranslationFlags.Feedback; } TranslationCounts counts = new TranslationCounts(); HostShaderCacheEntry[] hostShaderEntries = null; // Try loading host shader binary. if (hasHostCache) { hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan <byte> hostProgramBinarySpan); hostProgramBinary = hostProgramBinarySpan.ToArray(); hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary); } bool isHostProgramValid = hostProgram != null; // Reconstruct code holder. for (int i = 0; i < cachedShaderEntries.Length; i++) { GuestShaderCacheEntry entry = cachedShaderEntries[i]; if (entry == null) { continue; } ShaderProgram program; if (entry.Header.SizeA != 0) { ShaderProgramInfo shaderProgramInfo; if (isHostProgramValid) { program = new ShaderProgram(entry.Header.Stage, "", entry.Header.Size, entry.Header.SizeA); shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo(); } else { IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); program = Translator.CreateContext((ulong)entry.Header.Size, 0, gpuAccessor, flags, counts).Translate(out shaderProgramInfo); } // NOTE: Vertex B comes first in the shader cache. byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size).ToArray(); byte[] code2 = entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray(); shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2); } else { ShaderProgramInfo shaderProgramInfo; if (isHostProgramValid) { program = new ShaderProgram(entry.Header.Stage, "", entry.Header.Size, entry.Header.SizeA); shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo(); } else { IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors); program = Translator.CreateContext(0, gpuAccessor, flags, counts).Translate(out shaderProgramInfo); } shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code); } shaderPrograms.Add(program); } // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again. if (!isHostProgramValid) { Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest..."); List <IShader> hostShaders = new List <IShader>(); // Compile shaders and create program as the shader program binary got invalidated. for (int stage = 0; stage < Constants.ShaderStages; stage++) { ShaderProgram program = shaders[stage]?.Program; if (program == null) { continue; } IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code); shaders[stage].HostShader = hostShader; hostShaders.Add(hostShader); } hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd); // As the host program was invalidated, save the new entry in the cache. hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders); if (!isReadOnly) { if (hasHostCache) { _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); } else { Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); _cacheManager.AddHostProgram(ref key, hostProgramBinary); } } } _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders)); } } if (!isReadOnly) { // Remove entries that are broken in the cache _cacheManager.RemoveManifestEntries(invalidEntries); _cacheManager.FlushToArchive(); _cacheManager.Synchronize(); } Logger.Info?.Print(LogClass.Gpu, "Shader cache loaded."); } }
/// <summary> /// Migrates from the old cache format to the new one. /// </summary> /// <param name="context">GPU context</param> /// <param name="hostStorage">Disk cache host storage (used to create the new shader files)</param> /// <returns>Number of migrated shaders</returns> public static int MigrateFromLegacyCache(GpuContext context, DiskCacheHostStorage hostStorage) { string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId); string cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program"); // If the directory does not exist, we have no old cache. // Exist early as the CacheManager constructor will create the directories. if (!Directory.Exists(cacheDirectory)) { return(0); } if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null) { CacheManager cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion); bool isReadOnly = cacheManager.IsReadOnly; HashSet <Hash128> invalidEntries = null; if (isReadOnly) { Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)"); } else { invalidEntries = new HashSet <Hash128>(); } ReadOnlySpan <Hash128> guestProgramList = cacheManager.GetGuestProgramList(); for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++) { Hash128 key = guestProgramList[programIndex]; byte[] guestProgram = cacheManager.GetGuestProgramByHash(ref key); if (guestProgram == null) { Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)"); continue; } ReadOnlySpan <byte> guestProgramReadOnlySpan = guestProgram; ReadOnlySpan <GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader); if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute) { Debug.Assert(cachedShaderEntries.Length == 1); GuestShaderCacheEntry entry = cachedShaderEntries[0]; byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); Span <byte> codeSpan = entry.Code; byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray(); ShaderProgramInfo info = new ShaderProgramInfo( Array.Empty <BufferDescriptor>(), Array.Empty <BufferDescriptor>(), Array.Empty <TextureDescriptor>(), Array.Empty <TextureDescriptor>(), ShaderStage.Compute, false, false, 0, 0); GpuChannelComputeState computeState = new GpuChannelComputeState( entry.Header.GpuAccessorHeader.ComputeLocalSizeX, entry.Header.GpuAccessorHeader.ComputeLocalSizeY, entry.Header.GpuAccessorHeader.ComputeLocalSizeZ, entry.Header.GpuAccessorHeader.ComputeLocalMemorySize, entry.Header.GpuAccessorHeader.ComputeSharedMemorySize); ShaderSpecializationState specState = new ShaderSpecializationState(computeState); foreach (var td in entry.TextureDescriptors) { var handle = td.Key; var data = td.Value; specState.RegisterTexture( 0, handle, -1, data.UnpackFormat(), data.UnpackSrgb(), data.UnpackTextureTarget(), data.UnpackTextureCoordNormalized()); } CachedShaderStage shader = new CachedShaderStage(info, code, cb1Data); CachedShaderProgram program = new CachedShaderProgram(null, specState, shader); hostStorage.AddShader(context, program, ReadOnlySpan <byte> .Empty); } else { Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages); CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1]; List <ShaderProgram> shaderPrograms = new List <ShaderProgram>(); TransformFeedbackDescriptorOld[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader); GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray(); GuestGpuAccessorHeader accessorHeader = entries[0].Header.GpuAccessorHeader; TessMode tessMode = new TessMode(); int tessPatchType = accessorHeader.TessellationModePacked & 3; int tessSpacing = (accessorHeader.TessellationModePacked >> 2) & 3; bool tessCw = (accessorHeader.TessellationModePacked & 0x10) != 0; tessMode.Packed = (uint)tessPatchType; tessMode.Packed |= (uint)(tessSpacing << 4); if (tessCw) { tessMode.Packed |= 0x100; } PrimitiveTopology topology = accessorHeader.PrimitiveTopology switch { InputTopology.Lines => PrimitiveTopology.Lines, InputTopology.LinesAdjacency => PrimitiveTopology.LinesAdjacency, InputTopology.Triangles => PrimitiveTopology.Triangles, InputTopology.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency, _ => PrimitiveTopology.Points }; GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState( accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce), topology, tessMode); TransformFeedbackDescriptor[] tfdNew = null; if (tfd != null) { tfdNew = new TransformFeedbackDescriptor[tfd.Length]; for (int tfIndex = 0; tfIndex < tfd.Length; tfIndex++) { Array32 <uint> varyingLocations = new Array32 <uint>(); Span <byte> varyingLocationsSpan = MemoryMarshal.Cast <uint, byte>(varyingLocations.ToSpan()); tfd[tfIndex].VaryingLocations.CopyTo(varyingLocationsSpan.Slice(0, tfd[tfIndex].VaryingLocations.Length)); tfdNew[tfIndex] = new TransformFeedbackDescriptor( tfd[tfIndex].BufferIndex, tfd[tfIndex].Stride, tfd[tfIndex].VaryingLocations.Length, ref varyingLocations); } } ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, tfdNew); for (int i = 0; i < entries.Length; i++) { GuestShaderCacheEntry entry = entries[i]; if (entry == null) { continue; } ShaderProgramInfo info = new ShaderProgramInfo( Array.Empty <BufferDescriptor>(), Array.Empty <BufferDescriptor>(), Array.Empty <TextureDescriptor>(), Array.Empty <TextureDescriptor>(), (ShaderStage)(i + 1), false, false, 0, 0); // NOTE: Vertex B comes first in the shader cache. byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null; Span <byte> codeSpan = entry.Code; byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray(); shaders[i + 1] = new CachedShaderStage(info, code, cb1Data); if (code2 != null) { shaders[0] = new CachedShaderStage(null, code2, cb1Data); } foreach (var td in entry.TextureDescriptors) { var handle = td.Key; var data = td.Value; specState.RegisterTexture( i, handle, -1, data.UnpackFormat(), data.UnpackSrgb(), data.UnpackTextureTarget(), data.UnpackTextureCoordNormalized()); } } CachedShaderProgram program = new CachedShaderProgram(null, specState, shaders); hostStorage.AddShader(context, program, ReadOnlySpan <byte> .Empty); } } return(guestProgramList.Length); } return(0); } }