计算:RenderScript

Android GPU编程:OpenGL ES、Vulkan与RenderScript GPU编程是指利用图形处理器的并行计算能力来执行通用计算或图形渲染任务。与CPU不同,GPU拥有数千个核心,专为处理大量并行计算而设计,特别适合处理图形渲染、矩阵运算、图像处理等任务[2]。在Android平台上,GPU编程主要通过两种API实现:OpenGL ES和Vulkan。OpenGL ES是OpenGL的嵌入式版本,历史悠久且兼容性好;Vulkan是新一代跨平台图形API,提供了更底层的硬件访问和更高的性能。// CPU: 串行处理,少量核心i < n;i++) {// 逐个处理。 阅读详情

RenderScript是在Android上以高性能运行计算密集型任务的框架。 RenderScript主要面向数据并行计算,尽管串行工作负载也可以受益。 RenderScript运行时间可以跨设备上可用的处理器(如多核CPU和GPU)进行并行工作。 这允许您专注于表达算法,而不是调度工作。 RenderScript对于执行图像处理,计算摄影或计算机视觉的应用程序特别有用。


要开始使用RenderScript,您应该了解两个主要的概念:

1、该语言本身是用于编写高性能计算代码的C99派生语言。 以下编写RenderScript内核一节描述如何使用它来编写计算内核。


2、控制API用于管理RenderScript资源的生命周期并控制内核执行。 它有三种不同的语言:Android,NDK中的Java,C ++以及C99派生的内核语言本身。 使用Java代码和单源RenderScript中的RenderScript分别描述了第一个和第三个选项。



一、编写一个RenderScript内核

RenderScript内核通常驻留在<project_root> / src /目录中的.rs文件中; 每个.rs文件都被称为脚本。 每个脚本都包含自己的一组内核,函数和变量。 脚本可以包含:

1、一个pragma声明(#pragma version(1)),声明此脚本中使用的RenderScript内核语言的版本。 目前,1是唯一的有效值。


2、一个pragma声明(#pragma rs java_package_name(com.example.app)),声明从这个脚本反映的Java类的包名称。 请注意,.rs文件必须是应用程序包的一部分,而不是库项目中。


3、零个或多个可调用功能。 调用函数是一个单线程的RenderScript函数,您可以使用任意参数从Java代码调用。 这些通常用于较大的处理流水线中的初始设置或串行计算。


4、零个或多个脚本全局变量。 脚本全局相当于C中的全局变量。您可以从Java代码访问脚本全局变量,这些常用于将参数传递给RenderScript内核。


5、零个或多个计算内核。 计算内核是一个函数或函数集合,您可以指导RenderScript运行时并行跨数据集合执行。 有两种计算内核:映射内核(也称为foreach内核)和还原内核。


映射内核是一个并行函数,它对相同维度的分配集合进行操作。 默认情况下,它对于这些维度中的每个坐标执行一次。 通常(但不排他地)用于将输入分配的集合一次转换为输出分配一个元素。


●这是一个简单映射内核的例子:

uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
  uchar4 out = in;
  out.r = 255 - in.r;
  out.g = 255 - in.g;
  out.b = 255 - in.b;
  return out;
}

在大多数方面,这与标准C函数相同。 应用于函数原型的RS_KERNEL属性指定该函数是RenderScript映射内核,而不是可调用函数。 in参数是根据传递给内核启动的输入分配自动填写的。 下面讨论参数x和y。 从内核返回的值将自动写入输出分配中的相应位置。 默认情况下,该内核在其整个输入分配中运行,每个元素在分配中执行一个内核函数。


映射内核可以具有一个或多个输入分配,单个输出分配或两者。 RenderScript运行时检查以确保所有输入和输出分配具有相同的维度,并且输入和输出分配的元素类型与内核的原型相匹配; 如果这些检查中的任何一个失败,RenderScript会引发异常。


注意:在Android 6.0(API级别23)之前,映射内核可能没有多个输入分配。


如果您需要比内核更多的输入或输出分配,则这些对象应绑定到rs_allocation脚本全局,并通过rsGetElementAt_type()或rsSetElementAt_type()从内核或可调用函数访问。


注意:RS_KERNEL是由RenderScript自动定义的一个宏,方便您使用:

#define RS_KERNEL __attribute__((kernel))

还原内核是一系列功能,可以对相同维度的输入分配集合进行操作。 默认情况下,其累加器功能对于这些维度中的每个坐标执行一次。 通常(但不排他地)用于将输入分配的集合“减少”为单个值。


●这是一个简单的还原内核的例子,它将其输入的元素相加:

#pragma rs reduce(addint) accumulator(addintAccum)

static void addintAccum(int *accum, int val) {
  *accum += val;
}

还原内核由一个或多个用户编写的功能组成。 #pragma rs reduce用于通过指定其名称(在本例中为addint)以及组成内核的函数(在本示例中为累加器函数addintAccum)的名称和角色来定义内核。 所有这些功能必须是静态的。 还原内核总是需要一个累加器功能; 它可能还有其他功能,这取决于您希望内核做什么。


还原内核累加器函数必须返回void并且必须至少有两个参数。 第一个参数(在本示例中为accum)是一个指向累加器数据项的指针,而第二个参数(在此示例中为val)将根据传递给内核启动的输入分配自动填写。 累加器数据项由RenderScript运行时创建; 默认情况下,它被初始化为零。 默认情况下,该内核在其整个输入分配中运行,每个元素在分配中执行一次累加器函数。 默认情况下,累加器数据项的最终值被视为减少的结果,并返回给Java。 RenderScript运行时检查以确保输入分配的Element类型与累加器函数的原型匹配; 如果不匹配,则RenderScript会引发异常。


还原内核具有一个或多个输入分配,但没有输出分配。


在缩小内核深度这一节更详细地说明还原粒子。


Android 7.0(API级别24)及更高版本支持减少内核。


映射内核函数或还原内核累加器函数可以使用特殊参数x,y和z访问当前执行的坐标,这些参数必须是int或uint32_t类型。 这些参数是可选的。


映射内核函数或还原内核累加器函数也可以使用类型为rs_kernel_context的可选特殊参数上下文。 一系列运行时API需要用于查询当前执行的某些属性 - 例如rsGetDimX。 (上下文参数在Android 6.0(API级别23)及更高版本中可用)。


6、一个可选的init()函数。 init()函数是一个特殊类型的调用函数,当脚本首次实例化时,RenderScript将会运行。 这允许一些计算在脚本创建时自动发生。


5、零个或多个静态脚本全局变量和函数。 静态脚本全局相当于脚本全局,除了它不能从Java代码访问。 静态函数是可以从脚本中的任何内核或调用函数调用但不暴露于Java API的标准C函数。 如果不需要从Java代码调用脚本全局或函数,则强烈建议将其声明为静态。


一)、设置浮点精度

您可以在脚本中控制所需的浮点精度级别。 如果不需要完整的IEEE 754-2008标准(默认使用),这是有用的。 以下pragmas可以设置不同级别的浮点精度:

1、#pragma rs_fp_full(如果没有指定,则为默认值):对于IEEE 754-2008标准所述的需要浮点精度的应用程序。


