public static ShaderCacheFile GetOrCreateCachedShaders( ResourceFactory factory, Assembly shaderAssembly, string shaderName) { const string shaderCacheFolder = "ShaderCache"; var backendType = factory.BackendType; var targetExtension = backendType.ToString().ToLowerInvariant(); if (!Directory.Exists(shaderCacheFolder)) { Directory.CreateDirectory(shaderCacheFolder); } var vsSpvBytes = ReadShaderSpv(shaderAssembly, shaderName, "vert"); var fsSpvBytes = ReadShaderSpv(shaderAssembly, shaderName, "frag"); var spvHash = GetShaderHash(vsSpvBytes, fsSpvBytes); // Look for cached shader file on disk match the input SPIR-V shaders. var cacheFilePath = Path.Combine(shaderCacheFolder, $"OpenSage.Assets.Shaders.{shaderName}.{spvHash}.{targetExtension}"); if (ShaderCacheFile.TryLoad(cacheFilePath, out var shaderCacheFile)) { // Cache is valid - use it. return(shaderCacheFile); } // Cache is invalid or doesn't exist - do cross-compilation. // For Vulkan, we don't actually need to do cross-compilation. But we do need to get reflection data. // So we cross-compile to HLSL, throw away the resulting HLSL, and use the reflection data. var compilationTarget = backendType == GraphicsBackend.Vulkan ? CrossCompileTarget.HLSL : GetCompilationTarget(backendType); var compilationResult = SpirvCompilation.CompileVertexFragment( vsSpvBytes, fsSpvBytes, compilationTarget, new CrossCompileOptions()); byte[] vsBytes, fsBytes; switch (backendType) { case GraphicsBackend.Vulkan: vsBytes = vsSpvBytes; fsBytes = fsSpvBytes; break; case GraphicsBackend.Direct3D11: vsBytes = CompileHlsl(compilationResult.VertexShader, "vs_5_0"); fsBytes = CompileHlsl(compilationResult.FragmentShader, "ps_5_0"); break; case GraphicsBackend.OpenGL: case GraphicsBackend.OpenGLES: vsBytes = Encoding.ASCII.GetBytes(compilationResult.VertexShader); fsBytes = Encoding.ASCII.GetBytes(compilationResult.FragmentShader); break; case GraphicsBackend.Metal: // TODO: Compile to IR. vsBytes = Encoding.UTF8.GetBytes(compilationResult.VertexShader); fsBytes = Encoding.UTF8.GetBytes(compilationResult.FragmentShader); break; default: throw new InvalidOperationException(); } var entryPoint = factory.BackendType == GraphicsBackend.Metal ? $"{EntryPoint}0" : EntryPoint; shaderCacheFile = new ShaderCacheFile( new ShaderDescription(ShaderStages.Vertex, vsBytes, entryPoint), new ShaderDescription(ShaderStages.Fragment, fsBytes, entryPoint), compilationResult.Reflection.ResourceLayouts); shaderCacheFile.Save(cacheFilePath); return(shaderCacheFile); }
public static bool TryLoad(string filePath, out ShaderCacheFile result) { if (!File.Exists(filePath)) { result = null; return(false); } using var fileStream = File.OpenRead(filePath); using var binaryReader = new BinaryReader(fileStream); var version = binaryReader.ReadInt32(); if (version != Version) { result = null; return(false); } var vsEntryPoint = binaryReader.ReadString(); var vsBytesLength = binaryReader.ReadInt32(); var vsBytes = new byte[vsBytesLength]; for (var i = 0; i < vsBytesLength; i++) { vsBytes[i] = binaryReader.ReadByte(); } var fsEntryPoint = binaryReader.ReadString(); var fsBytesLength = binaryReader.ReadInt32(); var fsBytes = new byte[fsBytesLength]; for (var i = 0; i < fsBytesLength; i++) { fsBytes[i] = binaryReader.ReadByte(); } var numResourceLayoutDescriptions = binaryReader.ReadInt32(); var resourceLayoutDescriptions = new ResourceLayoutDescription[numResourceLayoutDescriptions]; for (var i = 0; i < numResourceLayoutDescriptions; i++) { var numElements = binaryReader.ReadInt32(); resourceLayoutDescriptions[i] = new ResourceLayoutDescription { Elements = new ResourceLayoutElementDescription[numElements] }; for (var j = 0; j < numElements; j++) { resourceLayoutDescriptions[i].Elements[j] = new ResourceLayoutElementDescription { Name = binaryReader.ReadString(), Kind = (ResourceKind)binaryReader.ReadByte(), Stages = (ShaderStages)binaryReader.ReadByte(), Options = (ResourceLayoutElementOptions)binaryReader.ReadByte(), }; } } result = new ShaderCacheFile( new ShaderDescription(ShaderStages.Vertex, vsBytes, vsEntryPoint), new ShaderDescription(ShaderStages.Fragment, fsBytes, fsEntryPoint), resourceLayoutDescriptions); return(true); }