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);
    }
Beispiel #2
0
    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);
    }