2、#pragma rs_fp_relaxed:对于不需要严格的IEEE 754-2008兼容性并且可以容忍更低精度的应用程序。 此模式可实现对零零脉冲和零向零的清零。


3、#pragma rs_fp_imprecise:对于没有严格精度要求的应用程序。 此模式可以使rs_fp_relaxed中的所有内容与以下内容一起使用:

 1)、导致-0.0的操作可以返回+0.0替代。

 2)、INF和NAN的操作未定义。


大多数应用程序可以使用rs_fp_relaxed而没有任何副作用。 这对于某些架构可能是非常有益的,因为只有轻松的精度才能获得额外的优化(如SIMD CPU指令)。



二、从Java访问RenderScript API

在开发使用RenderScript的Android应用程序时,您可以通过以下两种方式之一从Java访问其API:

1、android.renderscript - 此类包中的API可用于运行Android 3.0(API级别11)及更高版本的设备。


2、android.support.v8.renderscript - 该软件包中的API可以通过支持库来获得,支持库可以在运行Android 2.3(API等级9)或更高版本的设备上使用。


以下是权衡:

1、如果您使用支持库API,则应用程序的RenderScript部分将与运行Android 2.3(API级别9)及更高版本的设备兼容,无论使用哪种RenderScript功能。 这允许您的应用程序使用更多的设备,而不是使用原生(android.renderscript)API。


2、某些RenderScript功能无法通过Support Library API进行。


3、如果您使用支持库API,您将比使用使用本机(android.renderscript)API获得(可能显着)更大的APK。


一)、使用RenderScript支持库API

为了使用支持库RenderScript API,您必须配置您的开发环境才能访问它们。 使用以下API需要以下Android SDK工具:

1、Android SDK Tools版本22.2或更高版本。


2、Android SDK Build-tools修订版18.1.0或更高版本。


您可以在Android SDK管理器中检查并更新这些工具的安装版本。


使用支持库RenderScript API:

1、确保您已安装所需的Android SDK版本和Build Tools版本。


2、更新Android构建过程的设置以包括RenderScript设置:

 1)、在应用程序模块的应用程序文件夹中打开build.gradle文件。

 2)、将以下RenderScript设置添加到文件中:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        minSdkVersion 9
        targetSdkVersion 19

        renderscriptTargetApi 18
        renderscriptSupportModeEnabled true

    }
}

上面列出的设置控制了Android构建过程中的特定行为:

1)、renderscriptTargetApi - 指定要生成的字节码版本。 我们建议您将此值设置为能够提供您正在使用的所有功能的最低API级别,并将renderscriptSupportModeEnabled设置为true。 此设置的有效值是从11到最近发布的API级别的任何整数值。 如果您的应用程序清单中指定的最小SDK版本设置为不同的值,则该值将被忽略,并且构建文件中的目标值用于设置最低SDK版本。

2)、renderscriptSupportModeEnabled - 指定生成的字节码如果运行的设备不支持目标版本,则应该恢复到兼容版本。

3)、buildToolsVersion - 使用Android SDK构建工具的版本。 该值应设置为18.1.0或更高。 如果未指定此选项,则使用最高版本的构建工具版本。 您应始终设置此值,以确保具有不同配置的开发机器之间的构建一致性。


3、在使用RenderScript的应用程序类中,为支持库类添加导入:

import android.support.v8.renderscript.*;


三、使用Java代码中的RenderScript

使用Java代码中的RenderScript依赖于android.renderscript或android.support.v8.renderscript包中的API类。 大多数应用程序遵循相同的基本使用模式:

1、初始化RenderScript上下文。 使用create(Context)创建的RenderScript上下文确保可以使用RenderScript,并提供一个对象来控制所有后续RenderScript对象的生命周期。 您应该将上下文创建视为潜在的长期运行操作,因为它可能会在不同的硬件上创建资源; 如果可能的话,它不应该在应用程序的关键路径中。 通常,应用程序一次只能有一个RenderScript上下文。


2、至少创建一个要传递给脚本的分配。 分配是一种为固定数量的数据提供存储的RenderScript对象。 脚本中的内核将Allocation对象作为其输入和输出,并且可以使用rsGetElementAt_type()和rsSetElementAt_type()绑定为脚本全局变量时,使用内核访问Allocation对象。 分配对象允许将数组从Java代码传递到RenderScript代码,反之亦然。 分配对象通常使用createTyped()或createFromBitmap()创建。


3、创建任何必要的脚本。 使用RenderScript时可以使用两种类型的脚本:

 1)、ScriptC:这些是用户定义的脚本,如上面写入RenderScript内核所述。 每个脚本都有一个由RenderScript编译器反映的Java类,以便从Java代码访问脚本; 该类的名称为ScriptC_filename。 例如,如果上面的映射内核位于invert.rs中,并且RenderScript上下文已经位于mRenderScript中,则用于实例化脚本的Java代码将是:

ScriptC_invert invert = new ScriptC_invert(mRenderScript);

ScriptIntrinsic:这些内置的RenderScript内核用于常见操作,如高斯模糊,卷积和图像混合。 有关更多信息,请参阅ScriptIntrinsic的子类。


4、使用数据填充分配。 除了使用createFromBitmap()创建的分配之外,首先创建一个分配的空的数据。 要填充分配,请使用分配中的“复制”方法之一。 “复制”方法是同步的。


5、设置任何必要的脚本全局变量。 您可以使用名为set_globalname的相同ScriptC_filename类中的方法设置全局变量。 例如,为了设置名为threshold的int变量,请使用Java方法set_threshold(int); 并且为了设置名为lookup的rs_allocation变量,请使用Java方法set_lookup(Allocation)。 集合方法是异步的。


6、启动适当的内核和调用功能。

启动给定内核的方法反映在相同的ScriptC_filename类中,方法名为forEach_mappingKernelName()或reduce_reductionKernelName()。 这些启动是异步的。 根据内核的参数,该方法需要一个或多个分配,所有这些分配必须具有相同的维度。 默认情况下,内核在这些维度中的每个坐标上执行; 在这些坐标的一个子集上执行一个内核,将适当的Script.LaunchOptions作为forEach或reduce方法的最后一个参数传递。


使用反映在相同ScriptC_filename类中的invoke_functionName方法启动可调用函数。 这些启动是异步的。


7、从Allocation对象和javaFutureType对象中检索数据。 为了从Java代码分配中访问数据,您必须使用“分配”中的“复制”方法之一将该数据复制回Java。 为了获得还原内核的结果,必须使用javaFutureType.get()方法。 “copy”和get()方法是同步的。


8、撕下RenderScript上下文。 您可以使用destroy()或通过允许RenderScript上下文对象进行垃圾回收来销毁RenderScript上下文。 这导致任何进一步使用属于该上下文的任何对象来抛出异常。


一)、异步执行模型

反映的forEach,invoke,reduce和set方法是异步的 - 每个可以在完成请求的操作之前返回Java。 但是,单个操作按照它们的启动顺序进行序列化。


