From 93369c247069b116f39c30530287809f5a190c22 Mon Sep 17 00:00:00 2001 From: David 'Digit' Turner Date: Fri, 12 Feb 2010 15:51:33 -0800 Subject: [PATCH] Add the 'hello-neon' sample app to demonstrate cpufeatures and ARM Neon usage. + Add a small cleanup script (build/tools/cleanup-apps.sh) + Minor formatting of hello-gl2 sample. --- ndk/apps/hello-gl2/project/jni/gl_code.cpp | 6 +- ndk/apps/hello-neon/Application.mk | 3 + .../hello-neon/project/AndroidManifest.xml | 15 ++ ndk/apps/hello-neon/project/build.properties | 20 +++ .../hello-neon/project/default.properties | 11 ++ ndk/apps/hello-neon/project/jni/Android.mk | 22 +++ .../project/jni/helloneon-intrinsics.c | 48 ++++++ .../project/jni/helloneon-intrinsics.h | 6 + ndk/apps/hello-neon/project/jni/helloneon.c | 147 ++++++++++++++++++ .../hello-neon/project/res/values/strings.xml | 4 + .../src/com/example/neon/HelloNeon.java | 37 +++++ ndk/build/tools/cleanup-apps.sh | 12 ++ ndk/docs/CPU-ARM-NEON.TXT | 9 ++ 13 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 ndk/apps/hello-neon/Application.mk create mode 100644 ndk/apps/hello-neon/project/AndroidManifest.xml create mode 100644 ndk/apps/hello-neon/project/build.properties create mode 100644 ndk/apps/hello-neon/project/default.properties create mode 100644 ndk/apps/hello-neon/project/jni/Android.mk create mode 100644 ndk/apps/hello-neon/project/jni/helloneon-intrinsics.c create mode 100644 ndk/apps/hello-neon/project/jni/helloneon-intrinsics.h create mode 100644 ndk/apps/hello-neon/project/jni/helloneon.c create mode 100644 ndk/apps/hello-neon/project/res/values/strings.xml create mode 100644 ndk/apps/hello-neon/project/src/com/example/neon/HelloNeon.java create mode 100755 ndk/build/tools/cleanup-apps.sh diff --git a/ndk/apps/hello-gl2/project/jni/gl_code.cpp b/ndk/apps/hello-gl2/project/jni/gl_code.cpp index e1e30cefd..42d99d3e3 100644 --- a/ndk/apps/hello-gl2/project/jni/gl_code.cpp +++ b/ndk/apps/hello-gl2/project/jni/gl_code.cpp @@ -42,12 +42,14 @@ static void checkGlError(const char* op) { } } -static const char gVertexShader[] = "attribute vec4 vPosition;\n" +static const char gVertexShader[] = + "attribute vec4 vPosition;\n" "void main() {\n" " gl_Position = vPosition;\n" "}\n"; -static const char gFragmentShader[] = "precision mediump float;\n" +static const char gFragmentShader[] = + "precision mediump float;\n" "void main() {\n" " gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n" "}\n"; diff --git a/ndk/apps/hello-neon/Application.mk b/ndk/apps/hello-neon/Application.mk new file mode 100644 index 000000000..7325bb9a8 --- /dev/null +++ b/ndk/apps/hello-neon/Application.mk @@ -0,0 +1,3 @@ +APP_PROJECT_PATH := $(call my-dir)/project +APP_MODULES := helloneon cpufeatures +APP_ABI := armeabi armeabi-v7a diff --git a/ndk/apps/hello-neon/project/AndroidManifest.xml b/ndk/apps/hello-neon/project/AndroidManifest.xml new file mode 100644 index 000000000..2d362d688 --- /dev/null +++ b/ndk/apps/hello-neon/project/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/ndk/apps/hello-neon/project/build.properties b/ndk/apps/hello-neon/project/build.properties new file mode 100644 index 000000000..81674648a --- /dev/null +++ b/ndk/apps/hello-neon/project/build.properties @@ -0,0 +1,20 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +# The name of your application package as defined in the manifest. +# Used by the 'uninstall' rule. +application.package=com.example.neon diff --git a/ndk/apps/hello-neon/project/default.properties b/ndk/apps/hello-neon/project/default.properties new file mode 100644 index 000000000..4513a1e4f --- /dev/null +++ b/ndk/apps/hello-neon/project/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-3 diff --git a/ndk/apps/hello-neon/project/jni/Android.mk b/ndk/apps/hello-neon/project/jni/Android.mk new file mode 100644 index 000000000..5c7cb2511 --- /dev/null +++ b/ndk/apps/hello-neon/project/jni/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := helloneon + +LOCAL_SRC_FILES := helloneon.c + +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) + LOCAL_CFLAGS := -DHAVE_NEON=1 + LOCAL_SRC_FILES += helloneon-intrinsics.c.neon +endif + +LOCAL_C_INCLUDES := sources/cpufeatures + +LOCAL_STATIC_LIBRARIES := cpufeatures + +LOCAL_LDLIBS := -llog + +include $(BUILD_SHARED_LIBRARY) + +include sources/cpufeatures/Android.mk diff --git a/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.c b/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.c new file mode 100644 index 000000000..bac8659dd --- /dev/null +++ b/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.c @@ -0,0 +1,48 @@ +#include "helloneon-intrinsics.h" +#include + +/* this source file should only be compiled by Android.mk when targeting + * the armeabi-v7a ABI, and should be built in NEON mode + */ +void +fir_filter_neon_intrinsics(short *output, const short* input, const short* kernel, int width, int kernelSize) +{ +#if 1 + int nn, offset = -kernelSize/2; + + for (nn = 0; nn < width; nn++) + { + int mm, sum = 0; + int32x4_t sum_vec = vdupq_n_s32(0); + for(mm = 0; mm < kernelSize/4; mm++) + { + int16x4_t kernel_vec = vld1_s16(kernel + mm*4); + int16x4_t input_vec = vld1_s16(input + (nn+offset+mm*4)); + sum_vec = vmlal_s16(sum_vec, kernel_vec, input_vec); + } + + sum += vgetq_lane_s32(sum_vec, 0); + sum += vgetq_lane_s32(sum_vec, 1); + sum += vgetq_lane_s32(sum_vec, 2); + sum += vgetq_lane_s32(sum_vec, 3); + + if(kernelSize & 3) + { + for(mm = kernelSize - (kernelSize & 3); mm < kernelSize; mm++) + sum += kernel[mm] * input[nn+offset+mm]; + } + + output[nn] = (short)((sum + 0x8000) >> 16); + } +#else /* for comparison purposes only */ + int nn, offset = -kernelSize/2; + for (nn = 0; nn < width; nn++) { + int sum = 0; + int mm; + for (mm = 0; mm < kernelSize; mm++) { + sum += kernel[mm]*input[nn+offset+mm]; + } + output[n] = (short)((sum + 0x8000) >> 16); + } +#endif +} diff --git a/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.h b/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.h new file mode 100644 index 000000000..1f2ff9a2d --- /dev/null +++ b/ndk/apps/hello-neon/project/jni/helloneon-intrinsics.h @@ -0,0 +1,6 @@ +#ifndef HELLONEON_INTRINSICS_H +#define HELLONEON_INTRINSICS_H + +void fir_filter_neon_intrinsics(short *output, const short* input, const short* kernel, int width, int kernelSize); + +#endif /* HELLONEON_INTRINSICS_H */ diff --git a/ndk/apps/hello-neon/project/jni/helloneon.c b/ndk/apps/hello-neon/project/jni/helloneon.c new file mode 100644 index 000000000..3a2e928ad --- /dev/null +++ b/ndk/apps/hello-neon/project/jni/helloneon.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include "helloneon-intrinsics.h" + +#define DEBUG 0 + +#if DEBUG +#include +# define D(x...) __android_log_print(ANDROID_LOG_INFO,"helloneon",x) +#else +# define D(...) do {} while (0) +#endif + +/* return current time in milliseconds */ +static double +now_ms(void) +{ + struct timespec res; + clock_gettime(CLOCK_REALTIME, &res); + return 1000.0*res.tv_sec + (double)res.tv_nsec/1e6; +} + + +/* this is a FIR filter implemented in C */ +static void +fir_filter_c(short *output, const short* input, const short* kernel, int width, int kernelSize) +{ + int offset = -kernelSize/2; + int nn; + for (nn = 0; nn < width; nn++) { + int sum = 0; + int mm; + for (mm = 0; mm < kernelSize; mm++) { + sum += kernel[mm]*input[nn+offset+mm]; + } + output[nn] = (short)((sum + 0x8000) >> 16); + } +} + +#define FIR_KERNEL_SIZE 32 +#define FIR_OUTPUT_SIZE 2560 +#define FIR_INPUT_SIZE (FIR_OUTPUT_SIZE + FIR_KERNEL_SIZE) +#define FIR_ITERATIONS 600 + +static const short fir_kernel[FIR_KERNEL_SIZE] = { + 0x10, 0x20, 0x40, 0x70, 0x8c, 0xa2, 0xce, 0xf0, 0xe9, 0xce, 0xa2, 0x8c, 070, 0x40, 0x20, 0x10, + 0x10, 0x20, 0x40, 0x70, 0x8c, 0xa2, 0xce, 0xf0, 0xe9, 0xce, 0xa2, 0x8c, 070, 0x40, 0x20, 0x10 }; + +static short fir_output[FIR_OUTPUT_SIZE]; +static short fir_input_0[FIR_INPUT_SIZE]; +static const short* fir_input = fir_input_0 + (FIR_KERNEL_SIZE/2); +static short fir_output_expected[FIR_OUTPUT_SIZE]; + +/* This is a trivial JNI example where we use a native method + * to return a new VM String. See the corresponding Java source + * file located at: + * + * apps/samples/hello-neon/project/src/com/example/neon/HelloNeon.java + */ +jstring +Java_com_example_neon_HelloNeon_stringFromJNI( JNIEnv* env, + jobject thiz ) +{ + char* str; + uint64_t features; + char buffer[512]; + char tryNeon = 0; + double t0, t1, time_c, time_neon; + + /* setup FIR input - whatever */ + { + int nn; + for (nn = 0; nn < FIR_INPUT_SIZE; nn++) { + fir_input_0[nn] = (5*nn) & 255; + } + fir_filter_c(fir_output_expected, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE); + } + + /* Benchmark small FIR filter loop - C version */ + t0 = now_ms(); + { + int count = FIR_ITERATIONS; + for (; count > 0; count--) { + fir_filter_c(fir_output, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE); + } + } + t1 = now_ms(); + time_c = t1 - t0; + + asprintf(&str, "FIR Filter benchmark:\nC version : %g ms\n", time_c); + strlcpy(buffer, str, sizeof buffer); + free(str); + + strlcat(buffer, "Neon version : ", sizeof buffer); + + if (android_getCpuFamily() != ANDROID_CPU_FAMILY_ARM) { + strlcat(buffer, "Not an ARM CPU !\n", sizeof buffer); + goto EXIT; + } + + features = android_getCpuFeatures(); + if ((features & ANDROID_CPU_ARM_FEATURE_ARMv7) == 0) { + strlcat(buffer, "Not an ARMv7 CPU !\n", sizeof buffer); + goto EXIT; + } + + /* HAVE_NEON is defined in Android.mk ! */ +#ifdef HAVE_NEON + if ((features & ANDROID_CPU_ARM_FEATURE_NEON) == 0) { + strlcat(buffer, "CPU doesn't support NEON !\n", sizeof buffer); + goto EXIT; + } + + /* Benchmark small FIR filter loop - Neon version */ + t0 = now_ms(); + { + int count = FIR_ITERATIONS; + for (; count > 0; count--) { + fir_filter_neon_intrinsics(fir_output, fir_input, fir_kernel, FIR_OUTPUT_SIZE, FIR_KERNEL_SIZE); + } + } + t1 = now_ms(); + time_neon = t1 - t0; + asprintf(&str, "%g ms (x%g faster)\n", time_neon, time_c / (time_neon < 1e-6 ? 1. : time_neon)); + strlcat(buffer, str, sizeof buffer); + free(str); + + /* check the result, just in case */ + { + int nn, fails = 0; + for (nn = 0; nn < FIR_OUTPUT_SIZE; nn++) { + if (fir_output[nn] != fir_output_expected[nn]) { + if (++fails < 16) + D("neon[%d] = %d expected %d", nn, fir_output[nn], fir_output_expected[nn]); + } + } + D("%d fails\n", fails); + } +#else /* !HAVE_NEON */ + strlcat(buffer, "Program not compiled with ARMv7 support !\n", sizeof buffer); +#endif /* !HAVE_NEON */ +EXIT: + return (*env)->NewStringUTF(env, buffer); +} diff --git a/ndk/apps/hello-neon/project/res/values/strings.xml b/ndk/apps/hello-neon/project/res/values/strings.xml new file mode 100644 index 000000000..8d8f980cc --- /dev/null +++ b/ndk/apps/hello-neon/project/res/values/strings.xml @@ -0,0 +1,4 @@ + + + HelloNeon + diff --git a/ndk/apps/hello-neon/project/src/com/example/neon/HelloNeon.java b/ndk/apps/hello-neon/project/src/com/example/neon/HelloNeon.java new file mode 100644 index 000000000..b2f5e88db --- /dev/null +++ b/ndk/apps/hello-neon/project/src/com/example/neon/HelloNeon.java @@ -0,0 +1,37 @@ +package com.example.neon; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +public class HelloNeon extends Activity +{ + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + /* Create a TextView and set its content. + * the text is retrieved by calling a native + * function. + */ + TextView tv = new TextView(this); + tv.setText( stringFromJNI() ); + setContentView(tv); + } + + /* A native method that is implemented by the + * 'helloneon' native library, which is packaged + * with this application. + */ + public native String stringFromJNI(); + + /* this is used to load the 'helloneon' library on application + * startup. The library has already been unpacked into + * /data/data/com.example.neon/lib/libhelloneon.so at + * installation time by the package manager. + */ + static { + System.loadLibrary("helloneon"); + } +} diff --git a/ndk/build/tools/cleanup-apps.sh b/ndk/build/tools/cleanup-apps.sh new file mode 100755 index 000000000..21a0df013 --- /dev/null +++ b/ndk/build/tools/cleanup-apps.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# +# This is used to cleanup the project directories before making a commit or +# a clean release. This will get rid of auto-generated files in the +# apps//project directories. +# +for projectPath in `find apps/*/project` ; do + rm -rf $projectPath/bin + rm -rf $projectPath/gen + rm -f $projectPath/build.xml + rm -f $projectPath/local.properties +done diff --git a/ndk/docs/CPU-ARM-NEON.TXT b/ndk/docs/CPU-ARM-NEON.TXT index f023cea79..61a83317a 100644 --- a/ndk/docs/CPU-ARM-NEON.TXT +++ b/ndk/docs/CPU-ARM-NEON.TXT @@ -116,3 +116,12 @@ that has the ANDROID_CPU_ARM_FEATURE_NEON flag set, as in: } ... + +Sample code: +------------ + +Look at the source code for the "hello-neon" sample in this NDK for an example +on how to use the 'cpufeatures' library and Neon intrinsics at the same time. + +This implements a tiny benchmark for a FIR filter loop using a C version, and +a NEON-optimized one for devices that support it.