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 TextureBindingsArrayCache _bindingsArrayCache;
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);
_bindingsArrayCache = new TextureBindingsArrayCache(context, channel);
_cpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, texturePoolCache, samplerPoolCache, isCompute: true);
_gpBindingsManager = new TextureBindingsManager(context, channel, _bindingsArrayCache, 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, _bindingsArrayCache);
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;
}
}
}