分配类提供“复制”方法来将数据复制到分配中或从分配中复制数据。 “复制”方法是同步的,并且相对于触摸相同分配的上述任何异步操作而被序列化。


反映的javaFutureType类提供了一个get()方法来获得缩减的结果。 get()是同步的,并且相对于缩减(这是异步的)被序列化。



四、单源RenderScript

Android 7.0(API第24级)引入了一种新的编程功能,名为Single-Source RenderScript,其中内核从定义的脚本启动,而不是从Java启动。 这种方法目前仅限于映射内核,在本节中简称为“内核”,以便简化。 此新功能还支持从脚本内部创建类型为rs_allocation的分配。 即使需要多个内核启动,现在也可以在脚本中实现整个算法。 好处是双重的:更可读的代码,因为它保持一种语言的算法的实现; 并且可能更快的代码,因为在多个内核启动之间的Java和RenderScript之间的转换较少。


在单源RenderScript中,您可以按照“编写RenderScript内核”中的说明编写内核。 然后,您可以编写一个调用rsForEach()来启动它们的调用函数。 该API将内核函数作为第一个参数,接着是输入和输出分配。 类似的API rsForEachWithOptions()接受一个类型为rs_script_call_t的额外参数,该参数指定要从内核函数处理的输入和输出分配中的元素的子集。


要启动RenderScript计算,可以从Java调用invokable函数。 按照从Java代码使用RenderScript中的步骤。 在启动适当的内核的步骤中,使用invoke_function_name()调用invokable函数,这将启动整个计算,包括启动内核。


通常需要分配来保存并将中间结果从一个内核启动传递到另一个内核。 您可以使用rsCreateAllocation()创建它们。 该API的一个易于使用的形式是rsCreateAllocation_ <T> <W>(...),其中T是元素的数据类型,W是元素的向量宽度。 API将X,Y和Z的大小作为参数。 对于1D或2D分配,可以省略尺寸为Y或Z的尺寸。 例如,rsCreateAllocation_uchar4(16384)创建了一个16384个元素的1D分配,每个元素的类型为uchar4。


分配由系统自动管理。 您不必明确释放它们。 但是,您可以调用rsClearObject(rs_allocation * alloc)来指示您不再需要对底层分配的handle分配,以便系统尽早释放资源。


编写RenderScript内核部分包含反转图像的示例内核。 下面的示例扩展了使用单源渲染脚本将多个效果应用于图像。 它包括另一个内核,灰度,将彩色图像变成黑白。 可调用函数process()然后将这两个内核连续地应用于输入图像,并产生输出图像。 输入和输出的分配作为rs_allocation类型的参数传递。

// File: singlesource.rs

#pragma version(1)
#pragma rs java_package_name(com.android.rssample)

static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f};

uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) {
  uchar4 out = in;
  out.r = 255 - in.r;
  out.g = 255 - in.g;
  out.b = 255 - in.b;
  return out;
}

uchar4 RS_KERNEL greyscale(uchar4 in) {
  const float4 inF = rsUnpackColor8888(in);
  const float4 outF = (float4){ dot(inF, weight) };
  return rsPackColorTo8888(outF);
}

void process(rs_allocation inputImage, rs_allocation outputImage) {
  const uint32_t imageWidth = rsAllocationGetDimX(inputImage);
  const uint32_t imageHeight = rsAllocationGetDimY(inputImage);
  rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight);
  rsForEach(invert, inputImage, tmp);
  rsForEach(greyscale, tmp, outputImage);
}

您可以从Java调用process()函数,如下所示:

// File SingleSource.java

RenderScript RS = RenderScript.create(context);
ScriptC_singlesource script = new ScriptC_singlesource(RS);
Allocation inputAllocation = Allocation.createFromBitmapResource(
    RS, getResources(), R.drawable.image);
Allocation outputAllocation = Allocation.createTyped(
    RS, inputAllocation.getType(),
    Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT);
script.invoke_process(inputAllocation, outputAllocation);

该示例显示了如何在RenderScript语言本身中完全实现涉及两个内核启动的算法。 没有单源RenderScript,您将不得不从Java代码启动内核,将内核启动与内核定义分开,并使其难以理解整个算法。 单源RenderScript代码不仅易于阅读,还可以消除跨内核启动的Java和脚本之间的转换。 一些迭代算法可能会启动数百次内核,从而使这种转换的开销相当可观。



五、缩小内核深度

减少是将数据集合到一个值中的过程。 这是并行编程中的一个有用的原语,应用如下:

1、计算所有数据的总和或乘积。


2、计算所有数据的逻辑运算(和,或xor)。


3、找到数据中的最小值或最大值。


4、搜索数据中的特定值或特定值的坐标。


在Android 7.0(API级别24)及更高版本中,RenderScript支持缩小内核以允许高效的用户编写的减少算法。 您可以在1,2或3维的输入上启动还原内核。


上面的例子显示了一个简单的addint还原内核。 这是一个更复杂的findMinAndMax还原内核,它可以在一维分配中找到最小和最大长值的位置:

#define LONG_MAX (long)((1UL << 63) - 1)
#define LONG_MIN (long)(1UL << 63)

#pragma rs reduce(findMinAndMax) \
  initializer(fMMInit) accumulator(fMMAccumulator) \
  combiner(fMMCombiner) outconverter(fMMOutConverter)

// Either a value and the location where it was found, or INITVAL.
typedef struct {
  long val;
  int idx;     // -1 indicates INITVAL
} IndexedVal;

typedef struct {
  IndexedVal min, max;
} MinAndMax;

// In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } }
// is called INITVAL.
static void fMMInit(MinAndMax *accum) {
  accum->min.val = LONG_MAX;
  accum->min.idx = -1;
  accum->max.val = LONG_MIN;
  accum->max.idx = -1;
}

//----------------------------------------------------------------------
// In describing the behavior of the accumulator and combiner functions,
// it is helpful to describe hypothetical functions
//   IndexedVal min(IndexedVal a, IndexedVal b)
//   IndexedVal max(IndexedVal a, IndexedVal b)
//   MinAndMax  minmax(MinAndMax a, MinAndMax b)
//   MinAndMax  minmax(MinAndMax accum, IndexedVal val)
//
// The effect of
//   IndexedVal min(IndexedVal a, IndexedVal b)
// is to return the IndexedVal from among the two arguments
// whose val is lesser, except that when an IndexedVal
// has a negative index, that IndexedVal is never less than
// any other IndexedVal; therefore, if exactly one of the
// two arguments has a negative index, the min is the other
// argument. Like ordinary arithmetic min and max, this function
// is commutative and associative; that is,
//
//   min(A, B) == min(B, A)               // commutative
//   min(A, min(B, C)) == min((A, B), C)  // associative
//
// The effect of
//   IndexedVal max(IndexedVal a, IndexedVal b)
// is analogous (greater . . . never greater than).
//
// Then there is
//
//   MinAndMax minmax(MinAndMax a, MinAndMax b) {
//     return MinAndMax(min(a.min, b.min), max(a.max, b.max));
//   }
//
// Like ordinary arithmetic min and max, the above function
// is commutative and associative; that is:
//
//   minmax(A, B) == minmax(B, A)                  // commutative
//   minmax(A, minmax(B, C)) == minmax((A, B), C)  // associative
//
// Finally define
//
//   MinAndMax minmax(MinAndMax accum, IndexedVal val) {
//     return minmax(accum, MinAndMax(val, val));
//   }
//----------------------------------------------------------------------

