【ARMv8 SIMD和浮点指令编程】Libyuv I420 转 ARGB 流程分析

news2024/9/23 23:34:49

Libyuv 可以说是做图形图像相关从业者绕不开的一个常用库,它使用了单指令多数据流提升性能。以 ARM 处理为主线,通过 I420 转 ARGB 流程来分析它是如何流转的。

Libyuv 是一个开源项目,包括 YUV 的缩放和转换功能。

  • 使用邻近、双线性或 box 插值缩放 YUV。
  • 将网络摄像头格式转化为 YUV。
  • 转换为 RGB 格式的渲染或效果。
  • 旋转 90、180 或 270 度。
  • 针对 x86/x64 上的 SSSE3/AVX2 进行优化。
  • 针对 Arm 上的 NEON 优化。
  • 针对 Mips 上的 MSA 优化。

官方地址 https://chromium.googlesource.com/libyuv/libyuv。

libyuv.h 是调用 Libyuv API 的入口。I420ToARGB 这个转换函数位于 libyuv/convert_argb.h 头文件内。

libyuv.h

#ifndef INCLUDE_LIBYUV_H_
#define INCLUDE_LIBYUV_H_

#include "libyuv/basic_types.h"
#include "libyuv/compare.h"
#include "libyuv/convert.h"
#include "libyuv/convert_argb.h"
#include "libyuv/convert_from.h"
#include "libyuv/convert_from_argb.h"
#include "libyuv/cpu_id.h"
#include "libyuv/mjpeg_decoder.h"
#include "libyuv/planar_functions.h"
#include "libyuv/rotate.h"
#include "libyuv/rotate_argb.h"
#include "libyuv/row.h"
#include "libyuv/scale.h"
#include "libyuv/scale_argb.h"
#include "libyuv/scale_row.h"
#include "libyuv/scale_uv.h"
#include "libyuv/version.h"
#include "libyuv/video_common.h"

#endif  // INCLUDE_LIBYUV_H_

LIBYUV_API 是导出 API 使用的修饰符,ARM 平台上无作用。I420ToARGB 函数入参共有 10 个。

src_y —— YUV Y 分量
src_stride_y —— Y 分量步幅,也就是多少个 Y 换一行
src_u —— YUV U 分量
src_stride_u —— U 分量步幅,由于 I420 上下两行附近 4 个 Y 共用一组 UV,所以每行 U 的数量应该设置为宽的一半
src_v —— YUV V 分量
src_stride_u —— V 分量步幅,同样为宽的一半
dst_argb —— ARGB 输出
dst_stride_argb —— ARGB 步幅
width —— 宽
height —— 高

libyuv/convert_argb.h

// Convert I420 to ARGB.
LIBYUV_API
int I420ToARGB(const uint8_t* src_y,
               int src_stride_y,
               const uint8_t* src_u,
               int src_stride_u,
               const uint8_t* src_v,
               int src_stride_v,
               uint8_t* dst_argb,
               int dst_stride_argb,
               int width,
               int height);

I420ToARGB 函数内部仅仅调用了 I420ToARGBMatrix,而 I420ToARGBMatrix 的入参多了个 kYuvI601Constants,从名字上不难得出这使用了 BT.601 标准的公式。YUV RGB 转化具体可参见《详解 YUV,一文搞定 YUV 是什么!》。

I420ToARGBMatrix 函数核心流程:

  1. I422ToARGBRow 这个函数指针先指向 C 实现版本的 I422ToARGBRow_C 函数。
  2. 入参高度(height)为负意味着反转图像,dst_argb 指向最后一行,而 dst_stride_argb(ARGB 步幅)修改为负,把源图像的第一行写入最后一行,第二行写入倒数第二行,以此类推。
  3. 如果定义了 HAS_I422TOARGBROW_NEON,并且调用 TestCpuFlag 测试 CPU 是否支持 NEON,如果支持 NEON,则 I422ToARGBRow 函数指针赋值为 I422ToARGBRow_Any_NEON,当入参宽度是 8 像素对齐时,I422ToARGBRow 最终赋值为 I422ToARGBRow_NEON。我们假设使用的是 arm64-v8a 的芯片,所以此处一定是支持 NEON 的,并且假设宽度是 640,则 I422ToARGBRow 会被赋值为 I422ToARGBRow_NEON。
  4. 以高 for 循环开始遍历调用 I422ToARGBRow_NEON 处理每一行图像,y & 1 用来隔一行去新增 U、V 分量的步长,I420 中上下两行的 4 个 Y 会复用一组 UV。

convert_argb.cc

