using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.Types; using Ryujinx.Graphics.Gpu.Shader; using System; namespace Ryujinx.Graphics.Gpu.Image { /// /// Texture manager. /// class TextureManager : IDisposable { private readonly GpuContext _context; private readonly GpuChannel _channel; private readonly TextureBindingsManager _cpBindingsManager; private readonly TextureBindingsManager _gpBindingsManager; private readonly TexturePoolCache _texturePoolCache; private readonly SamplerPoolCache _samplerPoolCache; private readonly Texture[] _rtColors; private readonly ITexture[] _rtHostColors; private readonly bool[] _rtColorsBound; private Texture _rtDepthStencil; private ITexture _rtHostDs; private bool _rtDsBound; public int ClipRegionWidth { get; private set; } public int ClipRegionHeight { get; private set; } /// /// The scaling factor applied to all currently bound render targets. /// public float RenderTargetScale { get; private set; } = 1f; /// /// Creates a new instance of the texture manager. /// /// GPU context that the texture manager belongs to /// GPU channel that the texture manager belongs to public TextureManager(GpuContext context, GpuChannel channel) { _context = context; _channel = channel; TexturePoolCache texturePoolCache = new(context); SamplerPoolCache samplerPoolCache = new(context); _cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: true); _gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, isCompute: false); _texturePoolCache = texturePoolCache; _samplerPoolCache = samplerPoolCache; _rtColors = new Texture[Constants.TotalRenderTargets]; _rtHostColors = new ITexture[Constants.TotalRenderTargets]; _rtColorsBound = new bool[Constants.TotalRenderTargets]; } /// /// Sets the texture and image bindings for the compute pipeline. /// /// Bindings for the active shader public void SetComputeBindings(CachedShaderBindings bindings) { _cpBindingsManager.SetBindings(bindings); } /// /// Sets the texture and image bindings for the graphics pipeline. /// /// Bindings for the active shader public void SetGraphicsBindings(CachedShaderBindings bindings) { _gpBindingsManager.SetBindings(bindings); } /// /// Sets the texture constant buffer index on the compute pipeline. /// /// The texture constant buffer index public void SetComputeTextureBufferIndex(int index) { _cpBindingsManager.SetTextureBufferIndex(index); } /// /// Sets the texture constant buffer index on the graphics pipeline. /// /// The texture constant buffer index public void SetGraphicsTextureBufferIndex(int index) { _gpBindingsManager.SetTextureBufferIndex(index); } /// /// Sets the current sampler pool on the compute pipeline. /// /// The start GPU virtual address of the sampler pool /// The maximum ID of the sampler pool /// The indexing type of the sampler pool public void SetComputeSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex) { _cpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex); } /// /// Sets the current sampler pool on the graphics pipeline. /// /// The start GPU virtual address of the sampler pool /// The maximum ID of the sampler pool /// The indexing type of the sampler pool public void SetGraphicsSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex) { _gpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex); } /// /// Sets the current texture pool on the compute pipeline. /// /// The start GPU virtual address of the texture pool /// The maximum ID of the texture pool public void SetComputeTexturePool(ulong gpuVa, int maximumId) { _cpBindingsManager.SetTexturePool(gpuVa, maximumId); } /// /// Sets the current texture pool on the graphics pipeline. /// /// The start GPU virtual address of the texture pool /// The maximum ID of the texture pool public void SetGraphicsTexturePool(ulong gpuVa, int maximumId) { _gpBindingsManager.SetTexturePool(gpuVa, maximumId); } /// /// Check if a texture's scale must be updated to match the configured resolution scale. /// /// The texture to check /// True if the scale needs updating, false if the scale is up to date private static bool ScaleNeedsUpdated(Texture texture) { return texture != null && !(texture.ScaleMode == TextureScaleMode.Blacklisted || texture.ScaleMode == TextureScaleMode.Undesired) && texture.ScaleFactor != GraphicsConfig.ResScale; } /// /// Sets the render target color buffer. /// /// The index of the color buffer to set (up to 8) /// The color buffer texture /// True if render target scale must be updated. public bool SetRenderTargetColor(int index, Texture color) { bool hasValue = color != null; bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor); if (_rtColors[index] != color) { if (_rtColorsBound[index]) { _rtColors[index]?.SignalModifying(false); } else { _rtColorsBound[index] = true; } if (color != null) { color.SynchronizeMemory(); color.SignalModifying(true); } _rtColors[index] = color; } return changesScale || ScaleNeedsUpdated(color); } /// /// Sets the render target depth-stencil buffer. /// /// The depth-stencil buffer texture /// True if render target scale must be updated. public bool SetRenderTargetDepthStencil(Texture depthStencil) { bool hasValue = depthStencil != null; bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor); if (_rtDepthStencil != depthStencil) { if (_rtDsBound) { _rtDepthStencil?.SignalModifying(false); } else { _rtDsBound = true; } if (depthStencil != null) { depthStencil.SynchronizeMemory(); depthStencil.SignalModifying(true); } _rtDepthStencil = depthStencil; } return changesScale || ScaleNeedsUpdated(depthStencil); } /// /// Sets the host clip region, which should be the intersection of all render target texture sizes. /// /// Width of the clip region, defined as the minimum width across all bound textures /// Height of the clip region, defined as the minimum height across all bound textures public void SetClipRegion(int width, int height) { ClipRegionWidth = width; ClipRegionHeight = height; } /// /// Gets the first available bound colour target, or the depth stencil target if not present. /// /// The first bound colour target, otherwise the depth stencil target public Texture GetAnyRenderTarget() { return _rtColors[0] ?? _rtDepthStencil; } /// /// 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. /// /// If this is not -1, it indicates that only the given indexed target will be used. 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; } /// /// Gets a texture and a sampler from their respective pools from a texture ID and a sampler ID. /// /// ID of the texture /// ID of the sampler public (Texture, Sampler) GetGraphicsTextureAndSampler(int textureId, int samplerId) { return _gpBindingsManager.GetTextureAndSampler(textureId, samplerId); } /// /// Commits bindings on the compute pipeline. /// /// Specialization state for the bound shader /// True if all bound textures match the current shader specialization state, false otherwise public bool CommitComputeBindings(ShaderSpecializationState specState) { // Every time we switch between graphics and compute work, // we must rebind everything. // Since compute work happens less often, we always do that // before and after the compute dispatch. _texturePoolCache.Tick(); _samplerPoolCache.Tick(); _cpBindingsManager.Rebind(); bool result = _cpBindingsManager.CommitBindings(specState); _gpBindingsManager.Rebind(); return result; } /// /// Commits bindings on the graphics pipeline. /// /// Specialization state for the bound shader /// True if there is a scale mismatch in the render targets, indicating they must be re-evaluated /// True if all bound textures match the current shader specialization state, false otherwise public bool CommitGraphicsBindings(ShaderSpecializationState specState, out bool scaleMismatch) { _texturePoolCache.Tick(); _samplerPoolCache.Tick(); bool result = _gpBindingsManager.CommitBindings(specState); scaleMismatch = UpdateRenderTargets(); return result; } /// /// Returns a texture pool from the cache, with the given address and maximum id. /// /// GPU virtual address of the texture pool /// Maximum ID of the texture pool /// The texture pool public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId) { ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa); TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId); return texturePool; } /// /// Gets a texture descriptor used on the compute pipeline. /// /// GPU virtual address of the texture pool /// Index of the constant buffer with texture handles /// Maximum ID of the texture pool /// Shader "fake" handle of the texture /// Shader constant buffer slot of the texture /// The texture descriptor public TextureDescriptor GetComputeTextureDescriptor(ulong poolGpuVa, int bufferIndex, int maximumId, int handle, int cbufSlot) { return _cpBindingsManager.GetTextureDescriptor(poolGpuVa, bufferIndex, maximumId, 0, handle, cbufSlot); } /// /// Gets a texture descriptor used on the graphics pipeline. /// /// GPU virtual address of the texture pool /// Index of the constant buffer with texture handles /// Maximum ID of the texture pool /// Index of the shader stage where the texture is bound /// Shader "fake" handle of the texture /// Shader constant buffer slot of the texture /// The texture descriptor public TextureDescriptor GetGraphicsTextureDescriptor( ulong poolGpuVa, int bufferIndex, int maximumId, int stageIndex, int handle, int cbufSlot) { return _gpBindingsManager.GetTextureDescriptor(poolGpuVa, bufferIndex, maximumId, stageIndex, handle, cbufSlot); } /// /// Update host framebuffer attachments based on currently bound render target buffers. /// /// True if there is a scale mismatch in the render targets, indicating they must be re-evaluated public bool UpdateRenderTargets() { bool anyChanged = false; float expectedScale = RenderTargetScale; bool scaleMismatch = false; Texture dsTexture = _rtDepthStencil; ITexture hostDsTexture = null; if (dsTexture != null) { hostDsTexture = dsTexture.HostTexture; if (!_rtDsBound) { dsTexture.SignalModifying(true); _rtDsBound = true; } } if (_rtHostDs != hostDsTexture) { _rtHostDs = hostDsTexture; anyChanged = true; if (dsTexture != null && dsTexture.ScaleFactor != expectedScale) { scaleMismatch = true; } } for (int index = 0; index < _rtColors.Length; index++) { Texture texture = _rtColors[index]; ITexture hostTexture = null; if (texture != null) { hostTexture = texture.HostTexture; if (!_rtColorsBound[index]) { texture.SignalModifying(true); _rtColorsBound[index] = true; } } if (_rtHostColors[index] != hostTexture) { _rtHostColors[index] = hostTexture; anyChanged = true; if (texture != null && texture.ScaleFactor != expectedScale) { scaleMismatch = true; } } } if (anyChanged) { _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs); } return scaleMismatch; } /// /// Update host framebuffer attachments based on currently bound render target buffers. /// /// /// All color attachments will be unbound. /// public void UpdateRenderTargetDepthStencil() { new Span(_rtHostColors).Clear(); _rtHostDs = _rtDepthStencil?.HostTexture; _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs); } /// /// Marks all currently bound render target textures as modified, and also makes them be set as modified again on next use. /// public void RefreshModifiedTextures() { Texture dsTexture = _rtDepthStencil; if (dsTexture != null && _rtDsBound) { dsTexture.SignalModifying(false); _rtDsBound = false; } for (int index = 0; index < _rtColors.Length; index++) { Texture texture = _rtColors[index]; if (texture != null && _rtColorsBound[index]) { texture.SignalModifying(false); _rtColorsBound[index] = false; } } } /// /// Forces the texture and sampler pools to be re-loaded from the cache on next use. /// public void ReloadPools() { _cpBindingsManager.ReloadPools(); _gpBindingsManager.ReloadPools(); } /// /// Forces all textures, samplers, images and render targets to be rebound the next time /// CommitGraphicsBindings is called. /// public void Rebind() { _gpBindingsManager.Rebind(); for (int index = 0; index < _rtHostColors.Length; index++) { _rtHostColors[index] = null; } _rtHostDs = null; } /// /// Disposes the texture manager. /// It's an error to use the texture manager after disposal. /// public void Dispose() { // Textures are owned by the texture cache, so we shouldn't dispose the texture pool cache. _samplerPoolCache.Dispose(); for (int i = 0; i < _rtColors.Length; i++) { if (_rtColorsBound[i]) { _rtColors[i]?.DecrementReferenceCount(); } _rtColors[i] = null; } if (_rtDsBound) { _rtDepthStencil?.DecrementReferenceCount(); } _rtDepthStencil = null; } } }