【问题标题】:Render script rendering is much slower than OpenGL rendering on Android渲染脚本渲染比 Android 上的 OpenGL 渲染慢很多
【发布时间】:2014-02-14 08:25:43
【问题描述】:

背景:

我想根据 Android 相机应用的代码添加实时滤镜。但 Android 相机应用的架构是基于 OpenGL ES 1.x。我需要使用着色器来自定义我们的过滤器实现。但是,将相机应用程序更新到 OpenGL ES 2.0 太难了。然后我必须找到一些其他方法来实现实时过滤器而不是OpenGL。经过一番研究,我决定使用渲染脚本。

问题:

我已经编写了一个简单的渲染脚本过滤器的演示。它表明 fps 比通过 OpenGL 实现它要低得多。大约 5 fps 与 15 fps。

问题:

  1. Android 官方站外表示:RenderScript 运行时将并行处理设备上所有可用处理器的工作,例如多核 CPU、GPU 或 DSP,让您可以专注于表达算法而不是调度工作或负载均衡。那为什么渲染脚本执行慢呢?

  2. 如果渲染脚本不能满足我的要求,有没有更好的方法?

代码详情:

您好,我和提问者在同一个团队。我们想编写一个基于渲染脚本的实时滤镜相机。在我们的测试演示项目中,我们使用了一个简单的过滤器:添加了一个覆盖过滤器 ScriptC 脚本的 YuvToRGB IntrinsicScript。 在 OpenGL 版本中,我们将相机数据设置为纹理,并使用着色器进行图像过滤处理。像这样:

    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureYHandle);
    GLES20.glUniform1i(shader.uniforms.get("uTextureY"), 0);
    GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, mTextureWidth,
            mTextureHeight, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
            mPixelsYBuffer.position(0));

在 RenderScript 版本中,我们将相机数据设置为 Allocation,并使用 script-kernals 执行 image-filter-procss。像这样:

    // The belowing code is from onPreviewFrame(byte[] data, Camera camera) which gives the camera frame data 
    byte[] imageData = datas[0];
    long timeBegin = System.currentTimeMillis();
    mYUVInAllocation.copyFrom(imageData);

    mYuv.setInput(mYUVInAllocation);
    mYuv.forEach(mRGBAAllocationA);
    // To make sure the process of YUVtoRGBA has finished!
    mRGBAAllocationA.copyTo(mOutBitmap);    
    Log.e(TAG, "RS time: YUV to RGBA : " + String.valueOf((System.currentTimeMillis() - timeBegin)));   

    mLayerScript.forEach_overlay(mRGBAAllocationA, mRGBAAllocationB);
    mRGBAAllocationB.copyTo(mOutBitmap);    
    Log.e(TAG, "RS time: overlay : " + String.valueOf((System.currentTimeMillis() - timeBegin)));

    mCameraSurPreview.refresh(mOutBitmap, mCameraDisplayOrientation, timeBegin);

这两个问题是: (1) RenderScript 进程似乎比 OpenGL 进程慢。 (2) 根据我们的时间日志,使用内部脚本的YUV到RGBA的过程非常快,大约需要6ms;但是使用 scriptC 的覆盖过程非常慢,大约需要 180 毫秒。这是怎么发生的?

这是我们使用的ScriptC(mLayerScript)的rs-kernal代码:

#pragma version(1)
#pragma rs java_package_name(**.renderscript)
#pragma stateFragment(parent)

#include "rs_graphics.rsh"

static rs_allocation layer;
static uint32_t dimX;
static uint32_t dimY;

void setLayer(rs_allocation layer1) {
    layer = layer1;
}

void setBitmapDim(uint32_t dimX1, uint32_t dimY1) {
    dimX = dimX1;
    dimY = dimY1;
}

static float BlendOverlayf(float base, float blend) {
    return (base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend)));
}

static float3 BlendOverlay(float3 base, float3 blend) {
    float3 blendOverLayPixel = {BlendOverlayf(base.r, blend.r), BlendOverlayf(base.g, blend.g), BlendOverlayf(base.b, blend.b)};
    return blendOverLayPixel;
}

uchar4 __attribute__((kernel)) overlay(uchar4 in, uint32_t x, uint32_t y) {
    float4 inPixel = rsUnpackColor8888(in);

    uint32_t layerDimX = rsAllocationGetDimX(layer);
    uint32_t layerDimY = rsAllocationGetDimY(layer);

    uint32_t layerX = x * layerDimX / dimX;
    uint32_t layerY = y * layerDimY / dimY;

    uchar4* p = (uchar4*)rsGetElementAt(layer, layerX, layerY);
    float4 layerPixel = rsUnpackColor8888(*p);

    float3 color = BlendOverlay(inPixel.rgb, layerPixel.rgb);

    float4 outf = {color.r, color.g, color.b, inPixel.a};
    uchar4 outc = rsPackColorTo8888(outf.r, outf.g, outf.b, outf.a);

    return outc;
}

