3D 游戏为何如此高效?

对于未来的谷歌员工,这里有一个我从 Quasimondo 移植过来的算法。混合了方块模糊和高斯模糊,非常漂亮,速度也很快。

有些事我一直不明白。一个像 GTA IV 这样的大型 PC 游戏怎么可能使用我50% 的 CPU 并以60fps 的速度运行,而一个旋转的 Teapot@60fps 的 DX 演示却使用了高达30% 的 CPU?

9022 次浏览

例如,您可以进行的一个简单优化包括不实际上试图绘制看不见的东西。考虑一个复杂的场景,比如 侠盗猎车手IV中的城市景观。渲染器实际上并没有渲染所有的建筑物和结构。相反,它只渲染相机能看到的东西。如果你能飞到这些建筑物的后面,面对原来的相机,你会看到一个半建成的空心壳结构。摄像机看不到的每一个点都不是渲染出来的——既然你看不到,就没有必要给你看。

此外,当您针对一组特定的硬件进行开发时,还存在优化的指令和特殊技术,以实现更好的加速。

你的问题的另一部分是为什么一个演示会使用如此多的 CPU:

而旋转茶壶的 DX 演示使用了高达30% 的速度?

编辑(2014年4月) : 这是一个问答页面,似乎仍然获得了很多点击。我知道我总是得到这个职位的支持。但是如果你正在读这篇文章,你需要实现 这里张贴的答案(包括我的和被接受的答案)已经过时了。如果你想实现高效的模糊 今天你应该使用渲染脚本而不是 NDK 或 Java。RenderScript 运行在 Android 2.2 + (使用 Android 支援库)上,所以没有理由不使用它。

旧的答案接踵而至,但要小心,因为它已经过时了。


对于图形 API 的演示(如 dxdemo)来说,当您的硬件不支持显示一个漂亮示例所需的所有特性时,退回到所谓的 软件渲染器软件渲染器是很常见的。这些特性可能包括阴影、反射、光线追踪、物理等等。

对于将来的谷歌人来说,这里有一个我从雅黑的 Quasimondo 端口移植过来的算法,但是使用的是 NDK。当然,这是基于亚赫尔的回答。但是这是运行本地 C 代码的,所以速度更快。快多了。快了40倍。

这模仿了一个完全功能齐全的硬件设备的功能,这是不太可能存在的,为了炫耀 API 的所有功能。但是因为硬件实际上并不存在,所以它运行在你的 CPU 上。这比将任务委托给显卡效率低得多——因此 CPU 使用率很高。

我发现使用 NDK 是所有图像操作应该在 Android 上完成的方式... 一开始实现起来有点烦人(阅读一个关于使用 JNI 和 NDK 给你的很棒的教程) ,但是在很多事情上要好得多,而且接近实时。

缩短,不想花太多时间重构整个事情。

#include <jni.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <android/log.h>
#include <android/bitmap.h>


#define LOG_TAG "libbitmaputils"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)


typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
} rgba;


JNIEXPORT void JNICALL Java_com_insert_your_package_ClassName_functionToBlur(JNIEnv* env, jobject obj, jobject bitmapIn, jobject bitmapOut, jint radius) {
LOGI("Blurring bitmap...");


// Properties
AndroidBitmapInfo   infoIn;
void*               pixelsIn;
AndroidBitmapInfo   infoOut;
void*               pixelsOut;


int ret;


// Get image info
if ((ret = AndroidBitmap_getInfo(env, bitmapIn, &infoIn)) < 0 || (ret = AndroidBitmap_getInfo(env, bitmapOut, &infoOut)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}


// Check image
if (infoIn.format != ANDROID_BITMAP_FORMAT_RGBA_8888 || infoOut.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGE("Bitmap format is not RGBA_8888!");
LOGE("==> %d %d", infoIn.format, infoOut.format);
return;
}


// Lock all images
if ((ret = AndroidBitmap_lockPixels(env, bitmapIn, &pixelsIn)) < 0 || (ret = AndroidBitmap_lockPixels(env, bitmapOut, &pixelsOut)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}


int h = infoIn.height;
int w = infoIn.width;


LOGI("Image size is: %i %i", w, h);


rgba* input = (rgba*) pixelsIn;
rgba* output = (rgba*) pixelsOut;


int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int whMax = max(w, h);
int div = radius + radius + 1;


int r[wh];
int g[wh];
int b[wh];
int rsum, gsum, bsum, x, y, i, yp, yi, yw;
rgba p;
int vmin[whMax];


int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}


yw = yi = 0;


int stack[div][3];
int stackpointer;
int stackstart;
int rbs;
int ir;
int ip;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;


for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = input[yi + min(wm, max(i, 0))];


ir = i + radius; // same as sir


stack[ir][0] = p.red;
stack[ir][1] = p.green;
stack[ir][2] = p.blue;
rbs = r1 - abs(i);
rsum += stack[ir][0] * rbs;
gsum += stack[ir][1] * rbs;
bsum += stack[ir][2] * rbs;
if (i > 0) {
rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];
} else {
routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];
}
}
stackpointer = radius;


for (x = 0; x < w; x++) {


r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];


rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;