// This function can be explained as doing:
//   *accum = minmax(*accum, IndexedVal(in, x))
//
// This function simply computes minimum and maximum values as if
// INITVAL.min were greater than any other minimum value and
// INITVAL.max were less than any other maximum value.  Note that if
// *accum is INITVAL, then this function sets
//   *accum = IndexedVal(in, x)
//
// After this function is called, both accum->min.idx and accum->max.idx
// will have nonnegative values:
// - x is always nonnegative, so if this function ever sets one of the
//   idx fields, it will set it to a nonnegative value
// - if one of the idx fields is negative, then the corresponding
//   val field must be LONG_MAX or LONG_MIN, so the function will always
//   set both the val and idx fields
static void fMMAccumulator(MinAndMax *accum, long in, int x) {
  IndexedVal me;
  me.val = in;
  me.idx = x;

  if (me.val <= accum->min.val)
    accum->min = me;
  if (me.val >= accum->max.val)
    accum->max = me;
}

// This function can be explained as doing:
//   *accum = minmax(*accum, *val)
//
// This function simply computes minimum and maximum values as if
// INITVAL.min were greater than any other minimum value and
// INITVAL.max were less than any other maximum value.  Note that if
// one of the two accumulator data items is INITVAL, then this
// function sets *accum to the other one.
static void fMMCombiner(MinAndMax *accum,
                        const MinAndMax *val) {
  if ((accum->min.idx < 0) || (val->min.val < accum->min.val))
    accum->min = val->min;
  if ((accum->max.idx < 0) || (val->max.val > accum->max.val))
    accum->max = val->max;
}

static void fMMOutConverter(int2 *result,
                            const MinAndMax *val) {
  result->x = val->min.idx;
  result->y = val->max.idx;
}

为了运行还原内核,RenderScript运行时创建一个或多个称为累加器数据项的变量来保存还原过程的状态。 RenderScript运行时以选择最大化性能的方式选择累加器数据项的数量。 累加器数据项(accumType)的类型由内核的累加器函数确定 - 该函数的第一个参数是指向累加器数据项的指针。 默认情况下,每个累加器数据项被初始化为零(好像通过memset); 但是,您可以编写一个初始化函数来执行不同的操作。


示例:在添加内核中,累加器数据项(类型int)用于将输入值相加。 没有初始化器功能,每个累加器数据项被初始化为零。


示例:在findMinAndMax内核中,累加器数据项(类型MinAndMax)用于跟踪到目前为止发现的最小值和最大值。 有一个初始化函数分别将它们设置为LONG_MAX和LONG_MIN; 并将这些值的位置设置为-1,表示这些值实际上并未存在于已处理的输入的(空)部分中。


RenderScript会在输入中为每个坐标调用累加器函数一次。 通常,您的功能应该根据输入以某种方式更新累加器数据项。


示例:在添加内核中,累加器函数将输入元素的值添加到累加器数据项。


示例:在findMinAndMax内核中,累加器功能检查输入元素的值是否小于或等于累加器数据项中记录的最小值和/或大于或等于记录在累加器中的最大值 数据项,并相应地更新累加器数据项。


对于输入中的每个坐标,累加器函数被调用一次后,RenderScript必须将累加器数据项组合成一个累加器数据项。 您可以编写组合器功能来执行此操作。 如果累加器函数具有单个输入并且没有特殊参数,则不需要编写组合器函数; RenderScript将使用累加器函数来组合累加器数据项。 (如果这个默认行为不是你想要的,你可能还会写一个组合器功能。)


示例:在addint内核中没有组合器功能,因此将使用累加器函数。 这是正确的行为,因为如果我们将值的集合分成两部分,并且我们分别将这两个数值相加,则将这两个总和相加,与将整个集合相加一样。


示例:在findMinAndMax内核中,组合器功能检查“源”累加器数据项* val中记录的最小值是否小于“目的地”累加器数据项* accum中记录的最小值,并更新* accum因此。 它为最大值做类似的工作。 这将更新*累加到所有输入值已累积到* accum而不是一些进入*。


在所有累加器数据项被组合后,RenderScript确定还原的结果返回到Java。 你可以写一个outconverter函数来做到这一点。 如果希望将合并的累加器数据项的最终值作为减少的结果,则不需要编写outconverter函数。


示例:在添加内核中,没有转换器功能。 组合数据项的最终值是输入的所有元素的总和,这是我们要返回的值。


示例:在findMinAndMax内核中,outconverter函数初始化一个int2结果值,以保存由所有累加器数据项组合而产生的最小值和最大值的位置。


一)、编写一个还原内核

#pragma rs reduce通过指定其名称以及构成内核的函数的名称和角色来定义还原内核。 所有这些功能必须是静态的。 还原内核总是需要一个累加器函数; 您可以省略部分或全部其他功能,具体取决于您希望内核执行的操作。
#pragma rs reduce(kernelName) \
  initializer(initializerName) \
  accumulator(accumulatorName) \
  combiner(combinerName) \
  outconverter(outconverterName)

#pragma中项目的含义如下:
1、reduce(kernelName)(mandatory):指定正在定义一个还原内核。 反映的Java方法reduce_kernelName将启动内核。

2、initializer(initializerName)(可选):指定此还原内核的初始化函数的名称。 启动内核时,RenderScript会为每个累加器数据项调用此函数一次。 该函数必须如下定义:
static void initializerName(accumType *accum) { … }

accum是一个指向累加器数据项的指针,用于此函数初始化。

如果您不提供初始化函数,则RenderScript会将每个累加器数据项初始化为零(如同memset一样),表现为像初始化函数一样,如下所示:
static void initializerName(accumType *accum) {
  memset(accum, 0, sizeof(*accum));
}

3、累加器(cumulativeName)(必需):指定此还原内核的累加器函数的名称。 启动内核时,RenderScript会在输入中为每个坐标调用此功能一次,以某种方式根据输入更新累加器数据项。 该函数必须如下定义:
static void accumulatorName(accumType *accum,
                            in1Type in1, …, inNType inN
                            [, specialArguments]) { … }

accum是指向该函数修改的累加器数据项的指针。 in1到inN是一个或多个参数,它们根据传递给内核启动的输入自动填充,每个输入一个参数。 累加器功能可以选择采用任何特殊参数。

组合器(combinerName)(可选):指定此还原内核的组合器功能的名称。 在RenderScript对输入中的每个坐标调用累加器函数一次后,根据需要调用此函数多次,以将所有累加器数据项组合到单个累加器数据项中。 该函数必须如下定义:
static void combinerName(accumType *accum, const accumType *other) { … }

accum是指向此函数进行修改的“目标”累加器数据项的指针。 另一个是一个指向“source”累加器数据项的指针,用于将此函数“合并”为* accum。

