/// <summary> /// Ensures that the image bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. /// </summary> /// <param name="pool">The current texture pool</param> /// <param name="stage">The shader stage using the textures to be bound</param> /// <param name="stageIndex">The stage number of the specified shader stage</param> private void CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex) { if (_imageBindings[stageIndex] == null) { return; } // Scales for images appear after the texture ones. int baseScaleIndex = _textureBindings[stageIndex]?.Length ?? 0; for (int index = 0; index < _imageBindings[stageIndex].Length; index++) { TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index]; int textureBufferIndex = bindingInfo.CbufSlot < 0 ? _textureBufferIndex : bindingInfo.CbufSlot; int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex); int textureId = UnpackTextureId(packedId); Texture texture = pool.Get(textureId); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore); if (hostTexture != null && texture.Target == Target.TextureBuffer) { // Ensure that the buffer texture is using the correct buffer as storage. // Buffers are frequently re-created to accomodate larger data, so we need to re-bind // to ensure we're not using a old buffer that was already deleted. Format format = bindingInfo.Format; if (format == 0 && texture != null) { format = texture.Format; } _context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true); } else if (isStore) { texture?.SignalModified(); } if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) { if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage)) { hostTexture = texture?.GetTargetTexture(bindingInfo.Target); } _imageState[stageIndex][index].Texture = hostTexture; Format format = bindingInfo.Format; if (format == 0 && texture != null) { format = texture.Format; } _context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format); } } }
/// <summary> /// Ensures that the texture bindings are visible to the host GPU. /// Note: this actually performs the binding using the host graphics API. /// </summary> /// <param name="pool">The current texture pool</param> /// <param name="stage">The shader stage using the textures to be bound</param> /// <param name="stageIndex">The stage number of the specified shader stage</param> private void CommitTextureBindings(TexturePool pool, ShaderStage stage, int stageIndex) { if (_textureBindings[stageIndex] == null) { return; } for (int index = 0; index < _textureBindings[stageIndex].Length; index++) { TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index]; int textureBufferIndex = bindingInfo.CbufSlot < 0 ? _textureBufferIndex : bindingInfo.CbufSlot; int packedId = ReadPackedId(stageIndex, bindingInfo.Handle, textureBufferIndex); int textureId = UnpackTextureId(packedId); int samplerId; if (_samplerIndex == SamplerIndex.ViaHeaderIndex) { samplerId = textureId; } else { samplerId = UnpackSamplerId(packedId); } Texture texture = pool.Get(textureId); ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target); if (_textureState[stageIndex][index].Texture != hostTexture || _rebind) { if (UpdateScale(texture, bindingInfo, index, stage)) { hostTexture = texture?.GetTargetTexture(bindingInfo.Target); } _textureState[stageIndex][index].Texture = hostTexture; _context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture); } if (hostTexture != null && texture.Target == Target.TextureBuffer) { // Ensure that the buffer texture is using the correct buffer as storage. // Buffers are frequently re-created to accomodate larger data, so we need to re-bind // to ensure we're not using a old buffer that was already deleted. _context.Methods.BufferManager.SetBufferTextureStorage(hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false); } Sampler sampler = _samplerPool.Get(samplerId); ISampler hostSampler = sampler?.HostSampler; if (_textureState[stageIndex][index].Sampler != hostSampler || _rebind) { _textureState[stageIndex][index].Sampler = hostSampler; _context.Renderer.Pipeline.SetSampler(bindingInfo.Binding, hostSampler); } } }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> /// <param name="flags">The texture search flags, defines texture comparison rules</param> /// <param name="info">Texture information of the texture to be found or created</param> /// <param name="layerSize">Size in bytes of a single texture layer</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <param name="range">Optional ranges of physical memory where the texture data is located</param> /// <returns>The texture</returns> public Texture FindOrCreateTexture( MemoryManager memoryManager, TextureSearchFlags flags, TextureInfo info, int layerSize = 0, Size?sizeHint = null, MultiRange?range = null) { bool isSamplerTexture = (flags & TextureSearchFlags.ForSampler) != 0; bool isScalable = IsUpscaleCompatible(info); TextureScaleMode scaleMode = TextureScaleMode.Blacklisted; if (isScalable) { scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; } ulong address; if (range != null) { address = range.Value.GetSubRange(0).Address; } else { address = memoryManager.Translate(info.GpuAddress); if (address == MemoryManager.PteUnmapped) { return(null); } } int sameAddressOverlapsCount; lock (_textures) { // Try to find a perfect texture match, with the same address and parameters. sameAddressOverlapsCount = _textures.FindOverlaps(address, ref _textureOverlaps); } Texture texture = null; TextureMatchQuality bestQuality = TextureMatchQuality.NoMatch; for (int index = 0; index < sameAddressOverlapsCount; index++) { Texture overlap = _textureOverlaps[index]; TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags); if (matchQuality != TextureMatchQuality.NoMatch) { // If the parameters match, we need to make sure the texture is mapped to the same memory regions. if (range != null) { // If a range of memory was supplied, just check if the ranges match. if (!overlap.Range.Equals(range.Value)) { continue; } } else { // If no range was supplied, we can check if the GPU virtual address match. If they do, // we know the textures are located at the same memory region. // If they don't, it may still be mapped to the same physical region, so we // do a more expensive check to tell if they are mapped into the same physical regions. // If the GPU VA for the texture has ever been unmapped, then the range must be checked regardless. if ((overlap.Info.GpuAddress != info.GpuAddress || overlap.ChangedMapping) && !memoryManager.CompareRange(overlap.Range, info.GpuAddress)) { continue; } } } if (matchQuality == TextureMatchQuality.Perfect) { texture = overlap; break; } else if (matchQuality > bestQuality) { texture = overlap; bestQuality = matchQuality; } } if (texture != null) { if (!isSamplerTexture) { // If not a sampler texture, it is managed by the auto delete // cache, ensure that it is on the "top" of the list to avoid // deletion. _cache.Lift(texture); } ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); texture.SynchronizeMemory(); return(texture); } // Calculate texture sizes, used to find all overlapping textures. SizeInfo sizeInfo = info.CalculateSizeInfo(layerSize); ulong size = (ulong)sizeInfo.TotalSize; if (range == null) { range = memoryManager.GetPhysicalRegions(info.GpuAddress, size); } // Find view compatible matches. int overlapsCount; lock (_textures) { overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); } if (_overlapInfo.Length != _textureOverlaps.Length) { Array.Resize(ref _overlapInfo, _textureOverlaps.Length); } // =============== Find Texture View of Existing Texture =============== int fullyCompatible = 0; // Evaluate compatibility of overlaps for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, sizeInfo.LayerSize, out int firstLayer, out int firstLevel); if (overlapCompatibility == TextureViewCompatibility.Full) { if (overlap.IsView) { overlapCompatibility = TextureViewCompatibility.CopyOnly; } else { fullyCompatible++; } } _overlapInfo[index] = new OverlapInfo(overlapCompatibility, firstLayer, firstLevel); } // Search through the overlaps to find a compatible view and establish any copy dependencies. for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (oInfo.Compatibility == TextureViewCompatibility.Full) { TextureInfo adjInfo = AdjustSizes(overlap, info, oInfo.FirstLevel); if (!isSamplerTexture) { info = adjInfo; } texture = overlap.CreateView(adjInfo, sizeInfo, range.Value, oInfo.FirstLayer, oInfo.FirstLevel); ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); texture.SynchronizeMemory(); break; } else if (oInfo.Compatibility == TextureViewCompatibility.CopyOnly && fullyCompatible == 0) { // Only copy compatible. If there's another choice for a FULLY compatible texture, choose that instead. texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); texture.InitializeGroup(true, true); texture.InitializeData(false, false); overlap.SynchronizeMemory(); overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); break; } } if (texture != null) { // This texture could be a view of multiple parent textures with different storages, even if it is a view. // When a texture is created, make sure all possible dependencies to other textures are created as copies. // (even if it could be fulfilled without a copy) for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (oInfo.Compatibility != TextureViewCompatibility.Incompatible && overlap.Group != texture.Group) { overlap.SynchronizeMemory(); overlap.CreateCopyDependency(texture, oInfo.FirstLayer, oInfo.FirstLevel, true); } } texture.SynchronizeMemory(); } // =============== Create a New Texture =============== // No match, create a new texture. if (texture == null) { texture = new Texture(_context, _physicalMemory, info, sizeInfo, range.Value, scaleMode); // Step 1: Find textures that are view compatible with the new texture. // Any textures that are incompatible will contain garbage data, so they should be removed where possible. int viewCompatible = 0; fullyCompatible = 0; bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy); bool hasLayerViews = false; bool hasMipViews = false; for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; bool overlapInCache = overlap.CacheNode != null; TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, overlap.LayerSize, out int firstLayer, out int firstLevel); if (overlap.IsView && compatibility == TextureViewCompatibility.Full) { compatibility = TextureViewCompatibility.CopyOnly; } if (compatibility != TextureViewCompatibility.Incompatible) { if (compatibility == TextureViewCompatibility.Full) { if (viewCompatible == fullyCompatible) { _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[viewCompatible++] = overlap; } else { // Swap overlaps so that the fully compatible views have priority. _overlapInfo[viewCompatible] = _overlapInfo[fullyCompatible]; _textureOverlaps[viewCompatible++] = _textureOverlaps[fullyCompatible]; _overlapInfo[fullyCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[fullyCompatible] = overlap; } fullyCompatible++; } else { _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[viewCompatible++] = overlap; } hasLayerViews |= overlap.Info.GetSlices() < texture.Info.GetSlices(); hasMipViews |= overlap.Info.Levels < texture.Info.Levels; } else if (overlapInCache || !setData) { if (info.GobBlocksInZ > 1 && info.GobBlocksInZ == overlap.Info.GobBlocksInZ) { // Allow overlapping slices of 3D textures. Could be improved in future by making sure the textures don't overlap. continue; } // The overlap texture is going to contain garbage data after we draw, or is generally incompatible. // If the texture cannot be entirely contained in the new address space, and one of its view children is compatible with us, // it must be flushed before removal, so that the data is not lost. // If the texture was modified since its last use, then that data is probably meant to go into this texture. // If the data has been modified by the CPU, then it also shouldn't be flushed. bool modified = overlap.ConsumeModified(); bool flush = overlapInCache && !modified && !texture.Range.Contains(overlap.Range) && overlap.HasViewCompatibleChild(texture); setData |= modified || flush; if (overlapInCache) { _cache.Remove(overlap, flush); } } } texture.InitializeGroup(hasLayerViews, hasMipViews); // We need to synchronize before copying the old view data to the texture, // otherwise the copied data would be overwritten by a future synchronization. texture.InitializeData(false, setData); for (int index = 0; index < viewCompatible; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (overlap.Group == texture.Group) { // If the texture group is equal, then this texture (or its parent) is already a view. continue; } TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); if (texture.ScaleFactor != overlap.ScaleFactor) { // A bit tricky, our new texture may need to contain an existing texture that is upscaled, but isn't itself. // In that case, we prefer the higher scale only if our format is render-target-like, otherwise we scale the view down before copy. texture.PropagateScale(overlap); } if (oInfo.Compatibility != TextureViewCompatibility.Full) { // Copy only compatibility, or target texture is already a view. overlap.SynchronizeMemory(); texture.CreateCopyDependency(overlap, oInfo.FirstLayer, oInfo.FirstLevel, false); } else { TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor); ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); overlap.SynchronizeMemory(); overlap.HostTexture.CopyTo(newView, 0, 0); overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); } } texture.SynchronizeMemory(); } // Sampler textures are managed by the texture pool, all other textures // are managed by the auto delete cache. if (!isSamplerTexture) { _cache.Add(texture); } lock (_textures) { _textures.Add(texture); } ShrinkOverlapsBufferIfNeeded(); return(texture); }
/// <summary> /// Adjusts the size of the texture information for a given mipmap level, /// based on the size of a parent texture. /// </summary> /// <param name="parent">The parent texture</param> /// <param name="info">The texture information to be adjusted</param> /// <param name="firstLevel">The first level of the texture view</param> /// <returns>The adjusted texture information with the new size</returns> private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel) { // When the texture is used as view of another texture, we must // ensure that the sizes are valid, otherwise data uploads would fail // (and the size wouldn't match the real size used on the host API). // Given a parent texture from where the view is created, we have the // following rules: // - The view size must be equal to the parent size, divided by (2 ^ l), // where l is the first mipmap level of the view. The division result must // be rounded down, and the result must be clamped to 1. // - If the parent format is compressed, and the view format isn't, the // view size is calculated as above, but the width and height of the // view must be also divided by the compressed format block width and height. // - If the parent format is not compressed, and the view is, the view // size is calculated as described on the first point, but the width and height // of the view must be also multiplied by the block width and height. int width = Math.Max(1, parent.Info.Width >> firstLevel); int height = Math.Max(1, parent.Info.Height >> firstLevel); if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed) { width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth); height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight); } else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed) { width *= info.FormatInfo.BlockWidth; height *= info.FormatInfo.BlockHeight; } int depthOrLayers; if (info.Target == Target.Texture3D) { depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel); } else { depthOrLayers = info.DepthOrLayers; } return(new TextureInfo( info.GpuAddress, width, height, depthOrLayers, info.Levels, info.SamplesInX, info.SamplesInY, info.Stride, info.IsLinear, info.GobBlocksInY, info.GobBlocksInZ, info.GobBlocksInTileX, info.Target, info.FormatInfo, info.DepthStencilMode, info.SwizzleR, info.SwizzleG, info.SwizzleB, info.SwizzleA)); }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="memoryManager">GPU memory manager where the texture is mapped</param> /// <param name="colorState">Color buffer texture to find or create</param> /// <param name="samplesInX">Number of samples in the X direction, for MSAA</param> /// <param name="samplesInY">Number of samples in the Y direction, for MSAA</param> /// <param name="sizeHint">A hint indicating the minimum used size for the texture</param> /// <returns>The texture</returns> public Texture FindOrCreateTexture(MemoryManager memoryManager, RtColorState colorState, int samplesInX, int samplesInY, Size sizeHint) { bool isLinear = colorState.MemoryLayout.UnpackIsLinear(); int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY(); int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ(); Target target; if (colorState.MemoryLayout.UnpackIsTarget3D()) { target = Target.Texture3D; } else if ((samplesInX | samplesInY) != 1) { target = colorState.Depth > 1 ? Target.Texture2DMultisampleArray : Target.Texture2DMultisample; } else { target = colorState.Depth > 1 ? Target.Texture2DArray : Target.Texture2D; } FormatInfo formatInfo = colorState.Format.Convert(); int width, stride; // For linear textures, the width value is actually the stride. // We can easily get the width by dividing the stride by the bpp, // since the stride is the total number of bytes occupied by a // line. The stride should also meet alignment constraints however, // so the width we get here is the aligned width. if (isLinear) { width = colorState.WidthOrStride / formatInfo.BytesPerPixel; stride = colorState.WidthOrStride; } else { width = colorState.WidthOrStride; stride = 0; } TextureInfo info = new TextureInfo( colorState.Address.Pack(), width, colorState.Height, colorState.Depth, 1, samplesInX, samplesInY, stride, isLinear, gobBlocksInY, gobBlocksInZ, 1, target, formatInfo); int layerSize = !isLinear ? colorState.LayerSize * 4 : 0; Texture texture = FindOrCreateTexture(memoryManager, TextureSearchFlags.WithUpscale, info, layerSize, sizeHint); texture?.SynchronizeMemory(); return(texture); }