stackstart = stackpointer - radius + div;
ir = stackstart % div; // same as sir


routsum -= stack[ir][0];
goutsum -= stack[ir][1];
boutsum -= stack[ir][2];


if (y == 0) {
vmin[x] = min(x + radius + 1, wm);
}
p = input[yw + vmin[x]];


stack[ir][0] = p.red;
stack[ir][1] = p.green;
stack[ir][2] = p.blue;


rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];


rsum += rinsum;
gsum += ginsum;
bsum += binsum;


stackpointer = (stackpointer + 1) % div;
ir = (stackpointer) % div; // same as sir


routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];


rinsum -= stack[ir][0];
ginsum -= stack[ir][1];
binsum -= stack[ir][2];


yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = max(0, yp) + x;


ir = i + radius; // same as sir


stack[ir][0] = r[yi];
stack[ir][1] = g[yi];
stack[ir][2] = b[yi];


rbs = r1 - abs(i);


rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;


if (i > 0) {
rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];
} else {
routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];
}


if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
output[yi].red = dv[rsum];
output[yi].green = dv[gsum];
output[yi].blue = dv[bsum];


rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;


stackstart = stackpointer - radius + div;
ir = stackstart % div; // same as sir


routsum -= stack[ir][0];
goutsum -= stack[ir][1];
boutsum -= stack[ir][2];


if (x == 0) vmin[y] = min(y + r1, hm) * w;
ip = x + vmin[y];


stack[ir][0] = r[ip];
stack[ir][1] = g[ip];
stack[ir][2] = b[ip];


rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];


rsum += rinsum;
gsum += ginsum;
bsum += binsum;


stackpointer = (stackpointer + 1) % div;
ir = stackpointer; // same as sir


routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];


rinsum -= stack[ir][0];
ginsum -= stack[ir][1];
binsum -= stack[ir][2];


yi += w;
}
}


// Unlocks everything
AndroidBitmap_unlockPixels(env, bitmapIn);
AndroidBitmap_unlockPixels(env, bitmapOut);


LOGI ("Bitmap blurred.");
}


int min(int a, int b) {
return a > b ? b : a;
}


int max(int a, int b) {
return a > b ? a : b;
}

作为参考,使用 Yahel 的 Java 函数,我花了10秒钟模糊了480x532像素的图像,模糊半径为10。但是使用原生 C 版本需要250毫秒。而且我很确定它仍然可以进一步优化... ... 我只是对 Java 代码做了一个愚蠢的转换,可能有一些操作可以缩短,不想花太多时间重构整个事情。

#include <jni.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <android/log.h>
#include <android/bitmap.h>


#define LOG_TAG "libbitmaputils"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)


typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
} rgba;


JNIEXPORT void JNICALL Java_com_insert_your_package_ClassName_functionToBlur(JNIEnv* env, jobject obj, jobject bitmapIn, jobject bitmapOut, jint radius) {
LOGI("Blurring bitmap...");


// Properties
AndroidBitmapInfo   infoIn;
void*               pixelsIn;
AndroidBitmapInfo   infoOut;
void*               pixelsOut;


int ret;


// Get image info
if ((ret = AndroidBitmap_getInfo(env, bitmapIn, &infoIn)) < 0 || (ret = AndroidBitmap_getInfo(env, bitmapOut, &infoOut)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}


// Check image
if (infoIn.format != ANDROID_BITMAP_FORMAT_RGBA_8888 || infoOut.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGE("Bitmap format is not RGBA_8888!");
LOGE("==> %d %d", infoIn.format, infoOut.format);
return;
}


// Lock all images
if ((ret = AndroidBitmap_lockPixels(env, bitmapIn, &pixelsIn)) < 0 || (ret = AndroidBitmap_lockPixels(env, bitmapOut, &pixelsOut)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}


int h = infoIn.height;
int w = infoIn.width;


LOGI("Image size is: %i %i", w, h);


rgba* input = (rgba*) pixelsIn;
rgba* output = (rgba*) pixelsOut;


int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int whMax = max(w, h);
int div = radius + radius + 1;


int r[wh];
int g[wh];
int b[wh];
int rsum, gsum, bsum, x, y, i, yp, yi, yw;
rgba p;
int vmin[whMax];


int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}


yw = yi = 0;


int stack[div][3];
int stackpointer;
int stackstart;
int rbs;
int ir;
int ip;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;


for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = input[yi + min(wm, max(i, 0))];


ir = i + radius; // same as sir


stack[ir][0] = p.red;
stack[ir][1] = p.green;
stack[ir][2] = p.blue;
rbs = r1 - abs(i);
rsum += stack[ir][0] * rbs;
gsum += stack[ir][1] * rbs;
bsum += stack[ir][2] * rbs;
if (i > 0) {
rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];
} else {
routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];
}
}
stackpointer = radius;


for (x = 0; x < w; x++) {


r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];


rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;


stackstart = stackpointer - radius + div;
ir = stackstart % div; // same as sir


routsum -= stack[ir][0];
goutsum -= stack[ir][1];
boutsum -= stack[ir][2];


if (y == 0) {
vmin[x] = min(x + radius + 1, wm);
}
p = input[yw + vmin[x]];