注意:* accum,* other或两者都可能被初始化,但从未传递给累加器函数; 也就是说,一个或两个从未根据任何输入数据进行更新。 例如,在findMinAndMax内核中,组合函数fMMCombiner显式地检查idx <0,因为它指示这样一个累加器数据项,其值为INITVAL。

如果您不提供组合器功能,则RenderScript会在其位置使用累加器函数,表现为像组合器函数如下所示:
static void combinerName(accumType *accum, const accumType *other) {
  accumulatorName(accum, *other);
}

如果内核具有多个输入,如果输入数据类型与累加器数据类型不相同,或者累加器函数需要一个或多个特殊参数,则组合器函数是必需的。

4、outconverter(outconverterName)(可选):指定此缩减内核的outconverter函数的名称。 在RenderScript结合所有累加器数据项之后,它调用此函数来确定还原到Java的结果。 该函数必须如下定义:
static void outconverterName(resultType *result, const accumType *accum) { … }

结果是指向结果数据项的指针(由RenderScript运行时分配而不是由RenderScript运行时初始化),以便此函数使用缩减的结果进行初始化。 resultType是该数据项的类型,不需要与accumType相同。 accum是指向由组合器函数计算的最终累加器数据项的指针。

如果不提供outconverter功能,RenderScript将最终的累加器数据项复制到结果数据项,表现为如果有一个outconverter函数,如下所示:
static void outconverterName(accumType *result, const accumType *accum) {
  *result = *accum;
}

如果您想要的结果类型不同于累加器数据类型,则outconverter函数是强制性的。

请注意,内核具有输入类型,累加器数据项类型和结果类型,不需要相同。 例如,在findMinAndMax内核中,输入类型long,累加器数据项类型MinAndMax和结果类型int2都不同。


你不能假设什么?
您不能依赖于给定内核启动的RenderScript创建的累加器数据项的数量。 不能保证具有相同输入的同一内核的两次启动将创建相同数量的累加器数据项。

您不能依赖于RenderScript调用初始化程序,累加器和组合器函数的顺序; 甚至可能并行调用其中的一些。 不能保证具有相同输入的同一内核的两次启动将遵循相同的顺序。 唯一的保证是只有初始化器功能才会看到未初始化的累加器数据项。 例如:
1、不能保证在累加器函数被调用之前,所有累加器数据项都将被初始化,尽管它只会在初始化的累加器数据项上被调用。

2、输入元素传递给累加器函数的顺序不能保证。

3、在组合器功能被调用之前,不能保证已经为所有输入元件调用了累加器函数。

一个后果是findMinAndMax内核不是确定性的:如果输入包含多个相同最小值或最大值的出现,那么您无法知道内核会发现哪个内容。


你必须保证什么?
因为RenderScript系统可以选择以许多不同的方式执行内核,所以您必须遵循某些规则,以确保您的内核按照所需的方式运行。 如果您不遵守这些规则,您可能会得到不正确的结果,非确定性行为或运行时错误。

下面的规则经常表示两个累加器数据项必须具有“相同的值”。 这是什么意思? 这取决于你想要的内核做什么。 对于诸如addint的数学简化来说,“相同”通常意味着数学平等。 对于“pick any”搜索,例如findMinAndMax(“找到最小和最大输入值的位置”),其中可能出现多个相同的输入值,给定输入值的所有位置必须被视为“相同”。 您可以编写一个类似的内核来“找到最左边的最小和最大输入值的位置”,其中(例如)位置100的最小值优于位置200上相同的最小值; 对于该内核,“相同”意味着相同的位置,而不仅仅是相同的值,并且累加器和组合器函数将不同于findMinAndMax的函数。

1、初始化函数必须创建一个标识值。也就是说,如果I和A是由初始化函数初始化的累加器数据项,并且我从未被传递给累加器函数(但A可能已经被传递),那么:
 1)、combinerName(&A,&I)必须离开与A一样。
 2)、combinerName(&I,&A)I必须离开时与A相同。

示例:在添加内核中,累加器数据项被初始化为零。 该内核的组合器功能执行加法; 零是加法的标识值。

示例:在findMinAndMax内核中,累加器数据项被初始化为INITVAL。
 1)、fMMCombiner(&A,&I)离开A一样,因为I是INITVAL。
 2)、fMMCombiner(&I,&A)将I设置为A,因为I是INITVAL。
因此,INITVAL确实是一个身份值。

2、组合器功能必须是可交换的。 也就是说,如果A和B是初始化函数初始化的累加器数据项,并且可能已被传递给累加器函数为零次或更多次,那么combinerName(&A,&B)必须将A设置为与combinerName(&B ,&A)设置B一样的值.

示例:在添加内核中,组合器函数添加两个累加器数据项值; 附加的是交换。

示例:在findMinAndMax内核中,
fMMCombiner(&A, &B)
是相同的跟:
A = minmax(A, B)
而minmax是可交换的,所以fMMCombiner也是。

也就是说,如果A,B和C是由初始化器函数初始化的累加器数据项,并且可能已经被传递给累加器函数零次或更多次,则以下两个代码序列必须将A设置为相同的值:
combinerName(&A, &B);
combinerName(&A, &C);

combinerName(&B, &C);
combinerName(&A, &B);

示例:在添加内核中,组合器函数添加两个累加器数据项值:
A = A + B
A = A + C
// Same as
//   A = (A + B) + C
B = B + C
A = A + B
// Same as
//   A = A + (B + C)
//   B = B + C
添加是关联的,因此组合器功能也是。

示例:在findMinAndMax内核中,
fMMCombiner(&A, &B)
是相同的跟:
A = minmax(A, B)
所以这两个序列是:
A = minmax(A, B)
A = minmax(A, C)
// Same as
//   A = minmax(minmax(A, B), C)
B = minmax(B, C)
A = minmax(A, B)
// Same as
//   A = minmax(A, minmax(B, C))
//   B = minmax(B, C)
minmax是关联的,所以fMMCombiner也是。


3、累加器功能和组合器功能必须遵守基本的折叠规则。 也就是说,如果A和B是累加器数据项,则A由初始化器函数初始化,并且可能已经被传递给累加器函数零次或更多次,B未被初始化,args是输入参数和特殊的列表 对于累加器函数的特定调用的参数,则以下两个代码序列必须将A设置为相同的值:
accumulatorName(&A, args);  // statement 1
initializerName(&B);        // statement 2
accumulatorName(&B, args);  // statement 3
combinerName(&A, &B);       // statement 4

示例:在添加内核中,对于输入值V:
1、声明1与A + = V相同。
2、声明2与B = 0相同。
3、声明3与B + = V相同,此与B = V相同。
4、声明4与A + = B相同,此与A + = V相同。
语句1和4将A设置为相同的值,因此该内核遵循基本的折叠规则。

示例:在findMinAndMax内核中,对于坐标X处的输入值V:
1、声明1与A = minmax(A,IndexedVal(V,X))相同)。
2、声明2与B = INITVAL相同。
3、声明3相同。
B = minmax(B, IndexedVal(V, X))
这是因为B是初始值,与以下相同:
B = IndexedVal(V, X)
4、声明4与以下相同:
A = minmax(A, B)
此下面的相同:
A = minmax(A, IndexedVal(V, X))
语句1和4将A设置为相同的值,因此该内核遵循基本的折叠规则。


