/// <summary>
        /// Recompiles a compute program from guest code.
        /// </summary>
        /// <param name="shaders">Shader stages</param>
        /// <param name="specState">Specialization state</param>
        /// <param name="programIndex">Program index</param>
        private void RecompileComputeFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
        {
            CachedShaderStage         shader       = shaders[0];
            ResourceCounts            counts       = new ResourceCounts();
            ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.ComputeState);
            DiskCacheGpuAccessor      gpuAccessor  = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0);

            TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0);

            ShaderProgram program = translatorContext.Translate();

            shaders[0] = new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data);

            _compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true));
        }
        public static ShaderSource[] Unpack(CachedShaderStage[] stages, byte[] code, bool compute)
        {
            using MemoryStream input  = new MemoryStream(code);
            using BinaryReader reader = new BinaryReader(input);

            List <ShaderSource> output = new List <ShaderSource>();

            for (int i = compute ? 0 : 1; i < stages.Length; i++)
            {
                CachedShaderStage stage = stages[i];

                if (stage == null)
                {
                    continue;
                }

                int    binaryCodeLength = reader.ReadInt32();
                byte[] binaryCode       = reader.ReadBytes(binaryCodeLength);

                output.Add(new ShaderSource(binaryCode, ShaderCache.GetBindings(stage.Info), stage.Info.Stage, TargetLanguage.Spirv));
            }

            return(output.ToArray());
        }
Beispiel #3
0
        /// <summary>
        /// Loads all shaders from the cache.
        /// </summary>
        /// <param name="context">GPU context</param>
        /// <param name="loader">Parallel disk cache loader</param>
        public void LoadShaders(GpuContext context, ParallelDiskCacheLoader loader)
        {
            if (!CacheExists())
            {
                return;
            }

            Stream hostTocFileStream  = null;
            Stream hostDataFileStream = null;

            try
            {
                using var tocFileStream  = DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: false);
                using var dataFileStream = DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: false);

                using var guestTocFileStream  = _guestStorage.OpenTocFileStream();
                using var guestDataFileStream = _guestStorage.OpenDataFileStream();

                BinarySerializer tocReader  = new BinarySerializer(tocFileStream);
                BinarySerializer dataReader = new BinarySerializer(dataFileStream);

                TocHeader header = new TocHeader();

                if (!tocReader.TryRead(ref header) || header.Magic != TocsMagic)
                {
                    throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
                }

                if (header.FormatVersion != FileFormatVersionPacked)
                {
                    throw new DiskCacheLoadException(DiskCacheLoadResult.IncompatibleVersion);
                }

                bool loadHostCache = header.CodeGenVersion == CodeGenVersion;

                int programIndex = 0;

                DataEntry entry = new DataEntry();

                while (tocFileStream.Position < tocFileStream.Length && loader.Active)
                {
                    ulong dataOffset = 0;
                    tocReader.Read(ref dataOffset);

                    if ((ulong)dataOffset >= (ulong)dataFileStream.Length)
                    {
                        throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
                    }

                    dataFileStream.Seek((long)dataOffset, SeekOrigin.Begin);

                    dataReader.BeginCompression();
                    dataReader.Read(ref entry);
                    uint stagesBitMask = entry.StagesBitMask;

                    if ((stagesBitMask & ~0x3fu) != 0)
                    {
                        throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
                    }

                    bool isCompute = stagesBitMask == 0;
                    if (isCompute)
                    {
                        stagesBitMask = 1;
                    }

                    CachedShaderStage[] shaders = new CachedShaderStage[isCompute ? 1 : Constants.ShaderStages + 1];

                    DataEntryPerStage stageEntry = new DataEntryPerStage();

                    while (stagesBitMask != 0)
                    {
                        int stageIndex = BitOperations.TrailingZeroCount(stagesBitMask);

                        dataReader.Read(ref stageEntry);

                        ShaderProgramInfo info = stageIndex != 0 || isCompute?ReadShaderProgramInfo(ref dataReader) : null;

                        (byte[] guestCode, byte[] cb1Data) = _guestStorage.LoadShader(
                            guestTocFileStream,
                            guestDataFileStream,
                            stageEntry.GuestCodeIndex);

                        shaders[stageIndex] = new CachedShaderStage(info, guestCode, cb1Data);

                        stagesBitMask &= ~(1u << stageIndex);
                    }

                    ShaderSpecializationState specState = ShaderSpecializationState.Read(ref dataReader);
                    dataReader.EndCompression();

                    if (loadHostCache)
                    {
                        byte[] hostCode = ReadHostCode(context, ref hostTocFileStream, ref hostDataFileStream, programIndex);

                        if (hostCode != null)
                        {
                            bool     hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
                            int      fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
                            IProgram hostProgram       = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, new ShaderInfo(fragmentOutputMap));

                            CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders);

                            loader.QueueHostProgram(program, hostProgram, programIndex, isCompute);
                        }
                        else
                        {
                            loadHostCache = false;
                        }
                    }

                    if (!loadHostCache)
                    {
                        loader.QueueGuestProgram(shaders, specState, programIndex, isCompute);
                    }

                    loader.CheckCompilation();
                    programIndex++;
                }
            }
            finally
            {
                _guestStorage.ClearMemoryCache();

                hostTocFileStream?.Dispose();
                hostDataFileStream?.Dispose();
            }
        }
        /// <summary>
        /// Recompiles a graphics program from guest code.
        /// </summary>
        /// <param name="shaders">Shader stages</param>
        /// <param name="specState">Specialization state</param>
        /// <param name="programIndex">Program index</param>
        private void RecompileGraphicsFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex)
        {
            ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.GraphicsState, specState.TransformFeedbackDescriptors);
            ResourceCounts            counts       = new ResourceCounts();

            TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1];
            TranslatorContext   nextStage          = null;

            for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--)
            {
                CachedShaderStage shader = shaders[stageIndex + 1];

                if (shader != null)
                {
                    byte[] guestCode = shader.Code;
                    byte[] cb1Data   = shader.Cb1Data;

                    DiskCacheGpuAccessor gpuAccessor  = new DiskCacheGpuAccessor(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex);
                    TranslatorContext    currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, 0);

                    if (nextStage != null)
                    {
                        currentStage.SetNextStage(nextStage);
                    }

                    if (stageIndex == 0 && shaders[0] != null)
                    {
                        byte[] guestCodeA = shaders[0].Code;
                        byte[] cb1DataA   = shaders[0].Cb1Data;

                        DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0);
                        translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, DefaultFlags | TranslationFlags.VertexA, 0);
                    }

                    translatorContexts[stageIndex + 1] = currentStage;
                    nextStage = currentStage;
                }
            }

            List <ShaderProgram> translatedStages = new List <ShaderProgram>();

            for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
            {
                TranslatorContext currentStage = translatorContexts[stageIndex + 1];

                if (currentStage != null)
                {
                    ShaderProgram program;

                    byte[] guestCode = shaders[stageIndex + 1].Code;
                    byte[] cb1Data   = shaders[stageIndex + 1].Cb1Data;

                    if (stageIndex == 0 && shaders[0] != null)
                    {
                        program = currentStage.Translate(translatorContexts[0]);

                        byte[] guestCodeA = shaders[0].Code;
                        byte[] cb1DataA   = shaders[0].Cb1Data;

                        shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA);
                        shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
                    }
                    else
                    {
                        program = currentStage.Translate();

                        shaders[stageIndex + 1] = new CachedShaderStage(program.Info, guestCode, cb1Data);
                    }

                    if (program != null)
                    {
                        translatedStages.Add(program);
                    }
                }
            }

            _compilationQueue.Enqueue(new ProgramCompilation(translatedStages.ToArray(), shaders, newSpecState, programIndex, isCompute: false));
        }