stack[ir][0] = p.red;
stack[ir][1] = p.green;
stack[ir][2] = p.blue;


rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];


rsum += rinsum;
gsum += ginsum;
bsum += binsum;


stackpointer = (stackpointer + 1) % div;
ir = (stackpointer) % div; // same as sir


routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];


rinsum -= stack[ir][0];
ginsum -= stack[ir][1];
binsum -= stack[ir][2];


yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = max(0, yp) + x;


ir = i + radius; // same as sir


stack[ir][0] = r[yi];
stack[ir][1] = g[yi];
stack[ir][2] = b[yi];


rbs = r1 - abs(i);


rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;


if (i > 0) {
rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];
} else {
routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];
}


if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
output[yi].red = dv[rsum];
output[yi].green = dv[gsum];
output[yi].blue = dv[bsum];


rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;


stackstart = stackpointer - radius + div;
ir = stackstart % div; // same as sir


routsum -= stack[ir][0];
goutsum -= stack[ir][1];
boutsum -= stack[ir][2];


if (x == 0) vmin[y] = min(y + r1, hm) * w;
ip = x + vmin[y];


stack[ir][0] = r[ip];
stack[ir][1] = g[ip];
stack[ir][2] = b[ip];


rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];


rsum += rinsum;
gsum += ginsum;
bsum += binsum;


stackpointer = (stackpointer + 1) % div;
ir = stackpointer; // same as sir


routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];


rinsum -= stack[ir][0];
ginsum -= stack[ir][1];
binsum -= stack[ir][2];


yi += w;
}
}


// Unlocks everything
AndroidBitmap_unlockPixels(env, bitmapIn);
AndroidBitmap_unlockPixels(env, bitmapOut);


LOGI ("Bitmap blurred.");
}


int min(int a, int b) {
return a > b ? b : a;
}


int max(int a, int b) {
return a > b ? a : b;
}

然后像这样使用它(考虑一个名为 com.insert. your.package 的类。ClassName 和一个名为 FunctionToBlur 的本机函数,如上述代码所述) :

// Create a copy
Bitmap bitmapOut = bitmapIn.copy(Bitmap.Config.ARGB_8888, true);
// Blur the copy
functionToBlur(bitmapIn, bitmapOut, __radius);

然后像这样使用它(考虑一个名为 com.insert. your.package 的类。ClassName 和一个名为 FunctionToBlur 的本机函数,如上述代码所述) :

// Create a copy
Bitmap bitmapOut = bitmapIn.copy(Bitmap.Config.ARGB_8888, true);
// Blur the copy
functionToBlur(bitmapIn, bitmapOut, __radius);

它需要一个 RGB _ 8888位图!

它需要一个 RGB _ 8888位图!

要使用 RGB _ 565位图,要么在传递参数之前创建一个转换后的副本(恶心) ,要么将函数改为使用新的 rgb565类型而不是 rgba:

typedef struct {
uint16_t byte0;
} rgb565;

要使用 RGB _ 565位图,要么在传递参数之前创建一个转换后的副本(恶心) ,要么将函数改为使用新的 rgb565类型而不是 rgba:

typedef struct {
uint16_t byte0;
} rgb565;

问题是,如果你这样做,你不能读取 .red.green.blue的像素了,你需要正确地读取字节,duh。当我需要的时候,我这么做了:

r = (pixels[x].byte0 & 0xF800) >> 8;
g = (pixels[x].byte0 & 0x07E0) >> 3;
b = (pixels[x].byte0 & 0x001F) << 3;

问题是,如果你这样做,你不能读取 .red.green.blue的像素了,你需要正确地读取字节,duh。当我需要的时候,我这么做了:

r = (pixels[x].byte0 & 0xF800) >> 8;
g = (pixels[x].byte0 & 0x07E0) >> 3;
b = (pixels[x].byte0 & 0x001F) << 3;

但是可能有一些不那么愚蠢的方法来做到这一点。恐怕我不是一个低级别的 C 程序员。

Android Blur Guide 2016 Android 模糊指南2016

渲染脚本 rs = RenderScript.create (ctx) ;

使用 Showcase/Benchmark App 来源: Github。 分配输入 = Allocation.createFromBitmap (rs,source,Allocation.MipmapControl.MIPMAP _ NONE,Allocation.USAGE _ SCRIPT) ; 分配输出 = Allocation.createType (rs,input.getType ()) ; 还可以查看我目前正在开发的 Blur 框架: 达利

ScriptintrinsicBlur script = ScriptintrinsicBlur.create (rs,Element.U8 _ 4(rs)) ;

经过大量的实验,我现在可以安全地给你一些可靠的建议,这些建议将使你在使用 Android 框架时在 Android 中的生活更加轻松。

SetRadius (半径) ;

加载和使用缩放的位图(对于非常模糊的图像)

SetInput (input) ;

永远不要使用位图的全尺寸。图像越大,需要的模糊越多,模糊半径也越大,通常,模糊半径越大,算法需要的时间越长。

final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap blurTemplate = BitmapFactory.decodeResource(getResources(), R.drawable.myImage, options);
每个(输出) ; (位图) ; 返回位图; }