【问题讨论】:

  • 你能分享一下两个版本的代码有什么不同吗?我怀疑问题是将相机中的数据输入 RS。
  • @R.JasonSams 感谢您的回复。我已经编辑了我的问题。并添加了一些代码。
  • 1.不要使用 rsAllocationGetDimX。将它们作为全局变量传递(如 dimX 和 dimY)。 2. 不要忘记常量上的 f 后缀。您现在正在使用双精度。 3. 使用 rsGetElementAt_uchar4,而不是 rsGetElementAt。 4.不要包含rs_graphics.rsh,没必要。 5.考虑缓存layerDimX/DimX为全局(与Y相同)。 6. 尝试#pragma rs_fp_relaxed,如果您不关心严格的 IEEE-754 合规性(NEON 和某些 GPU 需要放松),可以启用一些额外的优化。这些是亮点。
  • Tim 获得了大部分的高分,如果您不需要 rsPackColorTo8888() 所做的范围重新缩放(0-255 与 0-1),您也可以使用 convert_uchar4() 和 convert_float4()。
  • 谢谢蒂姆和杰森。我们将尝试根据您的观点修改我们的代码。你有关于渲染脚本代码优化的文章吗?我们用谷歌搜索这样的文章有点困难。

标签: android opengl-es renderscript


【解决方案1】:

Renderscript 不使用任何 GPU 或 DSP 内核。这是谷歌故意含糊不清的文档所鼓励的一种常见误解。 Renderscript 曾经有一个到 OpenGL ES 的接口,但它已被弃用,并且从未被用于动画壁纸之外。 Renderscript 将使用多个 CPU 内核(如果可用),但我怀疑 Renderscript 将被 OpenCL 取代。

查看 Android SDK 中的 Effects 类和 Effects 演示。它展示了如何使用 OpenGL ES 2.0 着色器将效果应用于图像,而无需编写 OpenGL ES 代码。

http://software.intel.com/en-us/articles/porting-opengl-games-to-android-on-intel-atom-processors-part-1

更新:

当我学会回答一个问题而不是提出一个问题时,这真是太好了,这就是这种情况。您可以从缺乏答案中看到,Renderscript 在 Google 之外几乎没有使用,因为它的奇怪架构忽略了 OpenCL 等行业标准,并且几乎不存在关于它如何实际工作的文档。 尽管如此,我的回答确实引起了 Renderscrpt 开发团队的罕见回应,其中仅包含一个链接,其中实际上包含有关渲染脚本的任何有用信息 - PowerVR GPU 供应商 IMG 的 Alexandru Voica 撰写的这篇文章:

http://withimagination.imgtec.com/index.php/powervr/running-renderscript-efficiently-with-powervr-gpus-on-android

那篇文章有一些很好的信息,对我来说是新的。那里有更多人发布了 cmets,他们无法让 Renderscript 代码在 GPU 上实际运行。

但是,我认为 Google 不再开发 Renderscript 是不正确的。尽管我声明“Renderscript 不使用任何 GPU 或 DSP 内核”。直到最近,我才知道这在 Jelly Bean 版本中发生了变化。 如果其中一位 Renderscript 开发人员能够解释这一点,那就太好了。或者即使他们有一个解释那个或那个列表的公共网页 实际支持哪些 GPU,如何判断您的代码是否实际在 GPU 上运行。

我的看法是,Google 最终会用 OpenCL 取代 Renderscript,我不会花时间开发它。

【讨论】:

  • “不支持 GPU”是完全错误的,目前市场上的每个 Nexus 设备(以及许多其他设备)都附带 RS GPU 驱动程序。
  • 为了做到这一点,他们必须为 Nexus 中使用的每种类型和版本的 GPU 提供 GLSL 着色器编译器,即使这样,Renderscript 代码也不能移植到具有不同 GPU 的 Android 设备- 这会破坏 Google 对可移植性的保证。
  • 这完全不正确。 RS 和 GLSL 没有任何关系;它们是完全独立的用户模式驱动程序堆栈。对于不支持 GPU 的设备,RS 位码可以在 CPU 上运行,或者在存在适当 GPU 时在 GPU 上运行。开发人员不必提供多个源文件或类似的东西。 (来源:我从事 RS 运行时、驱动程序模型和 API)
  • 你一直在暗示我没有写的东西。我要告诉你的是,在任何 GPU 上运行代码都需要用于所述 GPU 的编译器。有些人(芯片供应商或谷歌)必须为一系列 GPU 类型和版本提供这些编译器。 RS 代码不会在编译器不可用的 GPU 上运行。什么是“合适的 GPU”?谷歌为其提供编译器的那个?您可以访问 RS 上的文档,而我们这些 Google 以外的人则无法访问。 RS 实际支持的 GPU 类型的公共链接怎么样?那会很有帮助。
猜你喜欢
  • 2014-07-25
  • 1970-01-01
  • 2014-01-12
  • 2019-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-24
  • 1970-01-01
相关资源
最近更新 更多