// Convert I420 to ARGB with matrix.
LIBYUV_API
int I420ToARGBMatrix(const uint8_t* src_y,
                     int src_stride_y,
                     const uint8_t* src_u,
                     int src_stride_u,
                     const uint8_t* src_v,
                     int src_stride_v,
                     uint8_t* dst_argb,
                     int dst_stride_argb,
                     const struct YuvConstants* yuvconstants,
                     int width,
                     int height) {
  int y;
  void (*I422ToARGBRow)(const uint8_t* y_buf, const uint8_t* u_buf,
                        const uint8_t* v_buf, uint8_t* rgb_buf,
                        const struct YuvConstants* yuvconstants, int width) =
      I422ToARGBRow_C;
  if (!src_y || !src_u || !src_v || !dst_argb || width <= 0 || height == 0) {
    return -1;
  }
  // Negative height means invert the image.
  if (height < 0) {
    height = -height;
    dst_argb = dst_argb + (height - 1) * dst_stride_argb;
    dst_stride_argb = -dst_stride_argb;
  }
......
#if defined(HAS_I422TOARGBROW_NEON)
  if (TestCpuFlag(kCpuHasNEON)) {
    I422ToARGBRow = I422ToARGBRow_Any_NEON;
    if (IS_ALIGNED(width, 8)) {
      I422ToARGBRow = I422ToARGBRow_NEON;
    }
  }
#endif
......

  for (y = 0; y < height; ++y) {
    I422ToARGBRow(src_y, src_u, src_v, dst_argb, yuvconstants, width);
    dst_argb += dst_stride_argb;
    src_y += src_stride_y;
    if (y & 1) {
      src_u += src_stride_u;
      src_v += src_stride_v;
    }
  }
  return 0;
}

// Convert I420 to ARGB.
LIBYUV_API
int I420ToARGB(const uint8_t* src_y,
               int src_stride_y,
               const uint8_t* src_u,
               int src_stride_u,
               const uint8_t* src_v,
               int src_stride_v,
               uint8_t* dst_argb,
               int dst_stride_argb,
               int width,
               int height) {
  return I420ToARGBMatrix(src_y, src_stride_y, src_u, src_stride_u, src_v,
                          src_stride_v, dst_argb, dst_stride_argb,
                          &kYuvI601Constants, width, height);
}

kYuvI601Constants 定义在 convert_argb.h,实现在 row_common.cc 中。kYuvI601Constants 是个 YuvConstants 结构体,这个结构体又定义在 row.h 中。

libyuv/convert_argb.h

// Conversion matrix for YUV to RGB
LIBYUV_API extern const struct YuvConstants kYuvI601Constants;   // BT.601

由于假设了 CPU 的类型为 arm64-v8a,所以 __aarch64__ 宏定义是存在的,预编译的时候就会使用对应的代码。kUVCoeff 和 kRGBCoeffBias 会被相应的赋值。

libyuv/row.h

#if defined(__aarch64__) || defined(__arm__)
// This struct is for ARM color conversion.
struct YuvConstants {
  uvec8 kUVCoeff;
  vec16 kRGBCoeffBias;
};
#else
// This struct is for Intel color conversion.
struct YuvConstants {
  uint8_t kUVToB[32];
  uint8_t kUVToG[32];
  uint8_t kUVToR[32];
  int16_t kYToRgb[16];
  int16_t kYBiasToRgb[16];
};

MAKEYUVCONSTANTS(I601, YG, YB, UB, UG, VG, VR) 展开宏定义,就会出现结构体 kYuvI601Constants 和 kYvuI601Constants。具体结构体赋值是使用另外一个宏 YUVCONSTANTSBODY 定义的,同样预编译会选择 __aarch64__ 这个分支。

YUV 转 RGB 使用如下公式,化简后就是注释中的公式:

在这里插入图片描述

row_common.cc

// Macros to create SIMD specific yuv to rgb conversion constants.

// clang-format off

#if defined(__aarch64__) || defined(__arm__)
// Bias values include subtract 128 from U and V, bias from Y and rounding.
// For B and R bias is negative. For G bias is positive.
#define YUVCONSTANTSBODY(YG, YB, UB, UG, VG, VR)                             \
  {{UB, VR, UG, VG, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},                     \
   {YG, (UB * 128 - YB), (UG * 128 + VG * 128 + YB), (VR * 128 - YB), YB, 0, \
    0, 0}}
#else
......
#endif

// clang-format on