这将用 inSampleSize8加载位图,因此只有原始图像的1/64。测试什么样的 inSampleSize适合您的需要,但是保持它为2 ^ n (2,4,8,...) ,以避免由于缩放而降低质量。更多信息请参见谷歌文档

图片,选项) ;

另一个非常大的优势是位图加载非常快。在我早期的模糊测试中,我认为在整个模糊过程中最长的时间是图像加载。因此,从磁盘加载1920x1080的图像,我的 Nexus5需要500毫秒,而模糊只需要250毫秒左右。

这将用 inSampleSize8加载位图,因此只有原始图像的1/64。测试什么样的 inSampleSize适合您的需要,但是保持它为2 ^ n (2,4,8,...) ,以避免由于缩放而降低质量。更多信息请参见谷歌文档

另一个非常大的优势是位图加载非常快。在我早期的模糊测试中,我认为在整个模糊过程中最长的时间是图像加载。因此,从磁盘加载1920x1080的图像,我的 Nexus5需要500毫秒,而模糊只需要250毫秒左右。

使用渲染脚本

使用渲染脚本

渲染脚本提供了高斯模糊滤波器 ScriptIntrinsicBlur。它具有良好的视觉质量,只是最快的现实你在 Android 上得到。谷歌声称自己是 “通常比多线程 C 实现快2-3倍,比 Java 实现快10倍以上”。Renderscript 非常复杂(使用最快的处理设备(GPU、 ISP 等)) ,还有 V8支持库,使其兼容到2.2版本。至少在理论上,通过我自己的测试和其他开发人员的报告,似乎不可能盲目地使用渲染脚本,因为硬件/驱动程序碎片似乎会导致一些设备出现问题,即使是更高的 sdk lvl (例如我在4.1 Nexus S 上遇到了麻烦) ,所以要小心,在很多设备上测试。这里有一个简单的例子可以帮助你开始:

//define this only once if blurring multiple times
RenderScript rs = RenderScript.create(context);


(...)
//this will blur the bitmapOriginal with a radius of 8 and save it in bitmapOriginal
final Allocation input = Allocation.createFromBitmap(rs, bitmapOriginal); //use this constructor for best performance, because it uses USAGE_SHARED mode which reuses memory
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(8f);
script.setInput(input);
script.forEach(output);
output.copyTo(bitmapOriginal);

渲染脚本提供了高斯模糊滤波器 ScriptIntrinsicBlur。它具有良好的视觉质量,是你在安卓系统上实际获得的最快速度。谷歌声称自己是 “通常比多线程 C 实现快2-3倍,比 Java 实现快10倍以上”。Renderscript 非常复杂(使用最快的处理设备(GPU、 ISP 等)) ,还有 V8支持库,使其兼容到2.2版本。至少在理论上,通过我自己的测试和其他开发人员的报告,似乎不可能盲目地使用渲染脚本,因为硬件/驱动程序碎片似乎会导致一些设备出现问题,即使是更高的 sdk lvl (例如我在4.1 Nexus S 上遇到了麻烦) ,所以要小心,在很多设备上测试。这里有一个简单的例子可以帮助你开始:

//define this only once if blurring multiple times
RenderScript rs = RenderScript.create(context);


(...)
//this will blur the bitmapOriginal with a radius of 8 and save it in bitmapOriginal
final Allocation input = Allocation.createFromBitmap(rs, bitmapOriginal); //use this constructor for best performance, because it uses USAGE_SHARED mode which reuses memory
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(8f);
script.setInput(input);
script.forEach(output);
output.copyTo(bitmapOriginal);

在使用 Google “因为它们包括了最新的改进”特别推荐的 Gradle 支持 v8时,只使用 需要添加2行到您的构建脚本,并在当前构建工具(更新的语法为 android Gradle 插件 v14 + )中使用 android.support.v8.renderscript

android {
...
defaultConfig {
...
renderscriptTargetApi 19
renderscriptSupportModeEnabled true
}
}

在使用 Google “因为它们包括了最新的改进”特别推荐的 Gradle 支持 v8时,只使用 需要添加2行到您的构建脚本,并在当前构建工具(更新的语法为 android Gradle 插件 v14 + )中使用 android.support.v8.renderscript

android {
...
defaultConfig {
...
renderscriptTargetApi 19
renderscriptSupportModeEnabled true
}
}

Nexus 5的简单基准测试——比较渲染脚本和其他不同的 java 和渲染脚本实现:

The average runtime per blur on different pic sizes

Nexus 5的简单基准测试——比较渲染脚本和其他不同的 java 和渲染脚本实现:

The average runtime per blur on different pic sizes 不同图片大小下每个模糊的平均运行时间

Megapixels per sec that can be blurred 不同图片大小下每个模糊的平均运行时间

Megapixels per sec that can be blurred 每秒百万像素可以被模糊化

每秒百万像素可以被模糊化

每个值是250轮的平均值。RS_GAUSS_FASTScriptIntrinsicBlur(而且几乎总是最快的) ,其他以 RS_开始的实现大多是使用简单内核的卷积实现。算法的细节可以在这里找到.这不是纯粹的模糊,因为一个很好的部分是测量的垃圾收集。这可以在这里看到(ScriptIntrinsicBlur在一个100x100的图像与约500轮)