二)、从Java代码调用还原内核
对于在文件filename.rs中定义的名为kernelName的还原内核,ScriptC_filename类中反映了三种方法:
// Method 1
public javaFutureType reduce_kernelName(Allocation ain1, …,
                                        Allocation ainN);

// Method 2
public javaFutureType reduce_kernelName(Allocation ain1, …,
                                        Allocation ainN,
                                        Script.LaunchOptions sc);

// Method 3
public javaFutureType reduce_kernelName(devecSiIn1Type[] in1, …,
                                        devecSiInNType[] inN);

以下是调用添加内核的一些示例:
ScriptC_example script = new ScriptC_example(mRenderScript);

// 1D array
//   and obtain answer immediately
int input1[] = …;
int sum1 = script.reduce_addint(input1).get();  // Method 3

// 2D allocation
//   and do some additional work before obtaining answer
Type.Builder typeBuilder =
  new Type.Builder(RS, Element.I32(RS));
typeBuilder.setX(…);
typeBuilder.setY(…);
Allocation input2 = createTyped(RS, typeBuilder.create());
populateSomehow(input2);  // fill in input Allocation with data
script.result_int result2 = script.reduce_addint(input2);  // Method 1
doSomeAdditionalWork(); // might run at same time as reduction
int sum2 = result2.get();

方法1在内核的累加器函数中的每个输入参数都有一个输入分配参数。 RenderScript运行时检查以确保所有输入分配具有相同的维度,并且每个输入分配的Element类型与累加器函数原型的相应输入参数的Element类型相匹配。 如果任何这些检查失败,RenderScript会引发异常。 内核在这些维度中的每个坐标上执行。

方法2与方法1相同,只是方法2使用可用于将内核执行限制到坐标的子集的附加参数sc。

方法3与方法1相同,只是代替分配输入,它需要Java数组输入。 这是一种方便,可以节省您编写代码以显式创建分配并从Java数组复制数据。 然而,使用方法3而不是方法1不会增加代码的性能。 对于每个输入数组,方法3创建一个具有适当的Element类型和setAutoPadding(boolean)的临时一维分配,并将数组复制到分配,就好像通过适当的分配方式的copyFrom()方法。 然后调用方法1,传递这些临时分配。

注意:如果您的应用程序将使用相同的数组或具有相同维度和元素类型的不同数组进行多个内核调用,则可以通过显式创建,填充和重用分配来提高性能,而不是使用方法3。

javaFutureType是反映缩小方法的返回类型,是ScriptC_filename类中反映的静态嵌套类。 它代表了还原内核运行的未来结果。 要获取运行的实际结果,请调用该类的get()方法,该方法返回类型为javaResultType的值。 get()是同步的。
public class ScriptC_filename extends ScriptC {
  public static class javaFutureType {
    public javaResultType get() { … }
  }
}

javaResultType由outconverter函数的resultType决定。 除非resultType是无符号类型(标量,向量或数组),否则javaResultType是直接对应的Java类型。 如果resultType是一个无符号类型,并且有较大的Java签名类型,那么javaResultType是较大的Java签名类型; 否则,它是直接相应的Java类型。 例如:
1、如果resultType为int,int2或int [15],则javaResultType为int,Int2或int []。 resultType的所有值都可以由javaResultType表示。
2、如果resultType是uint,uint2或uint [15],那么javaResultType是long,Long2或long []。 resultType的所有值都可以由javaResultType表示。
3、如果resultType是ulong,ulong2或ulong [15],那么javaResultType是long,Long2或long []。 resultType的某些值不能由javaResultType表示。

javaFutureType是对应于outconverter函数的resultType的未来结果类型。
1、如果resultType不是数组类型,那么javaFutureType是result_resultType。
2、如果resultType是具有类型为memberType的成员的长度为Count的数组,那么javaFutureType为resultArrayCount_memberType。

例如:
public class ScriptC_filename extends ScriptC {
  // for kernels with int result
  public static class result_int {
    public int get() { … }
  }

  // for kernels with int[10] result
  public static class resultArray10_int {
    public int[] get() { … }
  }

  // for kernels with int2 result
  //   note that the Java type name "Int2" is not the same as the script type name "int2"
  public static class result_int2 {
    public Int2 get() { … }
  }

  // for kernels with int2[10] result
  //   note that the Java type name "Int2" is not the same as the script type name "int2"
  public static class resultArray10_int2 {
    public Int2[] get() { … }
  }

  // for kernels with uint result
  //   note that the Java type "long" is a wider signed type than the unsigned script type "uint"
  public static class result_uint {
    public long get() { … }
  }

  // for kernels with uint[10] result
  //   note that the Java type "long" is a wider signed type than the unsigned script type "uint"
  public static class resultArray10_uint {
    public long[] get() { … }
  }

  // for kernels with uint2 result
  //   note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2"
  public static class result_uint2 {
    public Long2 get() { … }
  }

  // for kernels with uint2[10] result
  //   note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2"
  public static class resultArray10_uint2 {
    public Long2[] get() { … }
  }
}

如果javaResultType是一个对象类型(包括数组类型),则对同一个实例上的每个对javaFutureType.get()的调用将返回相同的对象。

如果javaResultType不能表示类型为resultType的所有值,并且还原内核产生不可映射的值,那么javaFutureType.get()将抛出异常。


方法3和devecSiInXType
devecSiInXType是对应于累加器函数的相应参数的inXType的Java类型。 除非inXType是无符号类型或向量类型,否则devecSiInXType是直接相应的Java类型。 如果inXType是未签名的标量类型,则devecSiInXType是直接对应于相同大小的带符号标量类型的Java类型。 如果inXType是带符号向量类型,则devecSiInXType是直接对应于向量组件类型的Java类型。 如果inXType是无符号向量类型,则devecSiInXType是直接对应于与向量组件类型相同大小的带符号标量类型的Java类型。 例如:
1、如果inXType为int,则devecSiInXType为int。
2、如果inXType是int2,那么devecSiInXType是int。 数组是一个扁平化的表示形式:它具有两倍的标量元素,分配具有2分量矢量元素。 这与分配方式的copyFrom()方法相同。
3、如果inXType是uint,那么deviceSiInXType是int。 Java数组中的有符号值被解释为分配中相同位模式的无符号值。 这与分配方式的copyFrom()方法相同。
4、如果inXType是uint2,那么deviceSiInXType是int。 这是处理int2和uint的方式的组合:数组是一个展平表示,Java数组有符号值被解释为RenderScript无符号元素值。

请注意,对于方法3,输入类型的处理方式与结果类型不同:
1、脚本的向量输入在Java侧平坦化,而脚本的向量结果不是。
2、脚本的无符号输入在Java端表示为相同大小的签名输入,而脚本的无符号结果在Java侧被表示为扩展的签名类型(除了ulong的情况)。

三)、更多示例减少内核
#pragma rs reduce(dotProduct) \
  accumulator(dotProductAccum) combiner(dotProductSum)

