/ src / video_core / rasterizer_cache / texture_codec.h
texture_codec.h
  1  // Copyright 2022 Citra Emulator Project
  2  // Licensed under GPLv2 or any later version
  3  // Refer to the license.txt file included.
  4  
  5  #pragma once
  6  
  7  #include <algorithm>
  8  #include <bit>
  9  #include <span>
 10  #include "common/alignment.h"
 11  #include "common/color.h"
 12  #include "video_core/rasterizer_cache/pixel_format.h"
 13  #include "video_core/texture/etc1.h"
 14  #include "video_core/utils.h"
 15  
 16  namespace VideoCore {
 17  
 18  template <typename T>
 19  inline T MakeInt(const u8* bytes) {
 20      T integer{};
 21      std::memcpy(&integer, bytes, sizeof(T));
 22  
 23      return integer;
 24  }
 25  
 26  template <PixelFormat format, bool converted>
 27  constexpr void DecodePixel(const u8* source, u8* dest) {
 28      using namespace Common::Color;
 29      constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
 30  
 31      if constexpr (format == PixelFormat::RGBA8 && converted) {
 32          const auto abgr = DecodeRGBA8(source);
 33          std::memcpy(dest, abgr.AsArray(), 4);
 34      } else if constexpr (format == PixelFormat::RGB8 && converted) {
 35          const auto abgr = DecodeRGB8(source);
 36          std::memcpy(dest, abgr.AsArray(), 4);
 37      } else if constexpr (format == PixelFormat::RGB565 && converted) {
 38          const auto abgr = DecodeRGB565(source);
 39          std::memcpy(dest, abgr.AsArray(), 4);
 40      } else if constexpr (format == PixelFormat::RGB5A1 && converted) {
 41          const auto abgr = DecodeRGB5A1(source);
 42          std::memcpy(dest, abgr.AsArray(), 4);
 43      } else if constexpr (format == PixelFormat::RGBA4 && converted) {
 44          const auto abgr = DecodeRGBA4(source);
 45          std::memcpy(dest, abgr.AsArray(), 4);
 46      } else if constexpr (format == PixelFormat::IA8) {
 47          const auto abgr = DecodeIA8(source);
 48          std::memcpy(dest, abgr.AsArray(), 4);
 49      } else if constexpr (format == PixelFormat::RG8) {
 50          const auto abgr = DecodeRG8(source);
 51          std::memcpy(dest, abgr.AsArray(), 4);
 52      } else if constexpr (format == PixelFormat::I8) {
 53          const auto abgr = DecodeI8(source);
 54          std::memcpy(dest, abgr.AsArray(), 4);
 55      } else if constexpr (format == PixelFormat::A8) {
 56          const auto abgr = DecodeA8(source);
 57          std::memcpy(dest, abgr.AsArray(), 4);
 58      } else if constexpr (format == PixelFormat::IA4) {
 59          const auto abgr = DecodeIA4(source);
 60          std::memcpy(dest, abgr.AsArray(), 4);
 61      } else if constexpr (format == PixelFormat::D24 && converted) {
 62          const auto d32 = DecodeD24(source) / 16777215.f;
 63          std::memcpy(dest, &d32, sizeof(d32));
 64      } else if constexpr (format == PixelFormat::D24S8) {
 65          const u32 d24s8 = std::rotl(MakeInt<u32>(source), 8);
 66          std::memcpy(dest, &d24s8, sizeof(u32));
 67      } else {
 68          std::memcpy(dest, source, bytes_per_pixel);
 69      }
 70  }
 71  
 72  template <PixelFormat format>
 73  constexpr void DecodePixel4(u32 x, u32 y, const u8* source_tile, u8* dest_pixel) {
 74      const u32 morton_offset = VideoCore::MortonInterleave(x, y);
 75      const u8 value = source_tile[morton_offset >> 1];
 76      const u8 pixel = Common::Color::Convert4To8((morton_offset % 2) ? (value >> 4) : (value & 0xF));
 77  
 78      if constexpr (format == PixelFormat::I4) {
 79          std::memset(dest_pixel, pixel, 3);
 80          dest_pixel[3] = 255;
 81      } else {
 82          std::memset(dest_pixel, 0, 3);
 83          dest_pixel[3] = pixel;
 84      }
 85  }
 86  
 87  template <PixelFormat format>
 88  constexpr void DecodePixelETC1(u32 x, u32 y, const u8* source_tile, u8* dest_pixel) {
 89      constexpr u32 subtile_width = 4;
 90      constexpr u32 subtile_height = 4;
 91      constexpr bool has_alpha = format == PixelFormat::ETC1A4;
 92      constexpr std::size_t subtile_size = has_alpha ? 16 : 8;
 93  
 94      const u32 subtile_index = (x / subtile_width) + 2 * (y / subtile_height);
 95      x %= subtile_width;
 96      y %= subtile_height;
 97  
 98      const u8* subtile_ptr = source_tile + subtile_index * subtile_size;
 99  
100      u8 alpha = 255;
101      if constexpr (has_alpha) {
102          u64_le packed_alpha;
103          std::memcpy(&packed_alpha, subtile_ptr, sizeof(u64));
104          subtile_ptr += sizeof(u64);
105  
106          alpha = Common::Color::Convert4To8((packed_alpha >> (4 * (x * subtile_width + y))) & 0xF);
107      }
108  
109      const u64_le subtile_data = MakeInt<u64_le>(subtile_ptr);
110      const auto rgb = Pica::Texture::SampleETC1Subtile(subtile_data, x, y);
111  
112      // Copy the uncompressed pixel to the destination
113      std::memcpy(dest_pixel, rgb.AsArray(), 3);
114      dest_pixel[3] = alpha;
115  }
116  
117  template <PixelFormat format, bool converted>
118  constexpr void EncodePixel(const u8* source, u8* dest) {
119      using namespace Common::Color;
120      constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
121  
122      if constexpr (format == PixelFormat::RGBA8 && converted) {
123          Common::Vec4<u8> rgba;
124          std::memcpy(rgba.AsArray(), source, 4);
125          EncodeRGBA8(rgba, dest);
126      } else if constexpr (format == PixelFormat::RGB8 && converted) {
127          Common::Vec4<u8> rgba;
128          std::memcpy(rgba.AsArray(), source, 4);
129          EncodeRGB8(rgba, dest);
130      } else if constexpr (format == PixelFormat::RGB565 && converted) {
131          Common::Vec4<u8> rgba;
132          std::memcpy(rgba.AsArray(), source, 4);
133          EncodeRGB565(rgba, dest);
134      } else if constexpr (format == PixelFormat::RGB5A1 && converted) {
135          Common::Vec4<u8> rgba;
136          std::memcpy(rgba.AsArray(), source, 4);
137          EncodeRGB5A1(rgba, dest);
138      } else if constexpr (format == PixelFormat::RGBA4 && converted) {
139          Common::Vec4<u8> rgba;
140          std::memcpy(rgba.AsArray(), source, 4);
141          EncodeRGBA4(rgba, dest);
142      } else if constexpr (format == PixelFormat::IA8) {
143          Common::Vec4<u8> rgba;
144          std::memcpy(rgba.AsArray(), source, 4);
145          EncodeIA8(rgba, dest);
146      } else if constexpr (format == PixelFormat::RG8) {
147          Common::Vec4<u8> rgba;
148          std::memcpy(rgba.AsArray(), source, 4);
149          EncodeRG8(rgba, dest);
150      } else if constexpr (format == PixelFormat::I8) {
151          Common::Vec4<u8> rgba;
152          std::memcpy(rgba.AsArray(), source, 4);
153          EncodeI8(rgba, dest);
154      } else if constexpr (format == PixelFormat::A8) {
155          Common::Vec4<u8> rgba;
156          std::memcpy(rgba.AsArray(), source, 4);
157          EncodeA8(rgba, dest);
158      } else if constexpr (format == PixelFormat::IA4) {
159          Common::Vec4<u8> rgba;
160          std::memcpy(rgba.AsArray(), source, 4);
161          EncodeIA4(rgba, dest);
162      } else if constexpr (format == PixelFormat::D24 && converted) {
163          float d32;
164          std::memcpy(&d32, source, sizeof(d32));
165          EncodeD24(static_cast<u32>(d32 * 0xFFFFFF), dest);
166      } else if constexpr (format == PixelFormat::D24S8) {
167          const u32 s8d24 = std::rotr(MakeInt<u32>(source), 8);
168          std::memcpy(dest, &s8d24, sizeof(u32));
169      } else {
170          std::memcpy(dest, source, bytes_per_pixel);
171      }
172  }
173  
174  template <PixelFormat format>
175  constexpr void EncodePixel4(u32 x, u32 y, const u8* source_pixel, u8* dest_tile_buffer) {
176      Common::Vec4<u8> rgba;
177      std::memcpy(rgba.AsArray(), source_pixel, 4);
178  
179      u8 pixel;
180      if constexpr (format == PixelFormat::I4) {
181          pixel = Common::Color::AverageRgbComponents(rgba);
182      } else {
183          pixel = rgba.a();
184      }
185  
186      const u32 morton_offset = VideoCore::MortonInterleave(x, y);
187      const u32 byte_offset = morton_offset >> 1;
188  
189      const u8 current_values = dest_tile_buffer[byte_offset];
190      const u8 new_value = Common::Color::Convert8To4(pixel);
191  
192      if (morton_offset % 2) {
193          dest_tile_buffer[byte_offset] = (new_value << 4) | (current_values & 0x0F);
194      } else {
195          dest_tile_buffer[byte_offset] = (current_values & 0xF0) | new_value;
196      }
197  }
198  
199  template <bool morton_to_linear, PixelFormat format, bool converted>
200  constexpr void MortonCopyTile(u32 stride, std::span<u8> tile_buffer, std::span<u8> linear_buffer) {
201      constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
202      constexpr u32 linear_bytes_per_pixel = converted ? 4 : GetFormatBytesPerPixel(format);
203      constexpr bool is_compressed = format == PixelFormat::ETC1 || format == PixelFormat::ETC1A4;
204      constexpr bool is_4bit = format == PixelFormat::I4 || format == PixelFormat::A4;
205  
206      for (u32 y = 0; y < 8; y++) {
207          for (u32 x = 0; x < 8; x++) {
208              const auto tiled_pixel = tile_buffer.subspan(
209                  VideoCore::MortonInterleave(x, y) * bytes_per_pixel, bytes_per_pixel);
210              const auto linear_pixel = linear_buffer.subspan(
211                  ((7 - y) * stride + x) * linear_bytes_per_pixel, linear_bytes_per_pixel);
212              if constexpr (morton_to_linear) {
213                  if constexpr (is_compressed) {
214                      DecodePixelETC1<format>(x, y, tile_buffer.data(), linear_pixel.data());
215                  } else if constexpr (is_4bit) {
216                      DecodePixel4<format>(x, y, tile_buffer.data(), linear_pixel.data());
217                  } else {
218                      DecodePixel<format, converted>(tiled_pixel.data(), linear_pixel.data());
219                  }
220              } else {
221                  if constexpr (is_4bit) {
222                      EncodePixel4<format>(x, y, linear_pixel.data(), tile_buffer.data());
223                  } else {
224                      EncodePixel<format, converted>(linear_pixel.data(), tiled_pixel.data());
225                  }
226              }
227          }
228      }
229  }
230  
231  /**
232   * @brief Performs morton to/from linear convertions on the provided pixel data
233   * @param converted If true performs RGBA8 to/from convertion to all color formats
234   * @param width, height The dimentions of the rectangular region of pixels in linear_buffer
235   * @param start_offset The number of bytes from the start of the first tile to the start of
236   * tiled_buffer
237   * @param end_offset The number of bytes from the start of the first tile to the end of tiled_buffer
238   * @param linear_buffer The linear pixel data
239   * @param tiled_buffer The tiled pixel data
240   *
241   * The MortonCopy is at the heart of the PICA texture implementation, as it's responsible for
242   * converting between linear and morton tiled layouts. The function handles both convertions but
243   * there are slightly different paths and inputs for each:
244   *
245   * Morton to Linear:
246   * During uploads, tiled_buffer is always aligned to the tile or scanline boundary depending if the
247   * linear rectangle spans multiple vertical tiles. linear_buffer does not reference the entire
248   * texture area, but rather the specific rectangle affected by the upload.
249   *
250   * Linear to Morton:
251   * This is similar to the other convertion but with some differences. In this case tiled_buffer is
252   * not required to be aligned to any specific boundary which requires special care.
253   * start_offset/end_offset are useful here as they tell us exactly where the data should be placed
254   * in the linear_buffer.
255   */
256  template <bool morton_to_linear, PixelFormat format, bool converted = false>
257  static constexpr void MortonCopy(u32 width, u32 height, u32 start_offset, u32 end_offset,
258                                   std::span<u8> linear_buffer, std::span<u8> tiled_buffer) {
259      constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
260      constexpr u32 aligned_bytes_per_pixel = converted ? 4 : GetFormatBytesPerPixel(format);
261      constexpr u32 tile_size = GetFormatBpp(format) * 64 / 8;
262      static_assert(aligned_bytes_per_pixel >= bytes_per_pixel, "");
263  
264      const u32 linear_tile_stride = (7 * width + 8) * aligned_bytes_per_pixel;
265      const u32 aligned_down_start_offset = Common::AlignDown(start_offset, tile_size);
266      const u32 aligned_start_offset = Common::AlignUp(start_offset, tile_size);
267      const u32 aligned_end_offset = Common::AlignDown(end_offset, tile_size);
268      const u32 begin_pixel_index = aligned_down_start_offset * 8 / GetFormatBpp(format);
269  
270      ASSERT(!morton_to_linear ||
271             (aligned_start_offset == start_offset && aligned_end_offset == end_offset));
272  
273      // In OpenGL the texture origin is in the bottom left corner as opposed to other
274      // APIs that have it at the top left. To avoid flipping texture coordinates in
275      // the shader we read/write the linear buffer from the bottom up
276      u32 x = (begin_pixel_index % (width * 8)) / 8;
277      u32 y = (begin_pixel_index / (width * 8)) * 8;
278      u32 linear_offset = ((height - 8 - y) * width + x) * aligned_bytes_per_pixel;
279      u32 tiled_offset = 0;
280  
281      const auto linear_next_tile = [&] {
282          x = (x + 8) % width;
283          linear_offset += 8 * aligned_bytes_per_pixel;
284          if (!x) {
285              y = (y + 8) % height;
286              if (!y) {
287                  return;
288              }
289  
290              linear_offset -= width * 9 * aligned_bytes_per_pixel;
291          }
292      };
293  
294      // If during a texture download the start coordinate is not tile aligned, swizzle
295      // the tile affected to a temporary buffer and copy the part we are interested in
296      if (start_offset < aligned_start_offset && !morton_to_linear) {
297          std::array<u8, tile_size> tmp_buf;
298          auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
299          MortonCopyTile<morton_to_linear, format, converted>(width, tmp_buf, linear_data);
300  
301          std::memcpy(tiled_buffer.data(), tmp_buf.data() + start_offset - aligned_down_start_offset,
302                      std::min(aligned_start_offset, end_offset) - start_offset);
303  
304          tiled_offset += aligned_start_offset - start_offset;
305          linear_next_tile();
306      }
307  
308      // If the copy spans multiple tiles, copy the fully aligned tiles in between.
309      if (aligned_start_offset < aligned_end_offset) {
310          const u32 tile_buffer_size = static_cast<u32>(tiled_buffer.size());
311          const u32 buffer_end =
312              std::min(tiled_offset + aligned_end_offset - aligned_start_offset, tile_buffer_size);
313          while (tiled_offset < buffer_end) {
314              auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
315              auto tiled_data = tiled_buffer.subspan(tiled_offset, tile_size);
316              MortonCopyTile<morton_to_linear, format, converted>(width, tiled_data, linear_data);
317              tiled_offset += tile_size;
318              linear_next_tile();
319          }
320      }
321  
322      // If during a texture download the end coordinate is not tile aligned, swizzle
323      // the tile affected to a temporary buffer and copy the part we are interested in
324      if (end_offset > std::max(aligned_start_offset, aligned_end_offset) && !morton_to_linear) {
325          std::array<u8, tile_size> tmp_buf;
326          auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
327          MortonCopyTile<morton_to_linear, format, converted>(width, tmp_buf, linear_data);
328          std::memcpy(tiled_buffer.data() + tiled_offset, tmp_buf.data(),
329                      end_offset - aligned_end_offset);
330      }
331  }
332  
333  /**
334   * Performs a linear copy, converting pixel formats if required.
335   * @tparam decode If true, decodes the texture if needed. Otherwise, encodes if needed.
336   * @tparam format Pixel format to copy.
337   * @tparam converted If true, converts the texture to/from the appropriate format.
338   * @param src_buffer The source pixel data
339   * @param dst_buffer The destination pixel data
340   * @return
341   */
342  template <bool decode, PixelFormat format, bool converted = false>
343  static constexpr void LinearCopy(std::span<u8> src_buffer, std::span<u8> dst_buffer) {
344      std::size_t src_size = src_buffer.size();
345      std::size_t dst_size = dst_buffer.size();
346  
347      if constexpr (converted) {
348          constexpr u32 encoded_bytes_per_pixel = GetFormatBpp(format) / 8;
349          constexpr u32 decoded_bytes_per_pixel = 4;
350          constexpr u32 src_bytes_per_pixel =
351              decode ? encoded_bytes_per_pixel : decoded_bytes_per_pixel;
352          constexpr u32 dst_bytes_per_pixel =
353              decode ? decoded_bytes_per_pixel : encoded_bytes_per_pixel;
354  
355          src_size = Common::AlignDown(src_size, src_bytes_per_pixel);
356          dst_size = Common::AlignDown(dst_size, dst_bytes_per_pixel);
357  
358          for (std::size_t src_index = 0, dst_index = 0; src_index < src_size && dst_index < dst_size;
359               src_index += src_bytes_per_pixel, dst_index += dst_bytes_per_pixel) {
360              const auto src_pixel = src_buffer.subspan(src_index, src_bytes_per_pixel);
361              const auto dst_pixel = dst_buffer.subspan(dst_index, dst_bytes_per_pixel);
362              if constexpr (decode) {
363                  DecodePixel<format, converted>(src_pixel.data(), dst_pixel.data());
364              } else {
365                  EncodePixel<format, converted>(src_pixel.data(), dst_pixel.data());
366              }
367          }
368      } else {
369          std::memcpy(dst_buffer.data(), src_buffer.data(), std::min(src_size, dst_size));
370      }
371  }
372  
373  using MortonFunc = void (*)(u32, u32, u32, u32, std::span<u8>, std::span<u8>);
374  
375  static constexpr std::array<MortonFunc, 18> UNSWIZZLE_TABLE = {
376      MortonCopy<true, PixelFormat::RGBA8>,  // 0
377      MortonCopy<true, PixelFormat::RGB8>,   // 1
378      MortonCopy<true, PixelFormat::RGB5A1>, // 2
379      MortonCopy<true, PixelFormat::RGB565>, // 3
380      MortonCopy<true, PixelFormat::RGBA4>,  // 4
381      MortonCopy<true, PixelFormat::IA8>,    // 5
382      MortonCopy<true, PixelFormat::RG8>,    // 6
383      MortonCopy<true, PixelFormat::I8>,     // 7
384      MortonCopy<true, PixelFormat::A8>,     // 8
385      MortonCopy<true, PixelFormat::IA4>,    // 9
386      MortonCopy<true, PixelFormat::I4>,     // 10
387      MortonCopy<true, PixelFormat::A4>,     // 11
388      MortonCopy<true, PixelFormat::ETC1>,   // 12
389      MortonCopy<true, PixelFormat::ETC1A4>, // 13
390      MortonCopy<true, PixelFormat::D16>,    // 14
391      nullptr,                               // 15
392      MortonCopy<true, PixelFormat::D24>,    // 16
393      MortonCopy<true, PixelFormat::D24S8>,  // 17
394  };
395  
396  static constexpr std::array<MortonFunc, 18> UNSWIZZLE_TABLE_CONVERTED = {
397      MortonCopy<true, PixelFormat::RGBA8, true>,  // 0
398      MortonCopy<true, PixelFormat::RGB8, true>,   // 1
399      MortonCopy<true, PixelFormat::RGB5A1, true>, // 2
400      MortonCopy<true, PixelFormat::RGB565, true>, // 3
401      MortonCopy<true, PixelFormat::RGBA4, true>,  // 4
402      // The following formats are implicitly converted to RGBA regardless, so ignore them.
403      nullptr,                                  // 5
404      nullptr,                                  // 6
405      nullptr,                                  // 7
406      nullptr,                                  // 8
407      nullptr,                                  // 9
408      nullptr,                                  // 10
409      nullptr,                                  // 11
410      nullptr,                                  // 12
411      nullptr,                                  // 13
412      MortonCopy<true, PixelFormat::D16, true>, // 14
413      nullptr,                                  // 15
414      MortonCopy<true, PixelFormat::D24, true>, // 16
415      // No conversion here as we need to do a special deinterleaving conversion elsewhere.
416      nullptr, // 17
417  };
418  
419  static constexpr std::array<MortonFunc, 18> SWIZZLE_TABLE = {
420      MortonCopy<false, PixelFormat::RGBA8>,  // 0
421      MortonCopy<false, PixelFormat::RGB8>,   // 1
422      MortonCopy<false, PixelFormat::RGB5A1>, // 2
423      MortonCopy<false, PixelFormat::RGB565>, // 3
424      MortonCopy<false, PixelFormat::RGBA4>,  // 4
425      MortonCopy<false, PixelFormat::IA8>,    // 5
426      MortonCopy<false, PixelFormat::RG8>,    // 6
427      MortonCopy<false, PixelFormat::I8>,     // 7
428      MortonCopy<false, PixelFormat::A8>,     // 8
429      MortonCopy<false, PixelFormat::IA4>,    // 9
430      MortonCopy<false, PixelFormat::I4>,     // 10
431      MortonCopy<false, PixelFormat::A4>,     // 11
432      nullptr,                                // 12
433      nullptr,                                // 13
434      MortonCopy<false, PixelFormat::D16>,    // 14
435      nullptr,                                // 15
436      MortonCopy<false, PixelFormat::D24>,    // 16
437      MortonCopy<false, PixelFormat::D24S8>,  // 17
438  };
439  
440  static constexpr std::array<MortonFunc, 18> SWIZZLE_TABLE_CONVERTED = {
441      MortonCopy<false, PixelFormat::RGBA8, true>,  // 0
442      MortonCopy<false, PixelFormat::RGB8, true>,   // 1
443      MortonCopy<false, PixelFormat::RGB5A1, true>, // 2
444      MortonCopy<false, PixelFormat::RGB565, true>, // 3
445      MortonCopy<false, PixelFormat::RGBA4, true>,  // 4
446      // The following formats are implicitly converted from RGBA regardless, so ignore them.
447      nullptr,                                   // 5
448      nullptr,                                   // 6
449      nullptr,                                   // 7
450      nullptr,                                   // 8
451      nullptr,                                   // 9
452      nullptr,                                   // 10
453      nullptr,                                   // 11
454      nullptr,                                   // 12
455      nullptr,                                   // 13
456      MortonCopy<false, PixelFormat::D16, true>, // 14
457      nullptr,                                   // 15
458      MortonCopy<false, PixelFormat::D24, true>, // 16
459      // No conversion here as we need to do a special interleaving conversion elsewhere.
460      nullptr, // 17
461  };
462  
463  using LinearFunc = void (*)(std::span<u8>, std::span<u8>);
464  
465  static constexpr std::array<LinearFunc, 18> LINEAR_DECODE_TABLE = {
466      LinearCopy<true, PixelFormat::RGBA8>,  // 0
467      LinearCopy<true, PixelFormat::RGB8>,   // 1
468      LinearCopy<true, PixelFormat::RGB5A1>, // 2
469      LinearCopy<true, PixelFormat::RGB565>, // 3
470      LinearCopy<true, PixelFormat::RGBA4>,  // 4
471      // These formats cannot be used linearly and can be ignored.
472      nullptr,                              // 5
473      nullptr,                              // 6
474      nullptr,                              // 7
475      nullptr,                              // 8
476      nullptr,                              // 9
477      nullptr,                              // 10
478      nullptr,                              // 11
479      nullptr,                              // 12
480      nullptr,                              // 13
481      LinearCopy<true, PixelFormat::D16>,   // 14
482      nullptr,                              // 15
483      LinearCopy<true, PixelFormat::D24>,   // 16
484      LinearCopy<true, PixelFormat::D24S8>, // 17
485  };
486  
487  static constexpr std::array<LinearFunc, 18> LINEAR_DECODE_TABLE_CONVERTED = {
488      LinearCopy<true, PixelFormat::RGBA8, true>,  // 0
489      LinearCopy<true, PixelFormat::RGB8, true>,   // 1
490      LinearCopy<true, PixelFormat::RGB5A1, true>, // 2
491      LinearCopy<true, PixelFormat::RGB565, true>, // 3
492      LinearCopy<true, PixelFormat::RGBA4, true>,  // 4
493      // These formats cannot be used linearly and can be ignored.
494      nullptr,                                  // 5
495      nullptr,                                  // 6
496      nullptr,                                  // 7
497      nullptr,                                  // 8
498      nullptr,                                  // 9
499      nullptr,                                  // 10
500      nullptr,                                  // 11
501      nullptr,                                  // 12
502      nullptr,                                  // 13
503      LinearCopy<true, PixelFormat::D16, true>, // 14
504      nullptr,                                  // 15
505      LinearCopy<true, PixelFormat::D24, true>, // 16
506      // No conversion here as we need to do a special deinterleaving conversion elsewhere.
507      nullptr, // 17
508  };
509  
510  static constexpr std::array<LinearFunc, 18> LINEAR_ENCODE_TABLE = {
511      LinearCopy<false, PixelFormat::RGBA8>,  // 0
512      LinearCopy<false, PixelFormat::RGB8>,   // 1
513      LinearCopy<false, PixelFormat::RGB5A1>, // 2
514      LinearCopy<false, PixelFormat::RGB565>, // 3
515      LinearCopy<false, PixelFormat::RGBA4>,  // 4
516      // These formats cannot be used linearly and can be ignored.
517      nullptr,                               // 5
518      nullptr,                               // 6
519      nullptr,                               // 7
520      nullptr,                               // 8
521      nullptr,                               // 9
522      nullptr,                               // 10
523      nullptr,                               // 11
524      nullptr,                               // 12
525      nullptr,                               // 13
526      LinearCopy<false, PixelFormat::D16>,   // 14
527      nullptr,                               // 15
528      LinearCopy<false, PixelFormat::D24>,   // 16
529      LinearCopy<false, PixelFormat::D24S8>, // 17
530  };
531  
532  static constexpr std::array<LinearFunc, 18> LINEAR_ENCODE_TABLE_CONVERTED = {
533      LinearCopy<false, PixelFormat::RGBA8, true>,  // 0
534      LinearCopy<false, PixelFormat::RGB8, true>,   // 1
535      LinearCopy<false, PixelFormat::RGB5A1, true>, // 2
536      LinearCopy<false, PixelFormat::RGB565, true>, // 3
537      LinearCopy<false, PixelFormat::RGBA4, true>,  // 4
538      // These formats cannot be used linearly and can be ignored.
539      nullptr,                                   // 5
540      nullptr,                                   // 6
541      nullptr,                                   // 7
542      nullptr,                                   // 8
543      nullptr,                                   // 9
544      nullptr,                                   // 10
545      nullptr,                                   // 11
546      nullptr,                                   // 12
547      nullptr,                                   // 13
548      LinearCopy<false, PixelFormat::D16, true>, // 14
549      nullptr,                                   // 15
550      LinearCopy<false, PixelFormat::D24, true>, // 16
551      // No conversion here as we need to do a special interleaving conversion elsewhere.
552      nullptr, // 17
553  };
554  
555  } // namespace VideoCore