/// <summary> /// Updates the texture scale for a given texture or image. /// </summary> /// <param name="texture">Start GPU virtual address of the pool</param> /// <param name="binding">The related texture binding</param> /// <param name="index">The texture/image binding index</param> /// <param name="stage">The active shader stage</param> /// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns> private bool UpdateScale(Texture texture, TextureBindingInfo binding, int index, ShaderStage stage) { float result = 1f; bool changed = false; if ((binding.Flags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null) { switch (stage) { case ShaderStage.Fragment: if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0) { changed |= texture.ScaleMode != TextureScaleMode.Blacklisted; texture.BlacklistScale(); break; } float scale = texture.ScaleFactor; if (scale != 1) { Texture activeTarget = _channel.TextureManager.GetAnyRenderTarget(); if (activeTarget != null && activeTarget.Info.Width / (float)texture.Info.Width == activeTarget.Info.Height / (float)texture.Info.Height) { // If the texture's size is a multiple of the sampler size, enable interpolation using gl_FragCoord. (helps "invent" new integer values between scaled pixels) result = -scale; break; } } result = scale; break; case ShaderStage.Compute: if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0) { changed |= texture.ScaleMode != TextureScaleMode.Blacklisted; texture.BlacklistScale(); } result = texture.ScaleFactor; break; } } if (result != _scales[index]) { _scaleChanged = true; _scales[index] = result; } return(changed); }
/// <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; } for (int index = 0; index < _imageBindings[stageIndex].Length; index++) { TextureBindingInfo binding = _imageBindings[stageIndex][index]; int packedId = ReadPackedId(stageIndex, binding.Handle, _textureBufferIndex); int textureId = UnpackTextureId(packedId); Texture texture = pool.Get(textureId); if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0) { texture?.BlacklistScale(); } ITexture hostTexture = texture?.GetTargetTexture(binding.Target); if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) { _imageState[stageIndex][index].Texture = hostTexture; _context.Renderer.Pipeline.SetImage(index, stage, hostTexture); } } }
/// <summary> /// Propagates the scale between this texture and another to ensure they have the same scale. /// If one texture is blacklisted from scaling, the other will become blacklisted too. /// </summary> /// <param name="other">The other texture</param> public void PropagateScale(Texture other) { if (other.ScaleMode == TextureScaleMode.Blacklisted || ScaleMode == TextureScaleMode.Blacklisted) { BlacklistScale(); other.BlacklistScale(); } else { // Prefer the configured scale if present. If not, prefer the max. float targetScale = GraphicsConfig.ResScale; float sharedScale = (ScaleFactor == targetScale || other.ScaleFactor == targetScale) ? targetScale : Math.Max(ScaleFactor, other.ScaleFactor); SetScale(sharedScale); other.SetScale(sharedScale); } }
/// <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; } for (int index = 0; index < _imageBindings[stageIndex].Length; index++) { TextureBindingInfo binding = _imageBindings[stageIndex][index]; int packedId = ReadPackedId(stageIndex, binding.Handle, _textureBufferIndex); int textureId = UnpackTextureId(packedId); Texture texture = pool.Get(textureId); if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0) { texture?.BlacklistScale(); } ITexture hostTexture = texture?.GetTargetTexture(binding.Target); if (hostTexture != null && texture.Info.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.Address, texture.Size, _isCompute); } if (_imageState[stageIndex][index].Texture != hostTexture || _rebind) { _imageState[stageIndex][index].Texture = hostTexture; Format format = binding.Format; if (format == 0) { format = texture.Format; } _context.Renderer.Pipeline.SetImage(index, stage, hostTexture, format); } } }
/// <summary> /// Updates the Render Target scale, given the currently bound render targets. /// This will update scale to match the configured scale, scale textures that are eligible but not scaled, /// and propagate blacklisted status from one texture to the ones bound with it. /// </summary> /// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param> public void UpdateRenderTargetScale(int singleUse) { // Make sure all scales for render targets are at the highest they should be. Blacklisted targets should propagate their scale to the other targets. bool mismatch = false; bool blacklisted = false; bool hasUpscaled = false; bool hasUndesired = false; float targetScale = GraphicsConfig.ResScale; void ConsiderTarget(Texture target) { if (target == null) { return; } float scale = target.ScaleFactor; switch (target.ScaleMode) { case TextureScaleMode.Blacklisted: mismatch |= scale != 1f; blacklisted = true; break; case TextureScaleMode.Eligible: mismatch = true; // We must make a decision. break; case TextureScaleMode.Undesired: hasUndesired = true; mismatch |= scale != 1f || hasUpscaled; // If another target is upscaled, scale this one up too. break; case TextureScaleMode.Scaled: hasUpscaled = true; mismatch |= hasUndesired || scale != targetScale; // If the target scale has changed, reset the scale for all targets. break; } } if (singleUse != -1) { // If only one target is in use (by a clear, for example) the others do not need to be checked for mismatching scale. ConsiderTarget(_rtColors[singleUse]); } else { foreach (Texture color in _rtColors) { ConsiderTarget(color); } } ConsiderTarget(_rtDepthStencil); mismatch |= blacklisted && hasUpscaled; if (blacklisted || (hasUndesired && !hasUpscaled)) { targetScale = 1f; } if (mismatch) { if (blacklisted) { // Propagate the blacklisted state to the other textures. foreach (Texture color in _rtColors) { color?.BlacklistScale(); } _rtDepthStencil?.BlacklistScale(); } else { // Set the scale of the other textures. foreach (Texture color in _rtColors) { color?.SetScale(targetScale); } _rtDepthStencil?.SetScale(targetScale); } } RenderTargetScale = targetScale; }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="info">Texture information of the texture to be found or created</param> /// <param name="flags">The texture search flags, defines texture comparison rules</param> /// <returns>The texture</returns> public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None) { 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; } int sameAddressOverlapsCount; lock (_textures) { // Try to find a perfect texture match, with the same address and parameters. sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps); } for (int index = 0; index < sameAddressOverlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (overlap.IsPerfectMatch(info, flags)) { 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(overlap); } else if (!TextureCompatibility.SizeMatches(overlap.Info, info)) { // If this is used for sampling, the size must match, // otherwise the shader would sample garbage data. // To fix that, we create a new texture with the correct // size, and copy the data from the old one to the new one. overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers); } overlap.SynchronizeMemory(); return(overlap); } } // Calculate texture sizes, used to find all overlapping textures. SizeInfo sizeInfo; if (info.Target == Target.TextureBuffer) { sizeInfo = new SizeInfo(info.Width * info.FormatInfo.BytesPerPixel); } else if (info.IsLinear) { sizeInfo = SizeCalculator.GetLinearTextureSize( info.Stride, info.Height, info.FormatInfo.BlockHeight); } else { sizeInfo = SizeCalculator.GetBlockLinearTextureSize( info.Width, info.Height, info.GetDepth(), info.Levels, info.GetLayers(), info.FormatInfo.BlockWidth, info.FormatInfo.BlockHeight, info.FormatInfo.BytesPerPixel, info.GobBlocksInY, info.GobBlocksInZ, info.GobBlocksInTileX); } // Find view compatible matches. ulong size = (ulong)sizeInfo.TotalSize; int overlapsCount; lock (_textures) { overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps); } Texture texture = null; for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel) == TextureViewCompatibility.Full) { if (!isSamplerTexture) { info = AdjustSizes(overlap, info, firstLevel); } texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel); if (IsTextureModified(overlap)) { texture.SignalModified(); } // The size only matters (and is only really reliable) when the // texture is used on a sampler, because otherwise the size will be // aligned. if (!TextureCompatibility.SizeMatches(overlap.Info, info, firstLevel) && isSamplerTexture) { texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers); } break; } } // No match, create a new texture. if (texture == null) { texture = new Texture(_context, info, sizeInfo, 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; bool setData = isSamplerTexture || overlapsCount == 0; for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; bool overlapInCache = overlap.CacheNode != null; TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel); if (compatibility != TextureViewCompatibility.Incompatible) { if (_overlapInfo.Length != _textureOverlaps.Length) { Array.Resize(ref _overlapInfo, _textureOverlaps.Length); } _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[viewCompatible++] = overlap; } 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 && (overlap.Address <texture.Address || overlap.EndAddress> texture.EndAddress) && overlap.HasViewCompatibleChild(texture); setData |= modified || flush; if (overlapInCache) { _cache.Remove(overlap, flush); } } } // 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 (oInfo.Compatibility != TextureViewCompatibility.Full) { continue; // Copy only compatibilty. } TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities); 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); } ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); overlap.HostTexture.CopyTo(newView, 0, 0); // Inherit modification from overlapping texture, do that before replacing // the view since the replacement operation removes it from the list. if (IsTextureModified(overlap)) { texture.SignalModified(); } overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); } // If the texture is a 3D texture, we need to additionally copy any slice // of the 3D texture to the newly created 3D texture. if (info.Target == Target.Texture3D) { for (int index = 0; index < viewCompatible; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (oInfo.Compatibility != TextureViewCompatibility.Incompatible) { overlap.BlacklistScale(); overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel); if (IsTextureModified(overlap)) { texture.SignalModified(); } } } } } // Sampler textures are managed by the texture pool, all other textures // are managed by the auto delete cache. if (!isSamplerTexture) { _cache.Add(texture); texture.Modified += CacheTextureModified; texture.Disposed += CacheTextureDisposed; } lock (_textures) { _textures.Add(texture); } ShrinkOverlapsBufferIfNeeded(); return(texture); }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <param name="info">Texture information of the texture to be found or created</param> /// <param name="flags">The texture search flags, defines texture comparison rules</param> /// <returns>The texture</returns> public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None) { bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 0; bool isScalable = IsUpscaleCompatible(info); TextureScaleMode scaleMode = TextureScaleMode.Blacklisted; if (isScalable) { scaleMode = (flags & TextureSearchFlags.WithUpscale) != 0 ? TextureScaleMode.Scaled : TextureScaleMode.Eligible; } // Try to find a perfect texture match, with the same address and parameters. int sameAddressOverlapsCount = _textures.FindOverlaps(info.Address, ref _textureOverlaps); for (int index = 0; index < sameAddressOverlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (overlap.IsPerfectMatch(info, flags)) { 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(overlap); } else if (!overlap.SizeMatches(info)) { // If this is used for sampling, the size must match, // otherwise the shader would sample garbage data. // To fix that, we create a new texture with the correct // size, and copy the data from the old one to the new one. overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers); } overlap.SynchronizeMemory(); return(overlap); } } // Calculate texture sizes, used to find all overlapping textures. SizeInfo sizeInfo; if (info.Target == Target.TextureBuffer) { sizeInfo = new SizeInfo(info.Width * info.FormatInfo.BytesPerPixel); } else if (info.IsLinear) { sizeInfo = SizeCalculator.GetLinearTextureSize( info.Stride, info.Height, info.FormatInfo.BlockHeight); } else { sizeInfo = SizeCalculator.GetBlockLinearTextureSize( info.Width, info.Height, info.GetDepth(), info.Levels, info.GetLayers(), info.FormatInfo.BlockWidth, info.FormatInfo.BlockHeight, info.FormatInfo.BytesPerPixel, info.GobBlocksInY, info.GobBlocksInZ, info.GobBlocksInTileX); } // Find view compatible matches. ulong size = (ulong)sizeInfo.TotalSize; int overlapsCount = _textures.FindOverlaps(info.Address, size, ref _textureOverlaps); Texture texture = null; for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel)) { if (!isSamplerTexture) { info = AdjustSizes(overlap, info, firstLevel); } texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel); if (IsTextureModified(overlap)) { CacheTextureModified(texture); } // The size only matters (and is only really reliable) when the // texture is used on a sampler, because otherwise the size will be // aligned. if (!overlap.SizeMatches(info, firstLevel) && isSamplerTexture) { texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers); } break; } } // No match, create a new texture. if (texture == null) { texture = new Texture(_context, info, sizeInfo, scaleMode); // 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.SynchronizeMemory(); for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel)) { TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel); TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities); 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); } ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel); overlap.HostTexture.CopyTo(newView, 0, 0); // Inherit modification from overlapping texture, do that before replacing // the view since the replacement operation removes it from the list. if (IsTextureModified(overlap)) { CacheTextureModified(texture); } overlap.ReplaceView(texture, overlapInfo, newView, firstLayer, firstLevel); } } // If the texture is a 3D texture, we need to additionally copy any slice // of the 3D texture to the newly created 3D texture. if (info.Target == Target.Texture3D) { for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; if (texture.IsViewCompatible( overlap.Info, overlap.Size, isCopy: true, out int firstLayer, out int firstLevel)) { overlap.BlacklistScale(); overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel); if (IsTextureModified(overlap)) { CacheTextureModified(texture); } } } } } // Sampler textures are managed by the texture pool, all other textures // are managed by the auto delete cache. if (!isSamplerTexture) { _cache.Add(texture); texture.Modified += CacheTextureModified; texture.Disposed += CacheTextureDisposed; } _textures.Add(texture); ShrinkOverlapsBufferIfNeeded(); return(texture); }
/// <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; } bool changed = false; for (int index = 0; index < _textureBindings[stageIndex].Length; index++) { TextureBindingInfo binding = _textureBindings[stageIndex][index]; int packedId; if (binding.IsBindless) { ulong address; var bufferManager = _context.Methods.BufferManager; if (_isCompute) { address = bufferManager.GetComputeUniformBufferAddress(binding.CbufSlot); } else { address = bufferManager.GetGraphicsUniformBufferAddress(stageIndex, binding.CbufSlot); } packedId = _context.PhysicalMemory.Read <int>(address + (ulong)binding.CbufOffset * 4); } else { packedId = ReadPackedId(stageIndex, binding.Handle, _textureBufferIndex); } int textureId = UnpackTextureId(packedId); int samplerId; if (_samplerIndex == SamplerIndex.ViaHeaderIndex) { samplerId = textureId; } else { samplerId = UnpackSamplerId(packedId); } Texture texture = pool.Get(textureId); if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0) { texture?.BlacklistScale(); } ITexture hostTexture = texture?.GetTargetTexture(binding.Target); if (_textureState[stageIndex][index].Texture != hostTexture || _rebind) { _textureState[stageIndex][index].Texture = hostTexture; _context.Renderer.Pipeline.SetTexture(index, stage, hostTexture); changed = true; } if (hostTexture != null && texture.Info.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.Address, texture.Size, _isCompute); } 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(index, stage, hostSampler); } } if (changed) { _context.Renderer.Pipeline.UpdateRenderScale(stage, _textureBindings[stageIndex].Length); } }
/// <summary> /// Tries to find an existing texture, or create a new one if not found. /// </summary> /// <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(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 = _context.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]; bool rangeMatches = range != null?overlap.Range.Equals(range.Value) : overlap.Info.GpuAddress == info.GpuAddress; if (!rangeMatches) { continue; } TextureMatchQuality matchQuality = overlap.IsExactMatch(info, flags); 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 = _context.MemoryManager.GetPhysicalRegions(info.GpuAddress, size); } // Find view compatible matches. int overlapsCount; lock (_textures) { overlapsCount = _textures.FindOverlaps(range.Value, ref _textureOverlaps); } for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; TextureViewCompatibility overlapCompatibility = overlap.IsViewCompatible(info, range.Value, out int firstLayer, out int firstLevel); if (overlapCompatibility == TextureViewCompatibility.Full) { TextureInfo oInfo = AdjustSizes(overlap, info, firstLevel); if (!isSamplerTexture) { info = oInfo; } texture = overlap.CreateView(oInfo, sizeInfo, range.Value, firstLayer, firstLevel); if (overlap.IsModified) { texture.SignalModified(); } ChangeSizeIfNeeded(info, texture, isSamplerTexture, sizeHint); break; } else if (overlapCompatibility == TextureViewCompatibility.CopyOnly) { // TODO: Copy rules for targets created after the container texture. See below. overlap.DisableMemoryTracking(); } } // No match, create a new texture. if (texture == null) { texture = new Texture(_context, 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; bool setData = isSamplerTexture || overlapsCount == 0 || flags.HasFlag(TextureSearchFlags.ForCopy); for (int index = 0; index < overlapsCount; index++) { Texture overlap = _textureOverlaps[index]; bool overlapInCache = overlap.CacheNode != null; TextureViewCompatibility compatibility = texture.IsViewCompatible(overlap.Info, overlap.Range, out int firstLayer, out int firstLevel); if (compatibility != TextureViewCompatibility.Incompatible) { if (_overlapInfo.Length != _textureOverlaps.Length) { Array.Resize(ref _overlapInfo, _textureOverlaps.Length); } _overlapInfo[viewCompatible] = new OverlapInfo(compatibility, firstLayer, firstLevel); _textureOverlaps[viewCompatible++] = overlap; } 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); } } } // 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 (oInfo.Compatibility != TextureViewCompatibility.Full) { continue; // Copy only compatibilty. } TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, oInfo.FirstLevel); TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities, overlap.ScaleFactor); 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); } ITexture newView = texture.HostTexture.CreateView(createInfo, oInfo.FirstLayer, oInfo.FirstLevel); overlap.HostTexture.CopyTo(newView, 0, 0); // Inherit modification from overlapping texture, do that before replacing // the view since the replacement operation removes it from the list. if (overlap.IsModified) { texture.SignalModified(); } overlap.ReplaceView(texture, overlapInfo, newView, oInfo.FirstLayer, oInfo.FirstLevel); } // If the texture is a 3D texture, we need to additionally copy any slice // of the 3D texture to the newly created 3D texture. if (info.Target == Target.Texture3D && viewCompatible > 0) { // TODO: This copy can currently only happen when the 3D texture is created. // If a game clears and redraws the slices, we won't be able to copy the new data to the 3D texture. // Disable tracking to try keep at least the original data in there for as long as possible. texture.DisableMemoryTracking(); for (int index = 0; index < viewCompatible; index++) { Texture overlap = _textureOverlaps[index]; OverlapInfo oInfo = _overlapInfo[index]; if (oInfo.Compatibility != TextureViewCompatibility.Incompatible) { overlap.BlacklistScale(); overlap.HostTexture.CopyTo(texture.HostTexture, oInfo.FirstLayer, oInfo.FirstLevel); if (overlap.IsModified) { texture.SignalModified(); } } } } } // 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); }