2019-10-18 04:41:18 +02:00
using Ryujinx.Graphics.GAL ;
2019-12-05 21:34:47 +01:00
using Ryujinx.Graphics.Gpu.State ;
2019-10-18 04:41:18 +02:00
using Ryujinx.Graphics.Shader ;
namespace Ryujinx.Graphics.Gpu.Image
{
2019-12-30 00:26:37 +01:00
/// <summary>
/// Texture bindings manager.
/// </summary>
2019-10-18 04:41:18 +02:00
class TextureBindingsManager
{
2020-05-27 16:07:10 +02:00
private const int HandleHigh = 16 ;
private const int HandleMask = ( 1 < < HandleHigh ) - 1 ;
2021-06-09 00:42:25 +02:00
private const int SlotHigh = 16 ;
private const int SlotMask = ( 1 < < SlotHigh ) - 1 ;
2019-10-18 04:41:18 +02:00
private GpuContext _context ;
private bool _isCompute ;
private SamplerPool _samplerPool ;
2019-12-05 21:34:47 +01:00
private SamplerIndex _samplerIndex ;
2019-10-18 04:41:18 +02:00
private ulong _texturePoolAddress ;
private int _texturePoolMaximumId ;
private TexturePoolCache _texturePoolCache ;
private TextureBindingInfo [ ] [ ] _textureBindings ;
private TextureBindingInfo [ ] [ ] _imageBindings ;
private struct TextureStatePerStage
{
public ITexture Texture ;
public ISampler Sampler ;
}
private TextureStatePerStage [ ] [ ] _textureState ;
private TextureStatePerStage [ ] [ ] _imageState ;
private int _textureBufferIndex ;
private bool _rebind ;
2020-11-02 20:53:23 +01:00
private float [ ] _scales ;
private bool _scaleChanged ;
2019-12-30 00:26:37 +01:00
/// <summary>
/// Constructs a new instance of the texture bindings manager.
/// </summary>
/// <param name="context">The GPU context that the texture bindings manager belongs to</param>
/// <param name="texturePoolCache">Texture pools cache used to get texture pools from</param>
/// <param name="isCompute">True if the bindings manager is used for the compute engine</param>
2019-11-22 18:17:06 +01:00
public TextureBindingsManager ( GpuContext context , TexturePoolCache texturePoolCache , bool isCompute )
2019-10-18 04:41:18 +02:00
{
2019-11-22 18:17:06 +01:00
_context = context ;
_texturePoolCache = texturePoolCache ;
_isCompute = isCompute ;
2019-10-18 04:41:18 +02:00
2020-01-01 16:39:09 +01:00
int stages = isCompute ? 1 : Constants . ShaderStages ;
2019-10-18 04:41:18 +02:00
_textureBindings = new TextureBindingInfo [ stages ] [ ] ;
_imageBindings = new TextureBindingInfo [ stages ] [ ] ;
_textureState = new TextureStatePerStage [ stages ] [ ] ;
_imageState = new TextureStatePerStage [ stages ] [ ] ;
2020-11-02 20:53:23 +01:00
_scales = new float [ 64 ] ;
for ( int i = 0 ; i < 64 ; i + + )
{
_scales [ i ] = 1f ;
}
2019-10-18 04:41:18 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Binds textures for a given shader stage.
/// </summary>
/// <param name="stage">Shader stage number, or 0 for compute shaders</param>
/// <param name="bindings">Texture bindings</param>
2019-10-18 04:41:18 +02:00
public void SetTextures ( int stage , TextureBindingInfo [ ] bindings )
{
_textureBindings [ stage ] = bindings ;
_textureState [ stage ] = new TextureStatePerStage [ bindings . Length ] ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Binds images for a given shader stage.
/// </summary>
/// <param name="stage">Shader stage number, or 0 for compute shaders</param>
/// <param name="bindings">Image bindings</param>
2019-10-18 04:41:18 +02:00
public void SetImages ( int stage , TextureBindingInfo [ ] bindings )
{
_imageBindings [ stage ] = bindings ;
_imageState [ stage ] = new TextureStatePerStage [ bindings . Length ] ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Sets the textures constant buffer index.
/// The constant buffer specified holds the texture handles.
/// </summary>
/// <param name="index">Constant buffer index</param>
2019-10-18 04:41:18 +02:00
public void SetTextureBufferIndex ( int index )
{
_textureBufferIndex = index ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Sets the current texture sampler pool to be used.
/// </summary>
/// <param name="gpuVa">Start GPU virtual address of the pool</param>
/// <param name="maximumId">Maximum ID of the pool (total count minus one)</param>
/// <param name="samplerIndex">Type of the sampler pool indexing used for bound samplers</param>
2019-12-05 21:34:47 +01:00
public void SetSamplerPool ( ulong gpuVa , int maximumId , SamplerIndex samplerIndex )
2019-10-18 04:41:18 +02:00
{
ulong address = _context . MemoryManager . Translate ( gpuVa ) ;
if ( _samplerPool ! = null )
{
2019-12-07 00:19:12 +01:00
if ( _samplerPool . Address = = address & & _samplerPool . MaximumId > = maximumId )
2019-10-18 04:41:18 +02:00
{
return ;
}
_samplerPool . Dispose ( ) ;
}
_samplerPool = new SamplerPool ( _context , address , maximumId ) ;
2019-12-05 21:34:47 +01:00
_samplerIndex = samplerIndex ;
2019-10-18 04:41:18 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Sets the current texture pool to be used.
/// </summary>
/// <param name="gpuVa">Start GPU virtual address of the pool</param>
/// <param name="maximumId">Maximum ID of the pool (total count minus one)</param>
2019-10-18 04:41:18 +02:00
public void SetTexturePool ( ulong gpuVa , int maximumId )
{
ulong address = _context . MemoryManager . Translate ( gpuVa ) ;
_texturePoolAddress = address ;
_texturePoolMaximumId = maximumId ;
}
2020-11-02 20:53:23 +01:00
/// <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 )
{
_scaleChanged | = true ;
switch ( stage )
{
case ShaderStage . Fragment :
if ( ( binding . Flags & TextureUsageFlags . ResScaleUnsupported ) ! = 0 )
{
changed | = texture . ScaleMode ! = TextureScaleMode . Blacklisted ;
texture . BlacklistScale ( ) ;
break ;
}
float scale = texture . ScaleFactor ;
TextureManager manager = _context . Methods . TextureManager ;
if ( scale ! = 1 )
{
Texture activeTarget = manager . 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 ;
}
}
_scales [ index ] = result ;
return changed ;
}
/// <summary>
/// Uploads texture and image scales to the backend when they are used.
/// </summary>
/// <param name="stage">Current shader stage</param>
/// <param name="stageIndex">Shader stage index</param>
private void CommitRenderScale ( ShaderStage stage , int stageIndex )
{
if ( _scaleChanged )
{
_context . Renderer . Pipeline . UpdateRenderScale ( stage , _scales , _textureBindings [ stageIndex ] ? . Length ? ? 0 , _imageBindings [ stageIndex ] ? . Length ? ? 0 ) ;
}
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Ensures that the bindings are visible to the host GPU.
2020-01-01 16:39:09 +01:00
/// Note: this actually performs the binding using the host graphics API.
2019-12-30 00:26:37 +01:00
/// </summary>
2019-10-18 04:41:18 +02:00
public void CommitBindings ( )
{
TexturePool texturePool = _texturePoolCache . FindOrCreate (
_texturePoolAddress ,
_texturePoolMaximumId ) ;
if ( _isCompute )
{
CommitTextureBindings ( texturePool , ShaderStage . Compute , 0 ) ;
CommitImageBindings ( texturePool , ShaderStage . Compute , 0 ) ;
2020-11-02 20:53:23 +01:00
CommitRenderScale ( ShaderStage . Compute , 0 ) ;
2019-10-18 04:41:18 +02:00
}
else
{
for ( ShaderStage stage = ShaderStage . Vertex ; stage < = ShaderStage . Fragment ; stage + + )
{
int stageIndex = ( int ) stage - 1 ;
CommitTextureBindings ( texturePool , stage , stageIndex ) ;
CommitImageBindings ( texturePool , stage , stageIndex ) ;
2020-11-02 20:53:23 +01:00
CommitRenderScale ( stage , stageIndex ) ;
2019-10-18 04:41:18 +02:00
}
}
_rebind = false ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Ensures that the texture bindings are visible to the host GPU.
2020-01-01 16:39:09 +01:00
/// Note: this actually performs the binding using the host graphics API.
2019-12-30 00:26:37 +01:00
/// </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>
2019-10-18 04:41:18 +02:00
private void CommitTextureBindings ( TexturePool pool , ShaderStage stage , int stageIndex )
{
if ( _textureBindings [ stageIndex ] = = null )
{
return ;
}
for ( int index = 0 ; index < _textureBindings [ stageIndex ] . Length ; index + + )
{
2020-11-08 12:10:00 +01:00
TextureBindingInfo bindingInfo = _textureBindings [ stageIndex ] [ index ] ;
2019-10-18 04:41:18 +02:00
2021-06-09 00:42:25 +02:00
int textureBufferIndex ;
int samplerBufferIndex ;
2019-10-18 04:41:18 +02:00
2021-06-09 00:42:25 +02:00
if ( bindingInfo . CbufSlot < 0 )
{
textureBufferIndex = _textureBufferIndex ;
samplerBufferIndex = textureBufferIndex ;
}
else
{
textureBufferIndex = bindingInfo . CbufSlot & SlotMask ;
samplerBufferIndex = ( ( bindingInfo . CbufSlot > > SlotHigh ) ! = 0 ) ? ( bindingInfo . CbufSlot > > SlotHigh ) - 1 : textureBufferIndex ;
}
int packedId = ReadPackedId ( stageIndex , bindingInfo . Handle , textureBufferIndex , samplerBufferIndex ) ;
2019-10-18 04:41:18 +02:00
int textureId = UnpackTextureId ( packedId ) ;
2019-12-05 21:34:47 +01:00
int samplerId ;
if ( _samplerIndex = = SamplerIndex . ViaHeaderIndex )
{
samplerId = textureId ;
}
else
{
samplerId = UnpackSamplerId ( packedId ) ;
}
2019-10-18 04:41:18 +02:00
Texture texture = pool . Get ( textureId ) ;
2020-11-08 12:10:00 +01:00
ITexture hostTexture = texture ? . GetTargetTexture ( bindingInfo . Target ) ;
2019-10-18 04:41:18 +02:00
if ( _textureState [ stageIndex ] [ index ] . Texture ! = hostTexture | | _rebind )
{
2020-11-08 12:10:00 +01:00
if ( UpdateScale ( texture , bindingInfo , index , stage ) )
2020-11-02 20:53:23 +01:00
{
2020-11-08 12:10:00 +01:00
hostTexture = texture ? . GetTargetTexture ( bindingInfo . Target ) ;
2020-11-02 20:53:23 +01:00
}
2019-10-18 04:41:18 +02:00
_textureState [ stageIndex ] [ index ] . Texture = hostTexture ;
2020-11-08 12:10:00 +01:00
_context . Renderer . Pipeline . SetTexture ( bindingInfo . Binding , hostTexture ) ;
2019-10-18 04:41:18 +02:00
}
2020-12-03 20:34:27 +01:00
if ( hostTexture ! = null & & texture . Target = = Target . TextureBuffer )
2020-04-25 15:02:18 +02:00
{
// 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.
2021-03-08 22:43:39 +01:00
_context . Methods . BufferManager . SetBufferTextureStorage ( hostTexture , texture . Range . GetSubRange ( 0 ) . Address , texture . Size , bindingInfo , bindingInfo . Format , false ) ;
2020-04-25 15:02:18 +02:00
}
2019-10-18 04:41:18 +02:00
Sampler sampler = _samplerPool . Get ( samplerId ) ;
ISampler hostSampler = sampler ? . HostSampler ;
if ( _textureState [ stageIndex ] [ index ] . Sampler ! = hostSampler | | _rebind )
{
_textureState [ stageIndex ] [ index ] . Sampler = hostSampler ;
2020-11-08 12:10:00 +01:00
_context . Renderer . Pipeline . SetSampler ( bindingInfo . Binding , hostSampler ) ;
2019-10-18 04:41:18 +02:00
}
}
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Ensures that the image bindings are visible to the host GPU.
2020-01-01 16:39:09 +01:00
/// Note: this actually performs the binding using the host graphics API.
2019-12-30 00:26:37 +01:00
/// </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>
2019-10-18 04:41:18 +02:00
private void CommitImageBindings ( TexturePool pool , ShaderStage stage , int stageIndex )
{
if ( _imageBindings [ stageIndex ] = = null )
{
return ;
}
2020-11-02 20:53:23 +01:00
// Scales for images appear after the texture ones.
int baseScaleIndex = _textureBindings [ stageIndex ] ? . Length ? ? 0 ;
2019-10-18 04:41:18 +02:00
for ( int index = 0 ; index < _imageBindings [ stageIndex ] . Length ; index + + )
{
2020-11-08 12:10:00 +01:00
TextureBindingInfo bindingInfo = _imageBindings [ stageIndex ] [ index ] ;
2019-10-18 04:41:18 +02:00
2021-06-09 00:42:25 +02:00
int textureBufferIndex ;
int samplerBufferIndex ;
if ( bindingInfo . CbufSlot < 0 )
{
textureBufferIndex = _textureBufferIndex ;
samplerBufferIndex = textureBufferIndex ;
}
else
{
textureBufferIndex = bindingInfo . CbufSlot & SlotMask ;
samplerBufferIndex = ( ( bindingInfo . CbufSlot > > SlotHigh ) ! = 0 ) ? ( bindingInfo . CbufSlot > > SlotHigh ) - 1 : textureBufferIndex ;
}
2020-11-09 23:35:04 +01:00
2021-06-09 00:42:25 +02:00
int packedId = ReadPackedId ( stageIndex , bindingInfo . Handle , textureBufferIndex , samplerBufferIndex ) ;
2019-10-18 04:41:18 +02:00
int textureId = UnpackTextureId ( packedId ) ;
Texture texture = pool . Get ( textureId ) ;
2020-11-08 12:10:00 +01:00
ITexture hostTexture = texture ? . GetTargetTexture ( bindingInfo . Target ) ;
2019-10-18 04:41:18 +02:00
2021-03-08 22:43:39 +01:00
bool isStore = bindingInfo . Flags . HasFlag ( TextureUsageFlags . ImageStore ) ;
2020-12-03 20:34:27 +01:00
if ( hostTexture ! = null & & texture . Target = = Target . TextureBuffer )
2020-10-20 23:56:23 +02:00
{
// 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.
2021-03-08 22:43:39 +01:00
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 ) ;
2021-05-31 21:59:23 +02:00
}
2021-03-08 22:43:39 +01:00
else if ( isStore )
{
texture ? . SignalModified ( ) ;
2020-10-20 23:56:23 +02:00
}
2019-10-18 04:41:18 +02:00
if ( _imageState [ stageIndex ] [ index ] . Texture ! = hostTexture | | _rebind )
{
2020-11-08 12:10:00 +01:00
if ( UpdateScale ( texture , bindingInfo , baseScaleIndex + index , stage ) )
2020-11-02 20:53:23 +01:00
{
2020-11-08 12:10:00 +01:00
hostTexture = texture ? . GetTargetTexture ( bindingInfo . Target ) ;
2020-11-02 20:53:23 +01:00
}
2019-10-18 04:41:18 +02:00
_imageState [ stageIndex ] [ index ] . Texture = hostTexture ;
2020-11-08 12:10:00 +01:00
Format format = bindingInfo . Format ;
2020-10-21 00:03:20 +02:00
2020-10-21 14:06:13 +02:00
if ( format = = 0 & & texture ! = null )
2020-10-21 00:03:20 +02:00
{
format = texture . Format ;
}
2020-11-08 12:10:00 +01:00
_context . Renderer . Pipeline . SetImage ( bindingInfo . Binding , hostTexture , format ) ;
2019-10-18 04:41:18 +02:00
}
}
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Gets the texture descriptor for a given texture handle.
/// </summary>
/// <param name="state">The current GPU state</param>
/// <param name="stageIndex">The stage number where the texture is bound</param>
/// <param name="handle">The texture handle</param>
2021-05-19 20:05:43 +02:00
/// <param name="cbufSlot">The texture handle's constant buffer slot</param>
2019-12-30 00:26:37 +01:00
/// <returns>The texture descriptor for the specified texture</returns>
2021-05-19 20:05:43 +02:00
public TextureDescriptor GetTextureDescriptor ( GpuState state , int stageIndex , int handle , int cbufSlot )
2019-12-16 05:59:46 +01:00
{
2021-06-09 00:42:25 +02:00
int textureBufferIndex = cbufSlot < 0 ? state . Get < int > ( MethodOffset . TextureBufferIndex ) : cbufSlot & SlotMask ;
int packedId = ReadPackedId ( stageIndex , handle , textureBufferIndex , textureBufferIndex ) ;
2019-12-16 05:59:46 +01:00
int textureId = UnpackTextureId ( packedId ) ;
var poolState = state . Get < PoolState > ( MethodOffset . TexturePoolState ) ;
ulong poolAddress = _context . MemoryManager . Translate ( poolState . Address . Pack ( ) ) ;
TexturePool texturePool = _texturePoolCache . FindOrCreate ( poolAddress , poolState . MaximumId ) ;
return texturePool . GetDescriptor ( textureId ) ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Reads a packed texture and sampler ID (basically, the real texture handle)
/// from the texture constant buffer.
/// </summary>
/// <param name="stageIndex">The number of the shader stage where the texture is bound</param>
/// <param name="wordOffset">A word offset of the handle on the buffer (the "fake" shader handle)</param>
2020-04-25 15:02:18 +02:00
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
2021-06-09 00:42:25 +02:00
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
2019-12-30 00:26:37 +01:00
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
2021-06-09 00:42:25 +02:00
private int ReadPackedId ( int stageIndex , int wordOffset , int textureBufferIndex , int samplerBufferIndex )
2019-10-18 04:41:18 +02:00
{
var bufferManager = _context . Methods . BufferManager ;
2021-06-09 00:42:25 +02:00
ulong textureBufferAddress = _isCompute
? bufferManager . GetComputeUniformBufferAddress ( textureBufferIndex )
: bufferManager . GetGraphicsUniformBufferAddress ( stageIndex , textureBufferIndex ) ;
2019-10-18 04:41:18 +02:00
2021-06-09 00:42:25 +02:00
int handle = _context . PhysicalMemory . Read < int > ( textureBufferAddress + ( ulong ) ( wordOffset & HandleMask ) * 4 ) ;
2020-05-27 16:07:10 +02:00
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
// is a 13-bit value. However, in order to also support separate samplers and textures (which uses
// bindless textures on the shader), we extend it with another value on the higher 16 bits with
// another offset for the sampler.
// The shader translator has code to detect separate texture and sampler uses with a bindless texture,
// turn that into a regular texture access and produce those special handles with values on the higher 16 bits.
if ( wordOffset > > HandleHigh ! = 0 )
{
2021-06-09 00:42:25 +02:00
ulong samplerBufferAddress = _isCompute
? bufferManager . GetComputeUniformBufferAddress ( samplerBufferIndex )
: bufferManager . GetGraphicsUniformBufferAddress ( stageIndex , samplerBufferIndex ) ;
handle | = _context . PhysicalMemory . Read < int > ( samplerBufferAddress + ( ulong ) ( ( uint ) wordOffset > > HandleHigh ) * 4 ) ;
2020-05-27 16:07:10 +02:00
}
2019-10-18 04:41:18 +02:00
2020-05-27 16:07:10 +02:00
return handle ;
2019-10-18 04:41:18 +02:00
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Unpacks the texture ID from the real texture handle.
/// </summary>
/// <param name="packedId">The real texture handle</param>
/// <returns>The texture ID</returns>
2019-10-18 04:41:18 +02:00
private static int UnpackTextureId ( int packedId )
{
return ( packedId > > 0 ) & 0xfffff ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Unpacks the sampler ID from the real texture handle.
/// </summary>
/// <param name="packedId">The real texture handle</param>
/// <returns>The sampler ID</returns>
2019-10-18 04:41:18 +02:00
private static int UnpackSamplerId ( int packedId )
{
return ( packedId > > 20 ) & 0xfff ;
}
2019-12-30 00:26:37 +01:00
/// <summary>
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
/// </summary>
2019-10-18 04:41:18 +02:00
public void Rebind ( )
{
_rebind = true ;
}
}
}