enter image description here

每个值是250轮的平均值。RS_GAUSS_FASTScriptIntrinsicBlur(而且几乎总是最快的) ,其他以 RS_开始的实现大多是使用简单内核的卷积实现。算法的细节可以在这里找到.这不是纯粹的模糊,因为一个很好的部分是测量的垃圾收集。这可以在这里看到(ScriptIntrinsicBlur在一个100x100的图像与约500轮)

enter image description here

刺钉是气相色谱。

刺钉是气相色谱。

您可以自己检查,基准应用程序是在播放商店: BlurBenchmark

您可以自己检查,基准应用程序是在播放商店: BlurBenchmark

尽可能重用 Bitmap (如果优先级: 性能 > 内存占用)

尽可能重用 Bitmap (如果优先级: 性能 > 内存占用)

如果你需要一个活动模糊或类似的多重模糊,你的内存允许它不加载位图从绘制多次,但保持它“缓存”在一个成员变量。在这种情况下,总是尝试使用相同的变量,以保持垃圾收集到最低限度。

如果你需要一个活动模糊或类似的多重模糊,你的内存允许它不加载位图从绘制多次,但保持它“缓存”在一个成员变量。在这种情况下,总是尝试使用相同的变量,以保持垃圾收集到最低限度。

还可以从文件或绘图中检出新的 加载时选择 inBitmap,这将重用位图内存并节省垃圾回收时间。

还可以从文件或绘图中检出新的 加载时选择 inBitmap,这将重用位图内存并节省垃圾回收时间。

从清晰到模糊

从清晰到模糊

简单而天真的方法就是使用2个 ImageViews,一个模糊,一个渐变。但是,如果你想要一个更复杂的外观,顺利淡出从锐利到模糊,然后检查 Roman Nurik 在他的 Muzei 应用程序中介绍了如何做到这一点

简单而天真的方法就是使用2个 ImageViews,一个模糊,一个渐变。但是,如果你想要一个更复杂的外观,顺利淡出从锐利到模糊,然后检查 Roman Nurik 在他的 Muzei 应用程序中介绍了如何做到这一点

基本上,他解释说,他预先模糊了一些框架与不同的模糊范围,并使用它们作为关键帧的动画,看起来真的很顺利。

Diagram where Nurik exaplains his approach

中央处理器只能做一些简单的事情——比如人工智能和其他游戏逻辑。

我们尝试在不同的答案中实现上面提到的渲染脚本模糊。我们被限制使用 v8渲染脚本版本,这给我们带来了很多麻烦。

    因此 d 持续存在。

    private final Paint mPaint = new Paint();
    
    
    public Bitmap blur(final String pathToBitmap) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    final Bitmap normalOne = BitmapFactory.decodeFile(pathToBitmap, options);
    final Bitmap resultBitmap = Bitmap.createBitmap(options.outWidth, options.outHeight, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(resultBitmap);
    mPaint.setAlpha(180);
    canvas.drawBitmap(normalOne, 0, 0, mPaint);
    int blurRadius = 12;
    for (int row = -blurRadius; row < blurRadius; row += 2) {
    for (int col = -blurRadius; col < blurRadius; col += 2) {
    if (col * col + row * row <= blurRadius * blurRadius) {
    mPaint.setAlpha((blurRadius * blurRadius) / ((col * col + row * row) + 1) * 2);
    canvas.drawBitmap(normalOne, row, col, mPaint);
    }
    }
    }
    normalOne.recycle();
    return resultBitmap;
    }
    
  • 当我们尝试使用渲染脚本时,三星 S3会随机崩溃
  • 其他设备(跨不同 API)随机显示不同的颜色问题

这个解决方案远远不够完美,但是基于这样一个事实,它创建了一个合理的模糊效果,即它在一个几乎不透明的“清晰”版本上绘制了同一个图像的高透明版本。Α 取决于到原点的距离。

你可以根据自己的需要调整一些“神奇的数字”。

我想要分享我们肮脏的 Java 版本,它很慢,应该在一个单独的线程上完成,如果可能的话,在使用之前,因此要持久化。

private final Paint mPaint = new Paint();


