/ src / Ryujinx.Graphics.OpenGL / Image / TextureCopyIncompatible.cs
TextureCopyIncompatible.cs
  1  using OpenTK.Graphics.OpenGL;
  2  using System;
  3  using System.Collections.Generic;
  4  using System.Globalization;
  5  using System.Numerics;
  6  
  7  namespace Ryujinx.Graphics.OpenGL.Image
  8  {
  9      class TextureCopyIncompatible
 10      {
 11          private const string ComputeShaderShortening = @"#version 450 core
 12  
 13  layout (binding = 0, $SRC_FORMAT$) uniform uimage2D src;
 14  layout (binding = 1, $DST_FORMAT$) uniform uimage2D dst;
 15  
 16  layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
 17  
 18  void main()
 19  {
 20      uvec2 coords = gl_GlobalInvocationID.xy;
 21      ivec2 imageSz = imageSize(src);
 22  
 23      if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
 24      {
 25          return;
 26      }
 27  
 28      uint coordsShifted = coords.x << $RATIO_LOG2$;
 29  
 30      uvec2 dstCoords0 = uvec2(coordsShifted, coords.y);
 31      uvec2 dstCoords1 = uvec2(coordsShifted + 1, coords.y);
 32      uvec2 dstCoords2 = uvec2(coordsShifted + 2, coords.y);
 33      uvec2 dstCoords3 = uvec2(coordsShifted + 3, coords.y);
 34  
 35      uvec4 rgba = imageLoad(src, ivec2(coords));
 36  
 37      imageStore(dst, ivec2(dstCoords0), rgba.rrrr);
 38      imageStore(dst, ivec2(dstCoords1), rgba.gggg);
 39      imageStore(dst, ivec2(dstCoords2), rgba.bbbb);
 40      imageStore(dst, ivec2(dstCoords3), rgba.aaaa);
 41  }";
 42  
 43          private const string ComputeShaderWidening = @"#version 450 core
 44  
 45  layout (binding = 0, $SRC_FORMAT$) uniform uimage2D src;
 46  layout (binding = 1, $DST_FORMAT$) uniform uimage2D dst;
 47  
 48  layout (local_size_x = 32, local_size_y = 32, local_size_z = 1) in;
 49  
 50  void main()
 51  {
 52      uvec2 coords = gl_GlobalInvocationID.xy;
 53      ivec2 imageSz = imageSize(dst);
 54  
 55      if (int(coords.x) >= imageSz.x || int(coords.y) >= imageSz.y)
 56      {
 57          return;
 58      }
 59  
 60      uvec2 srcCoords = uvec2(coords.x << $RATIO_LOG2$, coords.y);
 61  
 62      uint r = imageLoad(src, ivec2(srcCoords) + ivec2(0, 0)).r;
 63      uint g = imageLoad(src, ivec2(srcCoords) + ivec2(1, 0)).r;
 64      uint b = imageLoad(src, ivec2(srcCoords) + ivec2(2, 0)).r;
 65      uint a = imageLoad(src, ivec2(srcCoords) + ivec2(3, 0)).r;
 66  
 67      imageStore(dst, ivec2(coords), uvec4(r, g, b, a));
 68  }";
 69  
 70          private readonly OpenGLRenderer _renderer;
 71          private readonly Dictionary<int, int> _shorteningProgramHandles;
 72          private readonly Dictionary<int, int> _wideningProgramHandles;
 73  
 74          public TextureCopyIncompatible(OpenGLRenderer renderer)
 75          {
 76              _renderer = renderer;
 77              _shorteningProgramHandles = new Dictionary<int, int>();
 78              _wideningProgramHandles = new Dictionary<int, int>();
 79          }
 80  
 81          public void CopyIncompatibleFormats(ITextureInfo src, ITextureInfo dst, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int depth, int levels)
 82          {
 83              int srcBpp = src.Info.BytesPerPixel;
 84              int dstBpp = dst.Info.BytesPerPixel;
 85  
 86              // Calculate ideal component size, given our constraints:
 87              // - Component size must not exceed bytes per pixel of source and destination image formats.
 88              // - Maximum component size is 4 (R32).
 89              int componentSize = Math.Min(Math.Min(srcBpp, dstBpp), 4);
 90  
 91              int srcComponentsCount = srcBpp / componentSize;
 92              int dstComponentsCount = dstBpp / componentSize;
 93  
 94              var srcFormat = GetFormat(componentSize, srcComponentsCount);
 95              var dstFormat = GetFormat(componentSize, dstComponentsCount);
 96  
 97              GL.UseProgram(srcBpp < dstBpp
 98                  ? GetWideningShader(componentSize, srcComponentsCount, dstComponentsCount)
 99                  : GetShorteningShader(componentSize, srcComponentsCount, dstComponentsCount));
100  
101              for (int l = 0; l < levels; l++)
102              {
103                  int srcWidth = Math.Max(1, src.Info.Width >> l);
104                  int srcHeight = Math.Max(1, src.Info.Height >> l);
105  
106                  int dstWidth = Math.Max(1, dst.Info.Width >> l);
107                  int dstHeight = Math.Max(1, dst.Info.Height >> l);
108  
109                  int width = Math.Min(srcWidth, dstWidth);
110                  int height = Math.Min(srcHeight, dstHeight);
111  
112                  for (int z = 0; z < depth; z++)
113                  {
114                      GL.BindImageTexture(0, src.Handle, srcLevel + l, false, srcLayer + z, TextureAccess.ReadOnly, srcFormat);
115                      GL.BindImageTexture(1, dst.Handle, dstLevel + l, false, dstLayer + z, TextureAccess.WriteOnly, dstFormat);
116  
117                      GL.DispatchCompute((width + 31) / 32, (height + 31) / 32, 1);
118                  }
119              }
120  
121              Pipeline pipeline = (Pipeline)_renderer.Pipeline;
122  
123              pipeline.RestoreProgram();
124              pipeline.RestoreImages1And2();
125          }
126  
127          private static SizedInternalFormat GetFormat(int componentSize, int componentsCount)
128          {
129              if (componentSize == 1)
130              {
131                  return componentsCount switch
132                  {
133                      1 => SizedInternalFormat.R8ui,
134                      2 => SizedInternalFormat.Rg8ui,
135                      4 => SizedInternalFormat.Rgba8ui,
136                      _ => throw new ArgumentException($"Invalid components count {componentsCount}."),
137                  };
138              }
139              else if (componentSize == 2)
140              {
141                  return componentsCount switch
142                  {
143                      1 => SizedInternalFormat.R16ui,
144                      2 => SizedInternalFormat.Rg16ui,
145                      4 => SizedInternalFormat.Rgba16ui,
146                      _ => throw new ArgumentException($"Invalid components count {componentsCount}."),
147                  };
148              }
149              else if (componentSize == 4)
150              {
151                  return componentsCount switch
152                  {
153                      1 => SizedInternalFormat.R32ui,
154                      2 => SizedInternalFormat.Rg32ui,
155                      4 => SizedInternalFormat.Rgba32ui,
156                      _ => throw new ArgumentException($"Invalid components count {componentsCount}."),
157                  };
158              }
159              else
160              {
161                  throw new ArgumentException($"Invalid component size {componentSize}.");
162              }
163          }
164  
165          private int GetShorteningShader(int componentSize, int srcComponentsCount, int dstComponentsCount)
166          {
167              return GetShader(ComputeShaderShortening, _shorteningProgramHandles, componentSize, srcComponentsCount, dstComponentsCount);
168          }
169  
170          private int GetWideningShader(int componentSize, int srcComponentsCount, int dstComponentsCount)
171          {
172              return GetShader(ComputeShaderWidening, _wideningProgramHandles, componentSize, srcComponentsCount, dstComponentsCount);
173          }
174  
175          private static int GetShader(
176              string code,
177              Dictionary<int, int> programHandles,
178              int componentSize,
179              int srcComponentsCount,
180              int dstComponentsCount)
181          {
182              int componentSizeLog2 = BitOperations.Log2((uint)componentSize);
183  
184              int srcIndex = componentSizeLog2 + BitOperations.Log2((uint)srcComponentsCount) * 3;
185              int dstIndex = componentSizeLog2 + BitOperations.Log2((uint)dstComponentsCount) * 3;
186  
187              int key = srcIndex | (dstIndex << 8);
188  
189              if (!programHandles.TryGetValue(key, out int programHandle))
190              {
191                  int csHandle = GL.CreateShader(ShaderType.ComputeShader);
192  
193                  string[] formatTable = new[] { "r8ui", "r16ui", "r32ui", "rg8ui", "rg16ui", "rg32ui", "rgba8ui", "rgba16ui", "rgba32ui" };
194  
195                  string srcFormat = formatTable[srcIndex];
196                  string dstFormat = formatTable[dstIndex];
197  
198                  int srcBpp = srcComponentsCount * componentSize;
199                  int dstBpp = dstComponentsCount * componentSize;
200  
201                  int ratio = srcBpp < dstBpp ? dstBpp / srcBpp : srcBpp / dstBpp;
202                  int ratioLog2 = BitOperations.Log2((uint)ratio);
203  
204                  GL.ShaderSource(csHandle, code
205                      .Replace("$SRC_FORMAT$", srcFormat)
206                      .Replace("$DST_FORMAT$", dstFormat)
207                      .Replace("$RATIO_LOG2$", ratioLog2.ToString(CultureInfo.InvariantCulture)));
208  
209                  GL.CompileShader(csHandle);
210  
211                  programHandle = GL.CreateProgram();
212  
213                  GL.AttachShader(programHandle, csHandle);
214                  GL.LinkProgram(programHandle);
215                  GL.DetachShader(programHandle, csHandle);
216                  GL.DeleteShader(csHandle);
217  
218                  GL.GetProgram(programHandle, GetProgramParameterName.LinkStatus, out int status);
219  
220                  if (status == 0)
221                  {
222                      throw new Exception(GL.GetProgramInfoLog(programHandle));
223                  }
224  
225                  programHandles.Add(key, programHandle);
226              }
227  
228              return programHandle;
229          }
230  
231          public void Dispose()
232          {
233              foreach (int handle in _shorteningProgramHandles.Values)
234              {
235                  GL.DeleteProgram(handle);
236              }
237  
238              _shorteningProgramHandles.Clear();
239  
240              foreach (int handle in _wideningProgramHandles.Values)
241              {
242                  GL.DeleteProgram(handle);
243              }
244  
245              _wideningProgramHandles.Clear();
246          }
247      }
248  }