Beispiel #5
0
        /// <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);
        }
    }
        /// <summary>
        /// Reads the host code for a given shader, if existent.
        /// </summary>
        /// <param name="context">GPU context</param>
        /// <param name="tocFileStream">Host TOC file stream, intialized if needed</param>
        /// <param name="dataFileStream">Host data file stream, initialized if needed</param>
        /// <param name="guestShaders">Guest shader code for each active stage</param>
        /// <param name="programIndex">Index of the program on the cache</param>
        /// <param name="expectedTimestamp">Timestamp of the shared cache file. The host file must be newer than it</param>
        /// <returns>Host binary code, or null if not found</returns>
        private (byte[], CachedShaderStage[]) ReadHostCode(
            GpuContext context,
            ref Stream tocFileStream,
            ref Stream dataFileStream,
            GuestCodeAndCbData?[] guestShaders,
            int programIndex,
            ulong expectedTimestamp)
        {
            if (tocFileStream == null && dataFileStream == null)
            {
                string tocFilePath  = Path.Combine(_basePath, GetHostTocFileName(context));
                string dataFilePath = Path.Combine(_basePath, GetHostDataFileName(context));

                if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath))
                {
                    return(null, null);
                }

                tocFileStream  = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false);
                dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false);

                BinarySerializer tempTocReader = new BinarySerializer(tocFileStream);

                TocHeader header = new TocHeader();

                tempTocReader.Read(ref header);

                if (header.Timestamp < expectedTimestamp)
                {
                    return(null, null);
                }
            }

            int offset = Unsafe.SizeOf <TocHeader>() + programIndex * Unsafe.SizeOf <OffsetAndSize>();

            if (offset + Unsafe.SizeOf <OffsetAndSize>() > tocFileStream.Length)
            {
                return(null, null);
            }

            if ((ulong)offset >= (ulong)dataFileStream.Length)
            {
                throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
            }

            tocFileStream.Seek(offset, SeekOrigin.Begin);

            BinarySerializer tocReader = new BinarySerializer(tocFileStream);

            OffsetAndSize offsetAndSize = new OffsetAndSize();

            tocReader.Read(ref offsetAndSize);

            if (offsetAndSize.Offset >= (ulong)dataFileStream.Length)
            {
                throw new DiskCacheLoadException(DiskCacheLoadResult.FileCorruptedGeneric);
            }

            dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin);

            byte[] hostCode = new byte[offsetAndSize.UncompressedSize];

            BinarySerializer.ReadCompressed(dataFileStream, hostCode);

            CachedShaderStage[] shaders    = new CachedShaderStage[guestShaders.Length];
            BinarySerializer    dataReader = new BinarySerializer(dataFileStream);

            dataFileStream.Seek((long)(offsetAndSize.Offset + offsetAndSize.CompressedSize), SeekOrigin.Begin);

            dataReader.BeginCompression();

            for (int index = 0; index < guestShaders.Length; index++)
            {
                if (!guestShaders[index].HasValue)
                {
                    continue;
                }

                GuestCodeAndCbData guestShader = guestShaders[index].Value;
                ShaderProgramInfo  info        = index != 0 || guestShaders.Length == 1 ? ReadShaderProgramInfo(ref dataReader) : null;

                shaders[index] = new CachedShaderStage(info, guestShader.Code, guestShader.Cb1Data);
            }

            dataReader.EndCompression();

            return(hostCode, shaders);
        }