public Bitmap blur(final String pathToBitmap) {
final BitmapFactory.Options options = new BitmapFactory.Options();
final Bitmap normalOne = BitmapFactory.decodeFile(pathToBitmap, options);
final Bitmap resultBitmap = Bitmap.createBitmap(options.outWidth, options.outHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(resultBitmap);
mPaint.setAlpha(180);
canvas.drawBitmap(normalOne, 0, 0, mPaint);
int blurRadius = 12;
for (int row = -blurRadius; row < blurRadius; row += 2) {
for (int col = -blurRadius; col < blurRadius; col += 2) {
if (col * col + row * row <= blurRadius * blurRadius) {
mPaint.setAlpha((blurRadius * blurRadius) / ((col * col + row * row) + 1) * 2);
canvas.drawBitmap(normalOne, row, col, mPaint);
}
}
}
normalOne.recycle();
return resultBitmap;
}

这个解决方案远远不够完美,但是基于这样一个事实,它创建了一个合理的模糊效果,即它在一个几乎不透明的“清晰”版本上绘制了同一个图像的高透明版本。Α 取决于到原点的距离。

我只是想把这个“解决方案”分享给那些对渲染脚本的 v8支持版本有疑问的人。

在游戏中你可能会看到类似的效果,但是它们通常是以一种妥协的方式来达到最大化帧速率的目的。这些优化扩展到你在游戏中看到的所有东西。问题变成了,“我们怎样才能用最少的处理能力创造出最壮观、最真实的场景?”这使得游戏程序员成为最好的优化者。

此外,从艺术的角度来看,还有许多节省计算能力的技巧。在许多游戏中,尤其是老游戏中,阴影是预先计算好的,并且会被“烘烤”到地图的纹理中。很多时候,艺术家们试图用平面(两个三角形)来表现像树木和特殊效果这样的东西,尽管它们看起来大致相同。游戏中的雾是一种避免渲染远距离物体的简单方法,而且通常情况下,游戏对远、中、近视图的每个物体都有多种分辨率。

这是为所有人谁需要增加半径的 ScriptIntrinsicBlur获得更难的高斯模糊。

你的位图 > ,真) ; Drawable d = new BitmapDrawable (getResources () ,output) ;

班级:

 public class GaussianBlur {
private final int DEFAULT_RADIUS = 25;
private final float DEFAULT_MAX_IMAGE_SIZE = 400;


private Context context;
private int radius;
private float maxImageSize;


public GaussianBlur(Context context) {
this.context = context;
setRadius(DEFAULT_RADIUS);
setMaxImageSize(DEFAULT_MAX_IMAGE_SIZE);
}


public Bitmap render(Bitmap bitmap, boolean scaleDown) {
RenderScript rs = RenderScript.create(context);


if (scaleDown) {
bitmap = scaleDown(bitmap);
}


Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);


Allocation inAlloc = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE);
Allocation outAlloc = Allocation.createFromBitmap(rs, output);


ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, inAlloc.getElement()); // Element.U8_4(rs));
script.setRadius(getRadius());
script.setInput(inAlloc);
script.forEach(outAlloc);
outAlloc.copyTo(output);


rs.destroy();


return output;
}


public Bitmap scaleDown(Bitmap input) {
float ratio = Math.min((float) getMaxImageSize() / input.getWidth(), (float) getMaxImageSize() / input.getHeight());
int width = Math.round((float) ratio * input.getWidth());
int height = Math.round((float) ratio * input.getHeight());


return Bitmap.createScaledBitmap(input, width, height, true);
}


public int getRadius() {
return radius;
}


public void setRadius(int radius) {
this.radius = radius;
}


public float getMaxImageSize() {
return maxImageSize;
}