#define MAKEYUVCONSTANTS(name, YG, YB, UB, UG, VG, VR)            \
  const struct YuvConstants SIMD_ALIGNED(kYuv##name##Constants) = \
      YUVCONSTANTSBODY(YG, YB, UB, UG, VG, VR);                   \
  const struct YuvConstants SIMD_ALIGNED(kYvu##name##Constants) = \
      YUVCONSTANTSBODY(YG, YB, VR, VG, UG, UB);

// TODO(fbarchard): Generate SIMD structures from float matrix.

// BT.601 limited range YUV to RGB reference
//  R = (Y - 16) * 1.164             + V * 1.596
//  G = (Y - 16) * 1.164 - U * 0.391 - V * 0.813
//  B = (Y - 16) * 1.164 + U * 2.018
// KR = 0.299; KB = 0.114

// U and V contributions to R,G,B.
#if defined(LIBYUV_UNLIMITED_DATA) || defined(LIBYUV_UNLIMITED_BT601)
#define UB 129 /* round(2.018 * 64) */
#else
#define UB 128 /* max(128, round(2.018 * 64)) */
#endif
#define UG 25  /* round(0.391 * 64) */
#define VG 52  /* round(0.813 * 64) */
#define VR 102 /* round(1.596 * 64) */

// Y contribution to R,G,B.  Scale and bias.
#define YG 18997 /* round(1.164 * 64 * 256 * 256 / 257) */
#define YB -1160 /* 1.164 * 64 * -16 + 64 / 2 */

MAKEYUVCONSTANTS(I601, YG, YB, UB, UG, VG, VR)

现在来分析 HAS_I422TOARGBROW_NEON 这个宏和 TestCpuFlag(kCpuHasNEON)。

HAS_I422TOARGBROW_NEON 宏定义在 row.h 中,由于 Libyuv 没有禁用 NEON,并且 __aarch64__ 被定义,所以 HAS_I422TOARGBROW_NEON 宏编译开关就会打开。

libyuv/row.h

// The following are available on Neon platforms:
#if !defined(LIBYUV_DISABLE_NEON) && \
    (defined(__aarch64__) || defined(__ARM_NEON__) || defined(LIBYUV_NEON))
......
#define HAS_I422TOARGBROW_NEON
......
#endif

TestCpuFlag 这个函数定义在 cpu_id.h 这个头文件。

  1. __ATOMIC_RELAXED(意味着没有线程间的排序约束) 和 __atomic_load_n 用作原子地加载 cpu_info_ 给 cpu_info 变量赋值,否则就直接把 cpu_info_ 的值赋给 cpu_info。
  2. 如果 cpu_info 为 0,则调用 InitCpuFlags() 获取值并与入参 test_flag 取与返回,否则直接使用 cpu_info 的值与 test_flag 取与。

SetCpuFlags 将 cpu_info_ 设置为 cpu_flags。 cpu_flags 应该是 kCpuHas 常数的有效组合,其中包括 kCpuInitialized。

libyuv/cpu_id.h

// Detect CPU has SSE2 etc.
// Test_flag parameter should be one of kCpuHas constants above.
// Returns non-zero if instruction set is detected
static __inline int TestCpuFlag(int test_flag) {
  LIBYUV_API extern int cpu_info_;
#ifdef __ATOMIC_RELAXED
  int cpu_info = __atomic_load_n(&cpu_info_, __ATOMIC_RELAXED);
#else
  int cpu_info = cpu_info_;
#endif
  return (!cpu_info ? InitCpuFlags() : cpu_info) & test_flag;
}
......
static __inline void SetCpuFlags(int cpu_flags) {
  LIBYUV_API extern int cpu_info_;
#ifdef __ATOMIC_RELAXED
  __atomic_store_n(&cpu_info_, cpu_flags, __ATOMIC_RELAXED);
#else
  cpu_info_ = cpu_flags;
#endif
}

InitCpuFlags 内部实际调用了 MaskCpuFlags(-1)。这里传递 -1 是因为 -1 的二进制形式(补码形式存放)为每个位都为 1(0xFFFFFFFF)。

MaskCpuFlags 内部调用了 GetCpuFlags() 获取 CPU Flag,接着调用 SetCpuFlags(…) 给 cpu_info_ 全局变量赋值。

GetCpuFlags() 内部根据预编译会走 defined(__arm__) || defined(__aarch64__) 这个条件下的代码块,结合注释不难知道对于 aarch64(arm64), /proc/cpuinfo 的功能是不完整的,例如,它没有 NEON 标志。因此对于 aarch64,这里硬编码了 NEON 启用。如果是其他 ARM 版本(例如 armv7a),则调用 ArmCpuCaps 解析 /proc/cpuinfo 内的文本查找是否支持 NEON。

ArmCpuCaps 首先打开 /proc/cpuinfo 文件,如果文件不存在,则假定支持 NEON,这将发生在 Chrome 沙盒 Pepper 或渲染进程。否则按照行去搜索 neon 或 asimd 字段,只要存在就认为 CPU 启用了 NEON 特性。

cpu_id.cc

// cpu_info_ variable for SIMD instruction sets detected.
LIBYUV_API int cpu_info_ = 0;

// Based on libvpx arm_cpudetect.c
// For Arm, but public to allow testing on any CPU
LIBYUV_API SAFEBUFFERS int ArmCpuCaps(const char* cpuinfo_name) {
  char cpuinfo_line[512];
  FILE* f = fopen(cpuinfo_name, "r");
  if (!f) {
    // Assume Neon if /proc/cpuinfo is unavailable.
    // This will occur for Chrome sandbox for Pepper or Render process.
    return kCpuHasNEON;
  }
  while (fgets(cpuinfo_line, sizeof(cpuinfo_line) - 1, f)) {
    if (memcmp(cpuinfo_line, "Features", 8) == 0) {
      char* p = strstr(cpuinfo_line, " neon");
      if (p && (p[5] == ' ' || p[5] == '\n')) {
        fclose(f);
        return kCpuHasNEON;
      }
      // aarch64 uses asimd for Neon.
      p = strstr(cpuinfo_line, " asimd");
      if (p) {
        fclose(f);
        return kCpuHasNEON;
      }
    }
  }
  fclose(f);
  return 0;
}

static SAFEBUFFERS int GetCpuFlags(void) {
  int cpu_info = 0;
......
#if defined(__arm__) || defined(__aarch64__)
// gcc -mfpu=neon defines __ARM_NEON__
// __ARM_NEON__ generates code that requires Neon.  NaCL also requires Neon.
// For Linux, /proc/cpuinfo can be tested but without that assume Neon.
#if defined(__ARM_NEON__) || defined(__native_client__) || !defined(__linux__)
  cpu_info = kCpuHasNEON;
// For aarch64(arm64), /proc/cpuinfo's feature is not complete, e.g. no neon
// flag in it.
// So for aarch64, neon enabling is hard coded here.
#endif
#if defined(__aarch64__)
  cpu_info = kCpuHasNEON;
#else
  // Linux arm parse text file for neon detect.
  cpu_info = ArmCpuCaps("/proc/cpuinfo");
#endif
  cpu_info |= kCpuHasARM;
#endif  // __arm__
  cpu_info |= kCpuInitialized;
  return cpu_info;
}

// Note that use of this function is not thread safe.
LIBYUV_API
int MaskCpuFlags(int enable_flags) {
  int cpu_info = GetCpuFlags() & enable_flags;
  SetCpuFlags(cpu_info);
  return cpu_info;
}

LIBYUV_API
int InitCpuFlags(void) {
  return MaskCpuFlags(-1);
}

现在是时候转到 I422ToARGBRow_NEON 具体实现了。只搜索代码的话会发现 I422ToARGBRow_NEON 在 row_neon.cc 和 row_neon64.cc 中均有实现。看代码就会发现,它们的使用是被条件编译约束着的,所以按照前面假设 CPU 类型为 arm64-v8a 则一定会调用到 row_neon64.cc 中的实现,row_neon.cc 中的实现预编译条件不满足,代码自然就不会加入。

1. YUVTORGB_SETUP

使用了两条 ld4r 指令加载 kUVCoeff 和 kRGBCoeffBias 中的数据,LD4R 指令表示加载单个四元素结构并复制到四个寄存器的所有通道,也就是 V28 寄存器每个通道的值都是 UB,V29:VR,V30:UG,V31:VG,V24:YG,V25:BB,V26:BG,V27:BR。

2. movi v19.8b, #255

将立即数 255(0xFF)移动到 V19 寄存器的每个 8 位通道。

3. READYUV422

从 YUV 422 数据中读取 8 个 Y、4 个 U 和 4 个 V(每一行上 2 个 Y 共用一组 UV)。

ldr d0, [%[src_y]], #8 加载 8 个 Y 到 d0 寄存器(64 位),并将偏移量加 8(%[src_y] + 8 写入 %[src_y])

ld1 {v1.s}[0], [%[src_u]], #4 加载 4 个 U 到 V1 寄存器第一个 S 通道,并将偏移量加 4

ld1 {v1.s}[1], [%[src_v]], #4 加载 4 个 V 到 V1 寄存器第二个 S 通道,并将偏移量加 4

zip1 v0.16b, v0.16b, v0.16b 执行完后 V0 寄存器数据排布变为: Y7 Y7 Y6 Y6 Y5 Y5 Y4 Y4 Y3 Y3 Y2 Y2 Y1 Y1 Y0 Y0

ZIP1 这条指令从两个源 SIMD&FP 寄存器的下半部分读取相邻的向量元素,并将其成对,然后将这两个向量交叉放置到一个向量中,最后将向量写入目标 SIMD&FP 寄存器。

在这里插入图片描述

prfm pldl1keep, [%[src_y], 448] 指示内存系统预加载 448 字节 Y 数据到 L1 缓存行上,448 立即数代表字节偏移量(8 的倍数)

PRFM 指令向内存系统发出信号,表明在不久的将来可能会从指定地址访问数据内存。当内存访问发生时,内存系统可以通过采取预期的操作来加速内存访问,例如将包含指定地址的缓存行预加载到一个或多个缓存中。

PLD —— 预取加载

L1 —— 一级缓存

KEEP —— 保留预取或临时预取,在缓存中正常分配

zip1 v1.16b, v1.16b, v1.16b 执行完后 V1 寄存器数据排布变为: V3 V3 V2 V2 V1 V1 V0 V0 U3 U3 U2 U2 U1 U1 U0 U0

prfm pldl1keep, [%[src_u], 128] 指示内存系统预加载 128 字节 U 数据到 L1 缓存行上

prfm pldl1keep, [%[src_v], 128] 指示内存系统预加载 128 字节 V 数据到 L1 缓存行上

4. YUVTORGB

umull2 v3.4s, v0.8h, v24.8h Y(上半部分) * YG -> V3

umull v6.8h, v1.8b, v30.8b U * UG -> V6

umull v0.4s, v0.4h, v24.4h Y(下半部分) * YG -> V0

UMULL、UMULL2 将第一个源 SIMD&FP 寄存器的下半部分或上半部分的每个向量元素与第二个源 SIMD&FP 寄存器的指定向量元素相乘,将结果放入一个向量中,并将该向量写入目标 SIMD&FP 寄存器。目标向量元素的长度是相乘元素长度的两倍。

UMULL 指令从第一个源寄存器的下半部分提取向量元素,而 UMULL2 指令从第一个源寄存器的上半部分提取向量元素。

umlal2 v6.8h, v1.16b, v31.16b U * UG + V * VG -> V6

UMLAL,UMLAL2 该指令将第一个源 SIMD&FP 寄存器的下半部或上半部的向量元素与第二个源 SIMD&FP 寄存器对应的向量元素相乘,并将结果与目标 SIMD&FP 寄存器的向量元素累加。目标向量元素的长度是相乘元素长度的两倍。

UMLAL 指令从第一个源寄存器的下半部分提取向量元素,而 UMLAL2 指令从第一个源寄存器的上半部分提取向量元素。

uqshrn v0.4h, v0.4s, #16 V0 中的 S 通道元素右移 16 位,并将结果饱和为 H 大小,最终写入 V0.4H(V0 下半部分 H 通道)

uqshrn2 v0.8h, v3.4s, #16 V3 中的 S 通道元素右移 16 位,并将结果饱和为 H 大小,最终写入 V0 上半部分 H 通道

UQSHRN,UQSHRN2 读取源 SIMD&FP 寄存器中的每个向量元素,将每个结果右移一个立即数,将每个移位后的结果饱和为原始宽度的一半,将最终结果放入一个向量,并将该向量写入目标 SIMD&FP 寄存器的下半部分或上半部分。这两条指令中的所有值都是无符号整数值,结果被截断了。要获得四舍五入的结果,请使用 UQRSHRN。

UQSHRN 指令将向量写入目标寄存器的下半部分并清除上半部分,而 UQSHRN2 指令将向量写入目标寄存器的上半部分而不影响寄存器的其他位。

umull v4.8h, v1.8b, v28.8b U * UB -> V4

umull2 v5.8h, v1.16b, v29.16b V * VR -> V5

add v17.8h, v0.8h, v26.8h Y * YG + BG -> V17

add v16.8h, v0.8h, v4.8h Y * YG + U * UB -> V16

add v18.8h, v0.8h, v5.8h Y * YG + V * VR -> V18

uqsub v17.8h, v17.8h, v6.8h G: Y * YG + BG - U * UG + V * VG

UQSUB 从第一个源 SIMD&FP 寄存器对应的元素值中减去第二个源 SIMD&FP 寄存器的元素值,将结果放入一个向量中,并将该向量写入目标 SIMD&FP 寄存器。

uqsub v16.8h, v16.8h, v25.8h B: Y * YG + U * UB - BB

uqsub v18.8h, v18.8h, v27.8h R: Y * YG + V * VR - BR

5. RGBTORGB8

uqshrn v17.8b, v17.8h, #6 G 右移 6 位(2^6 = 64),并将结果饱和为 B 大小,最终结果放入 V17 寄存器的下半部分

uqshrn v16.8b, v16.8h, #6 B 右移 6 位,并将结果饱和为 B 大小,最终结果放入 V16 寄存器的下半部分

uqshrn v18.8b, v18.8h, #6 R 右移 6 位,并将结果饱和为 B 大小,最终结果放入 V18 寄存器的下半部分

6. subs %w[width], %w[width], #8

SUBS 指令从一个寄存器值减去一个立即值,并将结果写入目标寄存器。它会根据结果更新条件标志。%w[width] 分配的 W 寄存器(使用 %w[name] 来操作 W 寄存器,也可以对 X 寄存器使用%x[name],但这是默认值)减去 8 并赋值回去。

AArch64 执行状态提供了 31 × 64 位通用寄存器,在任何时候和所有异常级别都可以访问。每个寄存器是 64 位宽的,它们通常被称为寄存器 X0~X30。每个 AArch64 64 位通用寄存器(X0~X30)也有 32 位(W0~W30)形式。

在这里插入图片描述

7. st4 {v16.8b,v17.8b,v18.8b,v19.8b}, [%[dst_argb]], #32

ST4 指令从 4 个寄存器存储多个 4 元素结构。该指令通过交错的方式,将多个四元素结构从四个 SIMD&FP 寄存器存储到内存中。存储每个寄存器的每个元素。

不难知道这里的内存排布为:BGRA BGRA BGRA…BGRA(一共 8 组 BGRA),另外偏移量需要加 32(8 * 4)。

注意:V19 里面的位全都为 1,所以 BGRA 中的 A 都为 0xFF。

++最后由于题目谈及的函数实际为 I420 转 ARGB,因此这里需要按照小端去理解。这是 Libyuv 和其它库的一些区别,其它库通常而言所有转换的格式和内存排列是一致的!++

8. b.gt 1b

B.

如果 subs %w[width], %w[width], #8 减法这条指令更新了零标志位,B 分支指令就不满足大于 0 的条件,就会退出这段内联汇编代码。否则,继续跳到标签 1 处继续处理一行中剩余的像素。

9. YUVTORGB_REGS

这个宏定义列出了 SIMD 需要使用的寄存器。ARMv8 有 32 个 128 位浮点寄存器,标记为 V0-V31。 32 个寄存器用于保存标量浮点指令的浮点操作数以及 NEON 操作的标量和向量操作数。

10. 内联汇编语法

GCC提供了 asm 帮助我们内联汇编,语法如下:

asm [ volatile ] ( code-strings [ : output-list [ : input-list [ : overwrite-list ] ] ] );

volatile 用于指示当添加此关键字时,不允许 GCC 编译器对 assmbly code 进行代码优化。

code-strings:书写我们的汇编指令,用 “” 括起来(就像字符串)。如果要内联多条汇编语句,则需要为每一条指令后添加 \n\t 保证格式。

outputlist , inputlist , overwritelist 要用“:”分隔,如果 outputlist、inputlist、overwritelist 有多个值,则用“,”分隔。

output-list:类似于返回值,即我们想在汇编代码里修改的值。

input-list:类似于参数,即我们要在汇编代码里使用的值。

overwrite-list:这是我们要使用的寄存器用“”括起来。

  • “cc” 表示内联汇编代码运算过程中,会产生符号变化、数据溢出等问题,这些操作最终会修改标志寄存器
  • “memory” 表示汇编代码对输入和输出操作数涉及内存操作

row_neon64.cc

// This module is for GCC Neon armv8 64 bit.
#if !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__)
......
// v0.8h: Y
// v1.16b: 8U, 8V

// Read 8 Y, 4 U and 4 V from 422
#define READYUV422                               \
  "ldr        d0, [%[src_y]], #8             \n" \
  "ld1        {v1.s}[0], [%[src_u]], #4      \n" \
  "ld1        {v1.s}[1], [%[src_v]], #4      \n" \
  "zip1       v0.16b, v0.16b, v0.16b         \n" \
  "prfm       pldl1keep, [%[src_y], 448]     \n" \
  "zip1       v1.16b, v1.16b, v1.16b         \n" \
  "prfm       pldl1keep, [%[src_u], 128]     \n" \
  "prfm       pldl1keep, [%[src_v], 128]     \n"
......
// UB VR UG VG
// YG BB BG BR
#define YUVTORGB_SETUP                                                \
  "ld4r       {v28.16b, v29.16b, v30.16b, v31.16b}, [%[kUVCoeff]] \n" \
  "ld4r       {v24.8h, v25.8h, v26.8h, v27.8h}, [%[kRGBCoeffBias]] \n"
  
// v16.8h: B
// v17.8h: G
// v18.8h: R

// Convert from YUV to 2.14 fixed point RGB
#define YUVTORGB                                          \
  "umull2     v3.4s, v0.8h, v24.8h           \n"          \
  "umull      v6.8h, v1.8b, v30.8b           \n"          \
  "umull      v0.4s, v0.4h, v24.4h           \n"          \
  "umlal2     v6.8h, v1.16b, v31.16b         \n" /* DG */ \
  "uqshrn     v0.4h, v0.4s, #16              \n"          \
  "uqshrn2    v0.8h, v3.4s, #16              \n" /* Y */  \
  "umull      v4.8h, v1.8b, v28.8b           \n" /* DB */ \
  "umull2     v5.8h, v1.16b, v29.16b         \n" /* DR */ \
  "add        v17.8h, v0.8h, v26.8h          \n" /* G */  \
  "add        v16.8h, v0.8h, v4.8h           \n" /* B */  \
  "add        v18.8h, v0.8h, v5.8h           \n" /* R */  \
  "uqsub      v17.8h, v17.8h, v6.8h          \n" /* G */  \
  "uqsub      v16.8h, v16.8h, v25.8h         \n" /* B */  \
  "uqsub      v18.8h, v18.8h, v27.8h         \n" /* R */

// Convert from 2.14 fixed point RGB To 8 bit RGB
#define RGBTORGB8                                \
  "uqshrn     v17.8b, v17.8h, #6             \n" \
  "uqshrn     v16.8b, v16.8h, #6             \n" \
  "uqshrn     v18.8b, v18.8h, #6             \n"
  
#define YUVTORGB_REGS                                                          \
  "v0", "v1", "v3", "v4", "v5", "v6", "v7", "v16", "v17", "v18", "v24", "v25", \
      "v26", "v27", "v28", "v29", "v30", "v31"
......
void I422ToARGBRow_NEON(const uint8_t* src_y,
                        const uint8_t* src_u,
                        const uint8_t* src_v,
                        uint8_t* dst_argb,
                        const struct YuvConstants* yuvconstants,
                        int width) {
  asm volatile(
      YUVTORGB_SETUP
      "movi        v19.8b, #255                  \n" /* A */
      "1:                                        \n" READYUV422 YUVTORGB
          RGBTORGB8
      "subs        %w[width], %w[width], #8      \n"
      "st4         {v16.8b,v17.8b,v18.8b,v19.8b}, [%[dst_argb]], #32 \n"
      "b.gt        1b                            \n"
      : [src_y] "+r"(src_y),                               // %[src_y]
        [src_u] "+r"(src_u),                               // %[src_u]
        [src_v] "+r"(src_v),                               // %[src_v]
        [dst_argb] "+r"(dst_argb),                         // %[dst_argb]
        [width] "+r"(width)                                // %[width]
      : [kUVCoeff] "r"(&yuvconstants->kUVCoeff),           // %[kUVCoeff]
        [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias)  // %[kRGBCoeffBias]
      : "cc", "memory", YUVTORGB_REGS, "v19");
}
......
#endif  // !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/132119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QThread、moveToThread用法详述

1.吐槽 QThread类提供了一种平台无关的方法对线程进行管理。但对于QThread类的熟练使用&#xff0c;即使是从事Qt开发多年的程序猿们&#xff0c;往往也会踩雷、入坑。总之&#xff1a;QThread类不好用、如果对该类理解不透&#xff0c;很容易导致程序崩溃。本人强烈建议&#…

(函数介绍)puts()函数

功能介绍 1. puts()函数用来向标准输出设备屏幕输出字符串并换行。 2. 函数的参数就是一个起始的地址&#xff0c;然后就从这个地址开始一直输出字符串&#xff0c;直到碰到\0就停止&#xff0c;然后这个\0是不进行输出的&#xff0c;是不能够算在里面的。与此同时&#xff…

十、字节缓冲流、字符流、转换流、对象操作流、对象序列化流

字节缓冲流 构造方法 字节缓冲流介绍 BufferedOutputStream&#xff1a;该类实现缓冲输出流.通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用BufferedInputStream&#xff1a;创建BufferedInputStream将创建一个内部缓冲区数…

2022年为什么要学习C语言?

为什么学习c语言 为什么学C语言逻辑&#xff1f; 为什么要学习C语言&#xff1f; 学习C语言的主要理由有以下几点&#xff1a; C语言可以作为学习计算机程序设计语言的入门语言&#xff1b; C语言是编写操作系统的首选语言&#xff0c;与计算机硬件打交道时灵巧且高效&…

labelImag安装与使用及构造数据集

在做目标检测任务时&#xff0c;需要进行标注&#xff0c;选择了LabelImg作为标注工具&#xff0c;下面是安装及使用过程。 我们使用Anconda的虚拟环境进行安装&#xff0c;激活环境后&#xff0c;执行&#xff1a; pip install labelimg -i https://pypi.tuna.tsinghua.edu.c…

代码随想录算法训练营第四天 java : 24. 两两交换链表中的节点 ,19.删除链表的倒数第N个节点 ,面试题 02.07. 链表相交,142环形链表II

文章目录Leetcode 24. 两两交换链表中的节点题目链接本题思路需要注意的点AC 代码Leetcode 19.删除链表的倒数第N个节点题目链接需要注意的点AC代码Leetcode面试题 02.07. 链表相交题目链接这个略了Leetcode 142环形链表II题目链接难点:AC代码今日收获**一朵玫瑰正马不停蹄地成…

【Linux】Linux下基本指令(三)

作者&#xff1a;一个喜欢猫咪的的程序员 专栏&#xff1a;《Linux》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 1. Linux基本指令&#xff1a;&#xff08;续&#xff09; 1.1zip指令和u…

极智编程 | C++模板函数

欢迎关注我的公众号 [极智视界]&#xff0c;获取我的更多笔记分享 大家好&#xff0c;我是极智视界&#xff0c;本文介绍一下 C模板函数。 模板函数是 C 中一种特殊的函数&#xff0c;它的类型参数列表用尖括号 <> 括起来&#xff0c;放在函数名的后面。使用模板函数&a…

Go 并发

来自 《Go 语言从入门到实战》 的并发章节学习笔记&#xff0c;欢迎阅读斧正&#xff0c;感觉该专栏整体来说对有些后端编程经验的来说比无后端编程经验的人更友好。。 Thread VS Groutine 创建时默认 Stack 大小&#xff1a;前者默认 1M&#xff0c;Groutint 的 Stack 初始化…

C语言可变参数与内存管理

有时&#xff0c;您可能会碰到这样的情况&#xff0c;您希望函数带有可变数量的参数&#xff0c;而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案&#xff0c;它允许您定义一个函数&#xff0c;能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定…

LeetCode题解 二叉树(八):404 左叶子之和;513 找树左下角的值;112 路径总和;113 路径总和II

二叉树 404 左叶子之和 easy 左叶子结点也好判断&#xff0c;若某结点属于左结点&#xff0c;且无子树&#xff0c;就是左叶子结点 即也如此&#xff0c;所以如果要判断&#xff0c;必然要从父结点下手&#xff0c;涉及到三层结点的处理 如果要使用递归法&#xff0c;要使用…

(二十三)大白话数据库服务器上的RAID存储架构的电池充放电原理

文章目录 1、RAID卡的缓存2、RAID卡的缓存里的数据会突然丢失怎么办?3、锂电池存在性能衰减问题1、RAID卡的缓存 服务器使用多块磁盘组成的RAID阵列的时候,一般会有一个RAID卡,这个RAID卡是带有一个缓存的,这个缓存不是直接用我们的服务器的主内存的那种模式,他是一种跟内…

网络静态路由综合实验

1.首先分配ip,配置ip和环回 [Huawei]sysname R1 [R1]interface LoopBack 0 [R1-LoopBack0]ip add 192.168.1.33 28 [R1-LoopBack0]q [R1]int l 1 [R1-LoopBack1]ip add 192.168.1.49 28 [R1-LoopBack1]q [R1]int g 0/0/0 [R1-GigabitEthernet0/0/0]ip add 192.168.1.1 30 [R1-…

JVM与Java体系结构

目录 前言 架构师每天都在思考什么&#xff1f; Java vs C Java生态圈 字节码 多语言混合编程 虚拟机与Java虚拟机 虚拟机 Java虚拟机 JVM的位置 JVM整体结构 Java代码执行流程 JVM的架构模型 举例 字节码反编译 总结 栈 JVM生命周期 虚拟机的启动 虚拟机的…

时间从来不语,确回答了所有问题——我的2022年终总结

趁着没阳&#xff0c;趁着电脑还能开机&#xff0c;趁着还能写&#xff0c;赶紧小结过去这一年。没有别的感觉&#xff0c;就是感觉太快&#xff0c;时间太过匆匆.....最大的感触是两个字“变化”&#xff0c;如果非要说四个字是“变化太快”&#xff0c;就如当下的yi情政策&am…

多线程_进阶

文章目录线程通信概念使用方式案例单例模式阻塞式队列线程池常见的锁策略乐观锁 悲观锁CASCAS存在的问题:ABA问题读写锁自旋锁公平锁 非公平锁非公平锁公平锁synchronizedjvm对synchronized的优化:锁升级synchronized的其他优化Lock体系synchronized vs lock独占锁vs共享锁独占…

Arch/Manjaro换源+安装常用的软件+安装显卡驱动

本文将教你&#xff1a;换源安装显卡驱动&#xff0c;安装常用软件例如腾讯会议&#xff0c;QQ&#xff0c;WPS 一起交流Linux知识&#xff0c;欢迎加入Skype群&#xff1a; Join conversationhttps://join.skype.com/q6wrF3d6Usni pacman换清华源 首先安装vim&#xff0c;用来…

(二十四)大白话RAID锂电池充放电导致的MySQL数据库性能抖动的优化

案例实战:RAID锂电池充放电导致的MySQL数据库性能抖动的优化 文章目录 1、磁盘故障怎么保障数据不丢失?2、线上MySQL数据库的性能定期抖动的原因1、磁盘故障怎么保障数据不丢失? 前面经过了几天的生产经验的一些铺垫,包括MySQL磁盘读写的机制,Linux存储系统的原理,RAID磁…

垃圾佬图拉丁装机

理论知识 缩线程 amd搞了个推土机架构 两个核心公用一个浮点运算单元&#xff0c;因为浮点运算只占百分之二十。 浮点运算应该交给更适合的gpu去做 好的对比 RDP 微软的RDP本身就定位是一个远程登录和维护windows系的工具&#xff0c;它为什么要支持管理别的系统&#xff…

【mybatis generator实战】 1.crud 2.计数 3.自定义复杂mapper代码组织

1.计数 2.CRUD 增 注意&#xff1a; insert&#xff1a;一个必须全部有值。 insertSelective是&#xff1a;部分有值就行&#xff0c;用的较多。 有疑问可以看源码&#xff0c;发现xxxSelective就是拼接了一些参数。 删 改 注意&#xff1a; 4个更新方法&#xff1a; …