// Note: No initializer function -- therefore,
// each accumulator data item is implicitly initialized to 0.0f.

static void dotProductAccum(float *accum, float in1, float in2) {
  *accum += in1*in2;
}

// combiner function
static void dotProductSum(float *accum, const float *val) {
  *accum += *val;
}

// Find a zero Element in a 2D allocation; return (-1, -1) if none
#pragma rs reduce(fz2) \
  initializer(fz2Init) \
  accumulator(fz2Accum) combiner(fz2Combine)

static void fz2Init(int2 *accum) { accum->x = accum->y = -1; }

static void fz2Accum(int2 *accum,
                     int inVal,
                     int x /* special arg */,
                     int y /* special arg */) {
  if (inVal==0) {
    accum->x = x;
    accum->y = y;
  }
}

static void fz2Combine(int2 *accum, const int2 *accum2) {
  if (accum2->x >= 0) *accum = *accum2;
}

// Note that this kernel returns an array to Java
#pragma rs reduce(histogram) \
  accumulator(hsgAccum) combiner(hsgCombine)

#define BUCKETS 256
typedef uint32_t Histogram[BUCKETS];

// Note: No initializer function --
// therefore, each bucket is implicitly initialized to 0.

static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; }

static void hsgCombine(Histogram *accum,
                       const Histogram *addend) {
  for (int i = 0; i < BUCKETS; ++i)
    (*accum)[i] += (*addend)[i];
}

// Determines the mode (most frequently occurring value), and returns
// the value and the frequency.
//
// If multiple values have the same highest frequency, returns the lowest
// of those values.
//
// Shares functions with the histogram reduction kernel.
#pragma rs reduce(mode) \
  accumulator(hsgAccum) combiner(hsgCombine) \
  outconverter(modeOutConvert)

static void modeOutConvert(int2 *result, const Histogram *h) {
  uint32_t mode = 0;
  for (int i = 1; i < BUCKETS; ++i)
    if ((*h)[i] > (*h)[mode]) mode = i;
  result->x = mode;
  result->y = (*h)[mode];
}


27、RenderScript 密集计算:自定义内核与脚本组合 本文深入介绍了如何使用RenderScript进行密集计算,重点讲解了自定义内核的编写与多个脚本(包括Intrinsics)的组合应用。通过实现亮度阈值过滤器和模糊效果,并将其组合为更复杂的图像处理流程,展示了RenderScript在高性能图像处理中的强大能力。同时,文章还探讨了内存分配策略、数据通信机制、多输入输出处理以及性能优化建议,帮助开发者更好地掌握RenderScript的核心技术与最佳实践。 阅读详情

相关推荐

Android GPU计算实战:从RenderScript迁移到OpenGL ES Compute Shader

在移动端图像处理与矩阵运算等计算密集场景中,CPU多核并行受限于功耗与架构,而GPU凭借数千流处理器可提供数量级更高的浮点算力。Compute Shader作为一种在GPU上执行通用计算的着色器技术,将数据并行任务拆分为大量独立线程,通过工作组调度实现高效并发,已成为图形API体系中的重要计算能力。对于Android开发者而言,RenderScript停更后,OpenGL ES 3.1的Compute Shader以更低接入成本和更广设备兼容性,成为承接图像滤镜、卷积、像素级变换等任务的务实选择。本文聚焦计

weixin_32770423的博客 161

Android高效计算——RenderScript

文章目录1 RenderScript简介2 使用RenderScript2.1 环境配置2.2 编写RenderScript文件2.3 在Android framework层调用RenderScript2.4 RenderScript工作流程3 RenderScript运行时层与反射层3.1 RenderScript运行时层3.2 反射层4 内存4.1 内存分配API4.2 静态内存4.2.1 ...

Coder蒋的博客 4936

RenderScript简单使用

简单的RenderScript实现照片取反色

Android 高斯模糊之RenderScript

前言:近年来,app端的图片高斯模糊效果备受那些设计师们的青睐,在那些牛逼的APP中,如微信、QQ、网易云音乐等等都有对背景做高斯图模糊的设计,我们的项目设计师也提出了这个要求,我就查了一些文档做了一下,所以有了这篇文章的由来。 目前 咱们Android 上实现高斯模糊效果的方式有以下几种: Java : FastBlur.java ,是由应用非常广泛的 StackBlur 模糊算法实现...

蒙的博客 6537

Android代码-利用 Renderscript 实现的一个简单的模糊效果封装组件