public void setMaxImageSize(float maxImageSize) {
this.maxImageSize = maxImageSize;
}
}
  • 避免做大量的渲染状态开关(再次批处理类似的顶点数据) ,因为这会导致 GPU 停止
  • 旋转你的纹理,并确保它们是两个的力量-这提高了纹理缓存在 GPU 的性能。
  • 尽可能多地使用细节级别——低/中/高版本的3D 模型,并根据与摄像机播放器的距离进行切换——如果屏幕上只有5个像素,就没有必要渲染高分辨率版本。
  • 优化实际的执行是什么使游戏更有效率,但这只是一个线性优化。

    • 自定义内存管理。对齐内存,较少碎片。
    • 因为它将大量内容放入到您的名称空间中(可能会隐藏以前导入的其他对象,而您不会知道)。

    • 因为您不知道究竟导入了什么,也不容易找到从哪个模块导入了某个特定的东西(可读性)。

    • 自定义数据结构(即没有 STL,相对最小的模板)。
    • 装配到位,主要用于 SIMD。
  • 因为您不能使用像 pyflakes这样的很酷的工具来静态地检测代码中的错误。

  • Ytech 公司编写了一个令人印象深刻的图形演示程序,使 nVidia 能够在一次贸易展览会上“炫耀”。由此产生的印象,使他们的腿,创造了什么将成为 FarCry。

    虽然这里的许多答案提供了很好的 怎么做的迹象,我将回答更简单的问题 为什么

  • GTA4 第一周就赚了4亿美元
  • Crytech 公司编写了一个非常令人印象深刻的图形演示,使 nVidia 能够在一次贸易展览会上“炫耀”。由此产生的印象,使他们的腿,创造了什么将成为 FarCry。
  • 阀门的 2005年收入和营业利润分别报价为7000万美元和5500万美元。
  • 也许最好的例子(当然最著名的例子之一)是 Id 软件。他们很早就意识到,在 基恩指挥官时代(远早于3D 时代) ,他们想出了一个聪明的方法来实现 1,即使它依赖于现代硬件(在这种情况下是 EGA 显卡!)比竞争对手更有优势这会让你的游戏脱颖而出。这是事实,但他们进一步意识到,他们可以获得技术许可,从而从他人那里获得收入,同时能够开发下一代引擎,从而再次超越竞争对手,而不是必须自己开发新游戏和内容。

    也许最好的例子(当然最著名的例子之一)是 Id 软件。他们很早就意识到,在 基恩指挥官时代(远早于3D 时代) ,他们想出了一个聪明的方法来实现 1,即使它依赖于现代硬件(在这种情况下是 EGA 显卡!)比竞争对手更有优势这会让你的游戏脱颖而出。这是事实,但他们进一步意识到,他们可以获得技术许可,从而从他人那里获得收入,同时能够开发下一代引擎,从而再次超越竞争对手,而不是必须自己开发新游戏和内容。

    这些程序员的能力(加上业务头脑)使他们富有。

    这些程序员的能力(加上业务头脑)使他们富有。

    也就是说,激励这些人的不一定是金钱。这很可能是同样多的渴望去实现,去完成。他们早期赚的钱只是意味着他们现在有时间专注于自己喜欢的事情。虽然许多人都有 外部利益几乎所有仍然程序,并试图找出方法来做得比上一次迭代更好。

    也就是说,激励这些人的不一定是金钱。这很可能是同样多的渴望去实现,去完成。他们早期赚的钱只是意味着他们现在有时间专注于自己喜欢的事情。虽然许多人都有 外部利益几乎所有仍然程序,并试图找出方法来做得比上一次迭代更好。

    简而言之,撰写茶壶演示的人可能有以下一个或多个问题:

      简而言之,撰写茶壶演示的人可能有以下一个或多个问题:

      • 更少的时间
      • 更少的时间
      • 更少的资源
      • 更少的资源
      • 更少的奖励激励
      • 更少的奖励激励
      • 减少内部和外部竞争
      • 减少内部和外部竞争
      • 较小的目标
      • 较小的目标
      • 没那么有天赋
    • less talent

    最后一个可能听起来很严厉,但是很明显有一些人比其他人更好,钟形曲线有时有极端的结束,他们往往被吸引到相应的极端结束什么是做的技能。

    最后一个可能听起来很严厉,但是很明显有一些人比其他人更好,钟形曲线有时有极端的结束,他们往往会被吸引到相应的极端结束什么是做的技能。

    一个较小的目标实际上可能是主要原因。茶壶演示的目标就是这个,一个演示。但不是程序员 技术3的演示。这将是一个(大)操作系统的一个小方面的演示,在这种情况下 DX 渲染。

    一个较小的目标实际上可能是主要原因。茶壶演示的目标就是这个,一个演示。但不是程序员 技术3的演示。这将是一个(大)操作系统的一个小方面的演示,在这种情况下 DX 渲染。

    对于那些观看演示的人来说,只要它看起来足够好,它使用的 CPU 比 需要多得多,这并不重要。如果没有受益者,就没有消除浪费的动力。相比之下,一个游戏会喜欢有更好的人工智能,更好的声音,更多的多边形,更多的效果闲置周期。


      对于那些观看演示的人来说,只要它看起来足够好,它使用的 CPU 比 需要多得多,这并不重要。如果没有受益者,就没有消除浪费的动力。相比之下,一个游戏会喜欢有更好的人工智能,更好的声音,更多的多边形,更多的效果闲置周期。


      1. 在这种情况下,在 PC 硬件上平滑滚动
      2. 在这种情况下,在 PC 硬件上平滑滚动
      3. 比我更可能,所以我们都清楚
      4. 比我更可能,所以我们都清楚
      5. 严格来说,这也会是一个演示给他/她的经理,但同样的驱动器在这里将是时间和/或视觉质量。

    你可以在 wikipedia 上找到它的详细信息以及为什么使用它: http://en.wikipedia.org/wiki/Vsync

    在交互式会话中执行 from ... import *是可以的。

    据我所知,在 Unreal 系列中,有些约定像封装一样被打破了。代码根据游戏的不同被编译成字节码或者直接编译成机器码。此外,对象渲染和打包的形式下,一个网格和物体,如纹理,照明和阴影是预先计算,而作为一个纯粹的3D 动画需要这一点,这实时。当游戏实际运行时,还有一些优化,比如只渲染对象的可见部分,只有在关闭时才显示纹理细节。最后,视频游戏的设计很可能是为了在给定的时间内充分利用平台(例如: Intelx86 MMX/SSE,DirectX,...)。

    根据 巨蟒之禅:

    这也导致了一个答案: 一个游戏如何才能是有效的?当编写“伟大的大型游戏”时,需要付出巨大的努力来优化游戏的各个方面(现在通常也包括多核优化)。至于 DX 演示,它的重点不是运行速度快,而是演示概念。

    此外,帧率是一些考虑,也许茶壶样本运行在全速(可能1000 fps)和大多数游戏是限制刷新频率的显示器(约60 fps)。

    返回,然后比较。

    您不会将 **locals()传递给函数,对吗?

    它很有可能隐藏了窃听器。

    import os, sys, foo, sqlalchemy, mystuff
    from bar import *
    

    现在,如果酒吧模块有任何的“ os”,“ mystuff”,等..。.属性,它们将覆盖显式导入的属性,并且可能指向非常不同的东西。在 bar 中定义 __all__通常是明智的——这表明将隐式导入什么——但是如果不读取和解析 bar 模块并遵循 它的导入,仍然很难跟踪对象来自哪里。当我获得一个项目的所有权时,我首先修复的是 import *网络。

    因为 Python 缺少一个“ include”语句,所以 还有self参数是显式的,还有的作用域规则非常简单,通常很容易指向一个变量并告诉它这个对象来自哪里——不需要读取其他模块,也不需要任何类型的 IDE (不管怎样,IDE 在自省方面是有限的,因为语言是非常动态的)。

    import *打破了这一切。

    不要误解我: 如果 import *不见了,我会哭着要它的。但必须谨慎使用。一个好的用例是在另一个模块上提供 facade 接口。

    此外,它还有隐藏 bug 的具体可能性。

    import os, sys, foo, sqlalchemy, mystuff
    from bar import *
    
    同样,使用条件导入语句,或者在函数/类名称空间中导入,也需要一些规程。

    现在,如果酒吧模块有任何的“ os”,“ mystuff”,等..。.属性,它们将覆盖显式导入的属性,并且可能指向非常不同的东西。在 bar 中定义 __all__通常是明智的——这表明将隐式导入什么——但是如果不读取和解析 bar 模块并遵循 它的导入,仍然很难跟踪对象来自哪里。当我获得一个项目的所有权时,我首先修复的是 import *网络。

    不要误解我: 如果 import *不见了,我会哭着要它的。但必须谨慎使用。一个好的用例是在另一个模块上提供 facade 接口。

    我认为在中大型项目中,或者有几个贡献者的小型项目中,在静态分析方面需要最低限度的卫生条件——至少运行 pyflakes,或者更好的是运行一个正确配置的 pylint ——以便在发生错误之前捕捉到几种类型的 bug。

    同样,使用条件导入语句,或者在函数/类名称空间中导入,也需要一些规程。

    我认为在中大型项目中,或者有几个贡献者的小型项目中,在静态分析方面需要最低限度的卫生条件——至少运行 pyflakes,或者更好的是运行一个正确配置的 pylint ——以便在发生错误之前捕捉到几种类型的 bug。

    当然,因为这是 python ——可以随意打破规则并进行探索——但是要小心那些可能增长十倍的项目,如果源代码缺乏规则,那将是一个问题。

    B9a22b098ca42/python3-script/mcpipy/mc.py”rel = “ norefrer”> 这个包装器 他们 from local_module import *一束模块包括 这份街区清单。让我们忽略名称空间冲突的风险。通过做 from mcpi.block import *,他们使这个模糊类型的块的整个列表的东西,你必须去看看,以了解什么是可用的。如果他们使用的是 from mcpi import block,那么您可以键入 walls = block.,然后会弹出一个自动完成列表。 Atom.io screenshot

    这是 非常糟糕的做法,原因有二:

      我认为这里缺少一个重要的答案部分。大多数答案告诉你“了解你的数据”。事实上,你必须以同样的方式,以同样的重要程度,了解你的:

      • 代码可读性
      • CPU (时钟和缓存)
      • 重写变量/函数等的风险

  • 内存(频率和延迟)
  • 硬盘驱动器(根据速度和寻道时间)
  • 对于 第一点: 我们来看一个例子:

    from module1 import *
    from module2 import *
    from module3 import *
    
    
    a = b + c - d
    
  • GPU (# 核心、时钟及其内存/缓存)
  • 接口: Sata 控制器,PCI 版本等。
  • 在这里,当看到代码没有人会得到想法从哪个模块 bcd实际上属于。

    另一方面,如果你这样做:

    #                   v  v  will know that these are from module1
    from module1 import b, c   # way 1
    import module2             # way 2
    
    
    a = b + c - module2.d
    #            ^ will know it is from module2
    

    但是 ,最重要的是,在目前的现代计算机上,你永远不可能在 > 30英尺的速度下播放真正的1080p 视频(一张64位的1080p 图像需要15000Ko/14.9 MB)。其原因是由于采样/精度。视频游戏永远不会使用双精度(64位)的像素,图像,数据等。.而是使用较低的自定义精度(约4-8位) ,有时也使用较低的精度重新调整插值技术以允许合理的计算时间。

    这对你来说更干净,而且新加入你的团队的人会有更好的主意。

    还有其他技术,如剪切数据(使用 OpengL 标准和软件实现)、数据压缩等。还要记住,当前的 GPU 在硬件性能方面可以比当前的 CPU 快300倍。然而,一个好的程序员可能会得到10-20倍的因子,除非你的问题是完全优化和完全可并行的(特别是任务可并行)。

    对于 第二点: 假设 module1module2都有作为 b的变量。当我这样做时:

    from module1 import *
    from module2 import *
    
    
    print b  # will print the value from module2
    

    根据经验,我可以告诉你优化就像一条指数曲线。为了达到最佳性能,所需的时间可能是非常重要的。

    在这里,module1的值丢失了。即使 b是在 module1中声明的,并且我已经编写了期望我的代码使用 module1.b的代码,也很难调试为什么代码无法工作

    因此,回到茶壶,您应该看到几何是如何表示,采样和精度 V 在 GTA 5看到,在几何学/纹理和最重要的,细节(精度,采样等)

    DX 茶壶演示没有使用30% 的 CPU 做有用的工作。它忙于等待,因为它没有别的事情可做。