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