BlurredView Dynamic blur of Image Views for Android. This library is based on RenderScript. You can find more detail about RenderScript here: RenderScript Includes library and small example project. Gradle compile 'com.qiushui:blurredview:0.8.1' Enable RenderScript support mode: defaultConfig { renderscriptTargetApi 19 renderscriptSupportModeEnabled true } How to use You can use app:src="@drawable/XXX" in layout to set the image to be blurred.Or you may use mBlurredView.setBlurredImg(

Android RenderScript高斯模糊

主要介绍了Android RenderScript高斯模糊的相关代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

Android 开发中一些很有用但你不知道的方法

English_Version:https://github.com/jiang111/awesome-android-tips/blob/master/README-en.md这里收集了大家常用的一些Android代码,持续更新中,内容来自自己的平时积累和网络上看到的文章,部分原文地址在最下方。如有错误欢迎指正,如有侵权,请联系我删除。里面可能会有重复内容,请忽略或者提醒我删除。setBackg

Android小码农的博客 1600

Spring AI 实战:5 分钟搭建你的第一个 AI 应用(含完整代码)

能力关键 API适用场景基础对话简单问答、文本生成流式输出实时打字机效果@Bean模型需要调用外部工具结构化输出需要返回 Java 对象多轮对话聊天机器人场景Spring AI 让 Java 开发者可以用最熟悉的方式(Spring Boot + Maven)进入 AI 应用开发领域,不用学新语言,不用换框架,这是它最大的优势。但目前为止,我们的 AI 应用只能回答"它脑子里已有的知识"。如果让它读懂你自己的技术文档、公司内部知识库呢?这就是下一篇文章的主题——RAG(检索增强生成)实战。

qq_17204557的博客 643

第三部分:Android 应用程序接口指南---第五节:计算---第一章 RenderScript

第1章 RenderScript RenderScript提供一个独立于平台并在本地运行的计算引擎,用它来加速你需要大量计算能力的应用。RenderScript是一个运行与Android上计算密集型的高性能架构。RenderScript主要是面向数据的并行计算,虽然串行计算密集型负载也不错。RenderScript运行时将在所有可用的处理器上并行工作,如多核CPU,GPU或DSP,让你专注于算法...

weixin_34313182的博客 189

中山大学_数据科学与计算机_预推免

前言 2019 年 9 月中旬是中山大学预推免时间,本人没能进到夏令营,但是最后在预推免上岸也算不幸中的万幸。 记忆不一定准确,但希望能帮到他人。 想看 流程 的可以直接看总结,正文部分有本人心路历程。 预推免 中山大学的预推免大致可以分为 报道,机试,面试 三大部分,而且尤其是面试,所有人加起来总时长 3 天,幸运的是,本人被安排在第一天的下午第一场,上午就是机试,报道,机试,面试,无缝衔接。 ...

一把健 5208

基于Dify构建银行智能客服:从意图识别到多RAG架构的实战解析

本文深入解析了如何利用Dify平台构建银行智能客服系统。从传统客服痛点出发,详细阐述了基于意图识别精准分类用户需求,并通过多RAG架构解决知识混淆问题,实现高效、准确的业务响应。文章重点介绍了结合工具调用实现业务流程自动化,并强调了数据本地化部署、内容安全过滤等银行级安全合规实践,为金融行业AI应用提供了实战指南。

yog99的博客 926

Android NDK开发详解RenderScript之RenderScript 概览

原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,您可使用这些平台库管理原生 activity 和访问实体设备组件,例如传感器和触控输入。NDK 可能不适合大多数 Android 编程初学者,这些初学者只需使用 Java 代码和框架 API 开发应用

hnjzfwy的博客 717

基于大模型的脑疝全流程预测与诊疗方案研究

目录一、引言1.1 研究背景与目的1.2 研究意义1.3 大模型技术概述二、脑疝概述2.1 定义与分类2.2 发病机制与病因2.3 临床表现2.4 诊断方法三、大模型在脑疝预测中的应用3.1 数据采集与预处理3.2 模型训练与选择3.3 模型验证与优化四、术前预测与评估4.1 病情严重程度预测4.2 手术风险预测4.3 个性化手术方案制定4.4 麻醉方案制定五、术中监测与决策5.1 实时监测指标5.2 大模型辅助决策5.3 应对突发情况六、术后评估与护理6.1 术后恢复情况评估6.2 并发症风险预测与预防6

嵌入式毕设/课设资料清单(速查手册·文档模板·题库,持续更新)→ 见最新文章 1285

计算:高级RenderScript

因为使用RenderScript的应用程序仍然在Android VM内部运行,所以您可以访问您熟悉的所有框架API,但在适当的时候可以使用RenderScript。 为了促进框架和RenderScript运行时之间的这种交互,还提供了一个代码中间层,以便于在两个级别的代码之间进行通信和内存管理。 本文档将详细介绍有关这些不同层次的代码以及如何在Android VM和RenderScript运行时之

一世豁然的专栏 1044

Python 爬虫教程:抓取交通流量和道路信息

Scrapy是一个强大的爬虫框架,适用于大规模的爬虫任务。它提供了高效的数据抓取和存储功能,支持数据清洗和解析操作。Scrapy特别适合用于抓取开放的API接口和数据流。

2201_76125261的博客 2234

【WRFDA教程第一期】运行观测预处理程序(OBSPROC)

运行观测预处理程序(OBSPROC)

WW、forever的博客 1893

Android Renderscript计算(一)

本文译自:http://developer.android.com/guide/topics/renderscript/compute.html#creating-renderscript 渲染脚本(Renderscript)提供用C语言(C99标准)编写的原生级高性能的计算API。Renderscript让你的应用程序有能力跨越所有可用的处理器内核来自动的平行的运行各种操作。它还提供了对不同类

FireOfStar的专栏 5517

word多级列表为什么会变成黑块解决思路

今天在写文档时,发现WORD5级的标题都变黑块了,找了度娘的资料,终于解决了。 第一种 1、光标bai放在黑快的右边,按向左的箭头,然后ctrl+shift+s,点击重新应用。 2、点击“定义新的多级列表”(不知道从哪儿找这个按钮的百度下),直接点确定。 3、这里很重要!!!不要直接保存,而是直接关掉word,会提示保存还是不保存,这个地方再点击保存。 第二种 Word2016 使用宏代码修复方法 1.找到视图-宏,打开 查看宏 2.创建宏 3.贴...

霍力强的专栏 8580

`android.renderscript` 是 Android 提供的一个用于高性能计算(如图像处理、计算机视觉、物理模拟等)的框架,基于 C99 风格的 Renderscript 语言

`android.renderscript` 是 Android 提供的一个用于高性能计算(如图像处理、计算机视觉、物理模拟等)的框架,基于 C99 风格的 Renderscript 语言(`.rs` 文件),运行在设备的 CPU/GPU/ DSP 上。但需注意:**自 Android 12(API 31)起,RenderScript 已被官方弃用(deprecated),且从 Android 13(API 33)开始,系统不再支持 RenderScript 运行时(`libRSSupport.so` 等被

[Blog][Domain] programb.blog.youkuaiyun.com 604

《SOC架构-ARM架构-文档导读》-目录

小白请看“Armv8/Armv9架构从入门到精通”专栏。以翻译为主的,深度深度深度深度/深入深入深入 学习ARM架构的文章。

baron-周贺贺-代码改变世界ctw 2083

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

Android高斯模糊实战:用RenderScript处理Bitmap的TaoToken配置指南

Android高斯模糊用RenderScript处理Bitmap时,先缩小再模糊可大幅降低真机耗时。本文给出可复制的BlurHelper初始化与半径配置片段,并通过TaoToken统一Key通道完成依赖校验,附401、local proxy failed等报错排查。详见 https://taotoken.net/?utm_source=taotoken_aicg_blog_end

weixin_35752122的博客 5

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Android图形渲染选型:OpenGL ES、Vulkan与RenderScript深度对比

在移动端图形处理领域,GPU渲染效率与API选型直接决定应用性能。从早期以易用性见长的OpenGL ES,到主打显式控制和低开销的Vulkan,再到已遭废弃的RenderScript,三种图形API分别代表了Android平台不同阶段的渲染路线。理解它们背后的设计哲学与适用边界,是进行高性能渲染优化的前提。OpenGL ES凭借成熟生态和较低门槛仍适合大多数中轻度场景,而Vulkan凭借极低的CPU提交开销和精细同步控制,成为游戏引擎与复杂渲染器的首选。若涉及纯计算任务,OpenGL ES Compute

weixin_28278377的博客 271

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

android 开源 高斯模糊_android高斯模糊特效

参考文献:http://blog.youkuaiyun.com/huli870715/article/details/39378349http://blog.youkuaiyun.com/grp0916/article/details/504947121. RenderScriptRenderScript是由Android3.0引入,用来在Android上编写高性能代码的一种语言(使用C99标准)。RenderScrip...

weixin_39625258的博客 387

h第一个程序 As 使用RenderScripted

RenderScript 不仅可以用来画图,而且可以用来做密集的计算操作。目前的API可以使用到的是利用CPU的核心优势来帮我们做计算。在未来,可能会包括GPU和DSP处理器上做精密计算。 创建一个Compute  的RenderScript    下面有一张图详细的介绍了勾划了一个Compute 的 RenderScript:   图解:Andriod 有一个Render

zhuiqiuk的专栏 807

【Android RenderScript】RenderScript 简介 ② ( RenderScript 引入 | RenderScript 简介 )

一、RenderScript 引入、 二、RenderScript 简介、 三、RenderScript 跨硬件平台

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 2189
上一篇: FFMPEG学习【libavcodec】:编解码器:硬件加速器桥:XvMC
下一篇: 计算:高级RenderScript
一世豁然
博客等级 码龄15年 1276粉丝 146原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值