2 Commits

Author SHA1 Message Date
Gerry
485f2b9d19 Merge pull request #489 from googlesamples/mk-fix-37001153
Fix gles3jni bug b/37001153 ( dated 2014 ) in branch android-mk
2018-02-01 15:27:44 -08:00
guanghuafan
0e88bb1c3e Fix gles3jni bug b/37001153 ( dated 2014 ) in branch android-mk 2018-02-01 14:39:58 -08:00
1101 changed files with 18369 additions and 157827 deletions

View File

@@ -1,2 +0,0 @@
BasedOnStyle: Google
DerivePointerAlignment: false

View File

@@ -1,29 +0,0 @@
name: build
on:
workflow_dispatch:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 21
uses: actions/setup-java@v1
with:
java-version: 21
- uses: pre-commit/action@v3.0.0
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
packages: "cmake;4.1.0"
- name: build samples
run: |
./gradlew build

13
.gitignore vendored
View File

@@ -1,13 +0,0 @@
.gradle
.idea
**/*.iml
local.properties
build
*~
.externalNativeBuild
libwebp
.DS_Store
**/ndkHelperBin
**/.cxx
display-p3/third_party

View File

@@ -1,13 +0,0 @@
status: PUBLISHED
technologies:
- Android
- NDK
- Platform
categories:
- NDK
languages:
- C++
solutions:
- Mobile
github: android/ndk-samples
license: apache2

View File

@@ -1,6 +0,0 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-xml
- id: check-yaml

View File

@@ -1,96 +0,0 @@
# Architecture
This document describes the layout of the samples repository.
The top level directory is a Gradle project. This directory can be opened in
Android Studio (and that will be the easiest way to work with the samples).
## Directory structure
### Samples
Most subdirectories are the individual sample apps. These can't be opened in
Android Studio individually, as they rely on common code from the top level
project. These subdirectories have their own `build.gradle` (the Groovy DSL) or
`build.gradle.kts` (the Kotlin DSL) files. In Gradle terms these are "projects".
Android Studio calls them "modules". The documentation in this repository will
typically call them "modules".
Each sample has its own README.md that explains what features it demonstrates,
as well as any unique requirements.
To run a sample, use the configuration selector in the top panel of Android
Studio to select the sample and then click run. For example, to run the
endless-tunnel sample game:
![Select and run a sample][docs/run-sample.png]
#### Build types
Android Gradle modules have multiple "build types", sometimes called "variants".
For most of the samples, there are only two build types: debug and release. A
few samples, notably the sanitizers sample, have more. To view the build types
for the modules in this repository, in the Android Studio application menu,
select View -> Tool Windows -> Build Variants. A window will open that lets you
select the active variant for each sample.
### build-logic
The `build-logic` directory contains Gradle convention plugins used by this
repository. This is where Gradle policy decisions that apply to the whole
repository are made.
See the README.md in that directory for more details.
### docs
Documentation and supporting artifacts for this repository. Yes, for now it's
just images for the READMEs and this doc.
### gradle/libs.versions.toml
This is a Gradle [version catalog]. It's the central location that defines the
library and plugin dependencies for each sample.
The Android Gradle Plugin does not support evaluating version catalog fields in
its DSL, so versions for SDK and NDK tools (`ndkVersion`, `compileSdkVersion`,
`targetSdkVersion`, etc) are all defined by the convention plugins in
[build-logic](#build-logic).
[version catalog]: https://docs.gradle.org/current/userguide/platforms.html
### Gradle wrapper
The `gradlew` and `gradlew.bat` scripts are the Gradle wrappers, macOS/Linux-
and Windows batch-compatible respectively. They will manage the Gradle
installation used by this repository for you, ensuring that the correct versions
and environment are used. These should be used instead of running `gradle`
directly.
`gradle/wrapper/gradle-wrapper.properties` defines which version of Gradle will
be used. This should rarely be modified manually. Android Studio's Upgrade
Assistant will manage that file and upgrade to whatever version of Gradle it
prefers for that version of Android Studio and Android Gradle.
### build.gradle and settings.gradle
The build.gradle (or build.gradle.kts) file is the top level build file. It
doesn't do anything interesting but declares which plugins will be used by the
child modules. Each sample's module has its own build.gradle file that defines
properties of the app.
The settings.gradle (or settings.gradle.kts) file configures repositories for
fetching dependencies, and declares each app module.
### Metadata directories
#### .github
This directory contains GitHub metadata files, such as GitHub Action workflows
and Issue templates.
#### .google
The `packaging.yaml` file in this directory is used by Android Studio to enable
the File -> New -> Import Sample feature. As this repository is a monolithic
sample project, it should not need to be changed.

View File

@@ -8,12 +8,12 @@ have to jump a couple of legal hurdles.
Please fill out either the individual or corporate Contributor License Agreement
(CLA).
- If you are an individual writing original source code and you're sure you own
the intellectual property, then you'll need to sign an
[individual CLA](http://code.google.com/legal/individual-cla-v1.0.html).
- If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a
[corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html).
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual CLA]
(http://code.google.com/legal/individual-cla-v1.0.html).
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA]
(http://code.google.com/legal/corporate-cla-v1.0.html).
Follow either of the two links above to access the appropriate CLA and
instructions for how to sign and return it. Once we receive it, we'll be able to
@@ -24,10 +24,33 @@ accept your pull requests.
1. Sign a Contributor License Agreement, if you have not yet done so (see
details above).
1. Create your change to the repo in question.
- Fork the desired repo, develop and test your code changes.
- Ensure that your code is clear and comprehensible.
- Ensure that your code has an appropriate set of unit tests which all pass.
* Fork the desired repo, develop and test your code changes.
* Ensure that your code is clear and comprehensible.
* Ensure that your code has an appropriate set of unit tests which all pass.
1. Submit a pull request.
1. The repo owner will review your request. If it is approved, the change will
be merged. If it needs additional work, the repo owner will respond with
useful comments.
## Contributing a New Sample App
1. Sign a Contributor License Agreement, if you have not yet done so (see
details above).
1. Create your own repo for your app following this naming convention:
* mirror-{app-name}-{language or plaform}
* apps: quickstart, photohunt-server, photohunt-client
* example: mirror-quickstart-android
* For multi-language apps, concatenate the primary languages like this:
mirror-photohunt-server-java-python.
1. Create your sample app in this repo.
* Be sure to clone the README.md, CONTRIBUTING.md and LICENSE files from the
googlecast repo.
* Ensure that your code is clear and comprehensible.
* Ensure that your code has an appropriate set of unit tests which all pass.
* Instructional value is the top priority when evaluating new app proposals for
this collection of repos.
1. Submit a request to fork your repo in googlecast organization.
1. The repo owner will review your request. If it is approved, the sample will
be merged. If it needs additional work, the repo owner will respond with
useful comments.

View File

@@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sample.moreteapots"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="19" />
<uses-feature android:glEsVersion="0x00020000"></uses-feature>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:hasCode="true"
android:name="com.sample.moreteapots.MoreTeapotsApplication"
>
<!-- Our activity is the built-in NativeActivity framework class.
This will take care of integrating with our NDK code. -->
<activity android:name="com.sample.moreteapots.MoreTeapotsNativeActivity"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<!-- Tell NativeActivity the name of or .so -->
<meta-data android:name="android.app.lib_name"
android:value="MoreTeapotsNativeActivity" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -17,7 +17,7 @@
//
uniform lowp vec3 vMaterialAmbient;
uniform lowp vec4 vMaterialSpecular;
uniform mediump vec4 vMaterialSpecular;
varying lowp vec4 colorDiffuse;

View File

@@ -17,7 +17,7 @@
#version 300 es
precision mediump float;
uniform lowp vec4 vMaterialSpecular;
uniform mediump vec4 vMaterialSpecular;
uniform highp vec3 vLight0;
in lowp vec4 colorDiffuse;

View File

@@ -31,6 +31,7 @@ uniform highp vec3 vLight0;
uniform lowp vec4 vMaterialDiffuse;
uniform lowp vec3 vMaterialAmbient;
uniform lowp vec4 vMaterialSpecular;
void main(void)
{

View File

@@ -0,0 +1,19 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := MoreTeapotsNativeActivity
LOCAL_SRC_FILES := MoreTeapotsNativeActivity.cpp \
MoreTeapotsRenderer.cpp \
LOCAL_C_INCLUDES :=
LOCAL_CFLAGS :=
LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv2
LOCAL_STATIC_LIBRARIES := cpufeatures android_native_app_glue ndk_helper
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/ndk_helper)
$(call import-module,android/native_app_glue)
$(call import-module,android/cpufeatures)

View File

@@ -0,0 +1,4 @@
APP_PLATFORM := android-9
APP_ABI := all
APP_STL := stlport_static

View File

@@ -0,0 +1,500 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//--------------------------------------------------------------------------------
// Include files
//--------------------------------------------------------------------------------
#include <jni.h>
#include <errno.h>
#include <vector>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#include <android/native_window_jni.h>
#include <cpu-features.h>
#include "MoreTeapotsRenderer.h"
//-------------------------------------------------------------------------
//Preprocessor
//-------------------------------------------------------------------------
#define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper function
//-------------------------------------------------------------------------
//Constants
//-------------------------------------------------------------------------
const int32_t NUM_TEAPOTS_X = 8;
const int32_t NUM_TEAPOTS_Y = 8;
const int32_t NUM_TEAPOTS_Z = 8;
//-------------------------------------------------------------------------
//Shared state for our app.
//-------------------------------------------------------------------------
struct android_app;
class Engine
{
MoreTeapotsRenderer renderer_;
ndk_helper::GLContext* gl_context_;
bool initialized_resources_;
bool has_focus_;
ndk_helper::DoubletapDetector doubletap_detector_;
ndk_helper::PinchDetector pinch_detector_;
ndk_helper::DragDetector drag_detector_;
ndk_helper::PerfMonitor monitor_;
ndk_helper::TapCamera tap_camera_;
android_app* app_;
ASensorManager* sensor_manager_;
const ASensor* accelerometer_sensor_;
ASensorEventQueue* sensor_event_queue_;
void UpdateFPS( float fps );
void ShowUI();
void TransformPosition( ndk_helper::Vec2& vec );
public:
static void HandleCmd( struct android_app* app,
int32_t cmd );
static int32_t HandleInput( android_app* app,
AInputEvent* event );
Engine();
~Engine();
void SetState( android_app* state );
int InitDisplay();
void LoadResources();
void UnloadResources();
void DrawFrame();
void TermDisplay();
void TrimMemory();
bool IsReady();
void UpdatePosition( AInputEvent* event,
int32_t index,
float& x,
float& y );
void InitSensors();
void ProcessSensors( int32_t id );
void SuspendSensors();
void ResumeSensors();
};
//-------------------------------------------------------------------------
//Ctor
//-------------------------------------------------------------------------
Engine::Engine() :
initialized_resources_( false ),
has_focus_( false ),
app_( NULL ),
sensor_manager_( NULL ),
accelerometer_sensor_( NULL ),
sensor_event_queue_( NULL )
{
gl_context_ = ndk_helper::GLContext::GetInstance();
}
//-------------------------------------------------------------------------
//Dtor
//-------------------------------------------------------------------------
Engine::~Engine()
{
}
/**
* Load resources
*/
void Engine::LoadResources()
{
renderer_.Init( NUM_TEAPOTS_X, NUM_TEAPOTS_Y, NUM_TEAPOTS_Z );
renderer_.Bind( &tap_camera_ );
}
/**
* Unload resources
*/
void Engine::UnloadResources()
{
renderer_.Unload();
}
/**
* Initialize an EGL context for the current display.
*/
int Engine::InitDisplay()
{
if( !initialized_resources_ )
{
gl_context_->Init( app_->window );
LoadResources();
initialized_resources_ = true;
}
else
{
// initialize OpenGL ES and EGL
if( EGL_SUCCESS != gl_context_->Resume( app_->window ) )
{
UnloadResources();
LoadResources();
}
}
ShowUI();
// Initialize GL state.
glEnable( GL_CULL_FACE );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
//Note that screen size might have been changed
glViewport( 0, 0, gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() );
renderer_.UpdateViewport();
tap_camera_.SetFlip( 1.f, -1.f, -1.f );
tap_camera_.SetPinchTransformFactor( 10.f, 10.f, 8.f );
return 0;
}
/**
* Just the current frame in the display.
*/
void Engine::DrawFrame()
{
float fps;
if( monitor_.Update( fps ) )
{
UpdateFPS( fps );
}
double dTime = monitor_.GetCurrentTime();
renderer_.Update( dTime );
// Just fill the screen with a color.
glClearColor( 0.5f, 0.5f, 0.5f, 1.f );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
renderer_.Render();
// Swap
if( EGL_SUCCESS != gl_context_->Swap() )
{
UnloadResources();
LoadResources();
}
}
/**
* Tear down the EGL context currently associated with the display.
*/
void Engine::TermDisplay()
{
gl_context_->Suspend();
}
void Engine::TrimMemory()
{
LOGI( "Trimming memory" );
gl_context_->Invalidate();
}
/**
* Process the next input event.
*/
int32_t Engine::HandleInput( android_app* app,
AInputEvent* event )
{
Engine* eng = (Engine*) app->userData;
if( AInputEvent_getType( event ) == AINPUT_EVENT_TYPE_MOTION )
{
ndk_helper::GESTURE_STATE doubleTapState = eng->doubletap_detector_.Detect( event );
ndk_helper::GESTURE_STATE dragState = eng->drag_detector_.Detect( event );
ndk_helper::GESTURE_STATE pinchState = eng->pinch_detector_.Detect( event );
//Double tap detector has a priority over other detectors
if( doubleTapState == ndk_helper::GESTURE_STATE_ACTION )
{
//Detect double tap
eng->tap_camera_.Reset( true );
}
else
{
//Handle drag state
if( dragState & ndk_helper::GESTURE_STATE_START )
{
//Otherwise, start dragging
ndk_helper::Vec2 v;
eng->drag_detector_.GetPointer( v );
eng->TransformPosition( v );
eng->tap_camera_.BeginDrag( v );
}
else if( dragState & ndk_helper::GESTURE_STATE_MOVE )
{
ndk_helper::Vec2 v;
eng->drag_detector_.GetPointer( v );
eng->TransformPosition( v );
eng->tap_camera_.Drag( v );
}
else if( dragState & ndk_helper::GESTURE_STATE_END )
{
eng->tap_camera_.EndDrag();
}
//Handle pinch state
if( pinchState & ndk_helper::GESTURE_STATE_START )
{
//Start new pinch
ndk_helper::Vec2 v1;
ndk_helper::Vec2 v2;
eng->pinch_detector_.GetPointers( v1, v2 );
eng->TransformPosition( v1 );
eng->TransformPosition( v2 );
eng->tap_camera_.BeginPinch( v1, v2 );
}
else if( pinchState & ndk_helper::GESTURE_STATE_MOVE )
{
//Multi touch
//Start new pinch
ndk_helper::Vec2 v1;
ndk_helper::Vec2 v2;
eng->pinch_detector_.GetPointers( v1, v2 );
eng->TransformPosition( v1 );
eng->TransformPosition( v2 );
eng->tap_camera_.Pinch( v1, v2 );
}
}
return 1;
}
return 0;
}
/**
* Process the next main command.
*/
void Engine::HandleCmd( struct android_app* app,
int32_t cmd )
{
Engine* eng = (Engine*) app->userData;
switch( cmd )
{
case APP_CMD_SAVE_STATE:
break;
case APP_CMD_INIT_WINDOW:
// The window is being shown, get it ready.
if( app->window != NULL )
{
eng->InitDisplay();
eng->DrawFrame();
}
break;
case APP_CMD_TERM_WINDOW:
// The window is being hidden or closed, clean it up.
eng->TermDisplay();
eng->has_focus_ = false;
break;
case APP_CMD_STOP:
break;
case APP_CMD_GAINED_FOCUS:
eng->ResumeSensors();
//Start animation
eng->has_focus_ = true;
break;
case APP_CMD_LOST_FOCUS:
eng->SuspendSensors();
// Also stop animating.
eng->has_focus_ = false;
eng->DrawFrame();
break;
case APP_CMD_LOW_MEMORY:
//Free up GL resources
eng->TrimMemory();
break;
}
}
//-------------------------------------------------------------------------
//Sensor handlers
//-------------------------------------------------------------------------
void Engine::InitSensors()
{
sensor_manager_ = ASensorManager_getInstance();
accelerometer_sensor_ = ASensorManager_getDefaultSensor( sensor_manager_,
ASENSOR_TYPE_ACCELEROMETER );
sensor_event_queue_ = ASensorManager_createEventQueue( sensor_manager_, app_->looper,
LOOPER_ID_USER, NULL, NULL );
}
void Engine::ProcessSensors( int32_t id )
{
// If a sensor has data, process it now.
if( id == LOOPER_ID_USER )
{
if( accelerometer_sensor_ != NULL )
{
ASensorEvent event;
while( ASensorEventQueue_getEvents( sensor_event_queue_, &event, 1 ) > 0 )
{
}
}
}
}
void Engine::ResumeSensors()
{
// When our app gains focus, we start monitoring the accelerometer.
if( accelerometer_sensor_ != NULL )
{
ASensorEventQueue_enableSensor( sensor_event_queue_, accelerometer_sensor_ );
// We'd like to get 60 events per second (in us).
ASensorEventQueue_setEventRate( sensor_event_queue_, accelerometer_sensor_,
(1000L / 60) * 1000 );
}
}
void Engine::SuspendSensors()
{
// When our app loses focus, we stop monitoring the accelerometer.
// This is to avoid consuming battery while not being used.
if( accelerometer_sensor_ != NULL )
{
ASensorEventQueue_disableSensor( sensor_event_queue_, accelerometer_sensor_ );
}
}
//-------------------------------------------------------------------------
//Misc
//-------------------------------------------------------------------------
void Engine::SetState( android_app* state )
{
app_ = state;
doubletap_detector_.SetConfiguration( app_->config );
drag_detector_.SetConfiguration( app_->config );
pinch_detector_.SetConfiguration( app_->config );
}
bool Engine::IsReady()
{
if( has_focus_ )
return true;
return false;
}
void Engine::TransformPosition( ndk_helper::Vec2& vec )
{
vec = ndk_helper::Vec2( 2.0f, 2.0f ) * vec
/ ndk_helper::Vec2( gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() )
- ndk_helper::Vec2( 1.f, 1.f );
}
void Engine::ShowUI()
{
JNIEnv *jni;
app_->activity->vm->AttachCurrentThread( &jni, NULL );
//Default class retrieval
jclass clazz = jni->GetObjectClass( app_->activity->clazz );
jmethodID methodID = jni->GetMethodID( clazz, "showUI", "()V" );
jni->CallVoidMethod( app_->activity->clazz, methodID );
app_->activity->vm->DetachCurrentThread();
return;
}
void Engine::UpdateFPS( float fps )
{
JNIEnv *jni;
app_->activity->vm->AttachCurrentThread( &jni, NULL );
//Default class retrieval
jclass clazz = jni->GetObjectClass( app_->activity->clazz );
jmethodID methodID = jni->GetMethodID( clazz, "updateFPS", "(F)V" );
jni->CallVoidMethod( app_->activity->clazz, methodID, fps );
app_->activity->vm->DetachCurrentThread();
return;
}
Engine g_engine;
/**
* This is the main entry point of a native application that is using
* android_native_app_glue. It runs in its own thread, with its own
* event loop for receiving input events and doing other things.
*/
void android_main( android_app* state )
{
app_dummy();
g_engine.SetState( state );
//Init helper functions
ndk_helper::JNIHelper::GetInstance()->Init( state->activity, HELPER_CLASS_NAME );
state->userData = &g_engine;
state->onAppCmd = Engine::HandleCmd;
state->onInputEvent = Engine::HandleInput;
#ifdef USE_NDK_PROFILER
monstartup("libMoreTeapotsNativeActivity.so");
#endif
// Prepare to monitor accelerometer
g_engine.InitSensors();
// loop waiting for stuff to do.
while( 1 )
{
// Read all pending events.
int id;
int events;
android_poll_source* source;
// If not animating, we will block forever waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while( (id = ALooper_pollAll( g_engine.IsReady() ? 0 : -1, NULL, &events, (void**) &source ))
>= 0 )
{
// Process this event.
if( source != NULL )
source->process( state, source );
g_engine.ProcessSensors( id );
// Check if we are exiting.
if( state->destroyRequested != 0 )
{
g_engine.TermDisplay();
return;
}
}
if( g_engine.IsReady() )
{
// Drawing is throttled to the screen update rate, so there
// is no need to do timing here.
g_engine.DrawFrame();
}
}
}

View File

@@ -0,0 +1,555 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//--------------------------------------------------------------------------------
// MoreTeapotsRenderer.cpp
// Render teapots
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// Include files
//--------------------------------------------------------------------------------
#include "MoreTeapotsRenderer.h"
//--------------------------------------------------------------------------------
// Teapot model data
//--------------------------------------------------------------------------------
#include "teapot.inl"
//--------------------------------------------------------------------------------
// Ctor
//--------------------------------------------------------------------------------
MoreTeapotsRenderer::MoreTeapotsRenderer() :
geometry_instancing_support_( false )
{
}
//--------------------------------------------------------------------------------
// Dtor
//--------------------------------------------------------------------------------
MoreTeapotsRenderer::~MoreTeapotsRenderer()
{
Unload();
}
//--------------------------------------------------------------------------------
// Init
//--------------------------------------------------------------------------------
void MoreTeapotsRenderer::Init( const int32_t numX,
const int32_t numY,
const int32_t numZ )
{
if( ndk_helper::GLContext::GetInstance()->GetGLVersion() >= 3.0 )
{
geometry_instancing_support_ = true;
}
else if( ndk_helper::GLContext::GetInstance()->CheckExtension( "GL_NV_draw_instanced" )
&& ndk_helper::GLContext::GetInstance()->CheckExtension(
"GL_NV_uniform_buffer_object" ) )
{
LOGI( "Supported via extension!" );
//_bGeometryInstancingSupport = true;
//_bARBSupport = true; //Need to patch shaders
//Currently this has been disabled
}
//Settings
glFrontFace( GL_CCW );
//Create Index buffer
num_indices_ = sizeof(teapotIndices) / sizeof(teapotIndices[0]);
glGenBuffers( 1, &ibo_ );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo_ );
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(teapotIndices), teapotIndices, GL_STATIC_DRAW );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
//Create VBO
num_vertices_ = sizeof(teapotPositions) / sizeof(teapotPositions[0]) / 3;
int32_t iStride = sizeof(TEAPOT_VERTEX);
int32_t iIndex = 0;
TEAPOT_VERTEX* p = new TEAPOT_VERTEX[num_vertices_];
for( int32_t i = 0; i < num_vertices_; ++i )
{
p[i].pos[0] = teapotPositions[iIndex];
p[i].pos[1] = teapotPositions[iIndex + 1];
p[i].pos[2] = teapotPositions[iIndex + 2];
p[i].normal[0] = teapotNormals[iIndex];
p[i].normal[1] = teapotNormals[iIndex + 1];
p[i].normal[2] = teapotNormals[iIndex + 2];
iIndex += 3;
}
glGenBuffers( 1, &vbo_ );
glBindBuffer( GL_ARRAY_BUFFER, vbo_ );
glBufferData( GL_ARRAY_BUFFER, iStride * num_vertices_, p, GL_STATIC_DRAW );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
delete[] p;
//Init Projection matrices
teapot_x_ = numX;
teapot_y_ = numY;
teapot_z_ = numZ;
vec_mat_models_.reserve( teapot_x_ * teapot_y_ * teapot_z_ );
UpdateViewport();
const float total_width = 500.f;
float gap_x = total_width / (teapot_x_ - 1);
float gap_y = total_width / (teapot_y_ - 1);
float gap_z = total_width / (teapot_z_ - 1);
float offset_x = -total_width / 2.f;
float offset_y = -total_width / 2.f;
float offset_z = -total_width / 2.f;
for( int32_t iX = 0; iX < teapot_x_; ++iX )
for( int32_t iY = 0; iY < teapot_y_; ++iY )
for( int32_t iZ = 0; iZ < teapot_z_; ++iZ )
{
vec_mat_models_.push_back(
ndk_helper::Mat4::Translation( iX * gap_x + offset_x, iY * gap_y + offset_y,
iZ * gap_z + offset_z ) );
vec_colors_.push_back(
ndk_helper::Vec3( random() / float( RAND_MAX * 1.1 ),
random() / float( RAND_MAX * 1.1 ),
random() / float( RAND_MAX * 1.1 ) ) );
float fX = random() / float( RAND_MAX ) - 0.5f;
float fY = random() / float( RAND_MAX ) - 0.5f;
vec_rotations_.push_back( ndk_helper::Vec2( fX * 0.05f, fY * 0.05f ) );
vec_current_rotations_.push_back( ndk_helper::Vec2( fX * M_PI, fY * M_PI ) );
}
if( geometry_instancing_support_ )
{
//
//Create parameter dictionary for shader patch
std::map<std::string, std::string> param;
param[std::string( "%NUM_TEAPOT%" )] = ToString( teapot_x_ * teapot_y_ * teapot_z_ );
param[std::string( "%LOCATION_VERTEX%" )] = ToString( ATTRIB_VERTEX );
param[std::string( "%LOCATION_NORMAL%" )] = ToString( ATTRIB_NORMAL );
if( arb_support_ )
param[std::string( "%ARB%" )] = std::string( "ARB" );
else
param[std::string( "%ARB%" )] = std::string( "" );
//Load shader
bool b = LoadShadersES3( &shader_param_, "Shaders/VS_ShaderPlainES3.vsh",
"Shaders/ShaderPlainES3.fsh", param );
if( b )
{
//
//Create uniform buffer
//
GLuint bindingPoint = 1;
GLuint blockIndex;
blockIndex = glGetUniformBlockIndex( shader_param_.program_, "ParamBlock" );
glUniformBlockBinding( shader_param_.program_, blockIndex, bindingPoint );
//Retrieve array stride value
int32_t iNumIndices;
glGetActiveUniformBlockiv( shader_param_.program_, blockIndex,
GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &iNumIndices );
GLint i[iNumIndices];
GLint stride[iNumIndices];
glGetActiveUniformBlockiv( shader_param_.program_, blockIndex,
GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, i );
glGetActiveUniformsiv( shader_param_.program_, iNumIndices, (GLuint*) i,
GL_UNIFORM_ARRAY_STRIDE, stride );
ubo_matrix_stride_ = stride[0] / sizeof(float);
ubo_vector_stride_ = stride[2] / sizeof(float);
glGenBuffers( 1, &ubo_ );
glBindBuffer( GL_UNIFORM_BUFFER, ubo_ );
glBindBufferBase( GL_UNIFORM_BUFFER, bindingPoint, ubo_ );
//Store color value which wouldn't be updated every frame
int32_t iSize = teapot_x_ * teapot_y_ * teapot_z_
* (ubo_matrix_stride_ + ubo_matrix_stride_ + ubo_vector_stride_); //Mat4 + Mat4 + Vec3 + 1 stride
float* pBuffer = new float[iSize];
float* pColor = pBuffer + teapot_x_ * teapot_y_ * teapot_z_ * ubo_matrix_stride_ * 2;
for( int32_t i = 0; i < teapot_x_ * teapot_y_ * teapot_z_; ++i )
{
memcpy( pColor, &vec_colors_[i], 3 * sizeof(float) );
pColor += ubo_vector_stride_; //Assuming std140 layout which is 4 DWORD stride for vectors
}
glBufferData( GL_UNIFORM_BUFFER, iSize * sizeof(float), pBuffer, GL_DYNAMIC_DRAW );
delete[] pBuffer;
}
else
{
LOGI( "Shader compilation failed!! Falls back to ES2.0 pass" );
//This happens some devices.
geometry_instancing_support_ = false;
//Load shader for GLES2.0
LoadShaders( &shader_param_, "Shaders/VS_ShaderPlain.vsh", "Shaders/ShaderPlain.fsh" );
}
}
else
{
//Load shader for GLES2.0
LoadShaders( &shader_param_, "Shaders/VS_ShaderPlain.vsh", "Shaders/ShaderPlain.fsh" );
}
}
void MoreTeapotsRenderer::UpdateViewport()
{
int32_t viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport );
float fAspect = (float) viewport[2] / (float) viewport[3];
const float CAM_NEAR = 5.f;
const float CAM_FAR = 10000.f;
bool bRotate = false;
mat_projection_ = ndk_helper::Mat4::Perspective( fAspect, 1.f, CAM_NEAR, CAM_FAR );
}
//--------------------------------------------------------------------------------
// Unload
//--------------------------------------------------------------------------------
void MoreTeapotsRenderer::Unload()
{
if( vbo_ )
{
glDeleteBuffers( 1, &vbo_ );
vbo_ = 0;
}
if( ubo_ )
{
glDeleteBuffers( 1, &ubo_ );
ubo_ = 0;
}
if( ibo_ )
{
glDeleteBuffers( 1, &ibo_ );
ibo_ = 0;
}
if( shader_param_.program_ )
{
glDeleteProgram( shader_param_.program_ );
shader_param_.program_ = 0;
}
}
//--------------------------------------------------------------------------------
// Update
//--------------------------------------------------------------------------------
void MoreTeapotsRenderer::Update( float fTime )
{
const float CAM_X = 0.f;
const float CAM_Y = 0.f;
const float CAM_Z = 2000.f;
mat_view_ = ndk_helper::Mat4::LookAt( ndk_helper::Vec3( CAM_X, CAM_Y, CAM_Z ),
ndk_helper::Vec3( 0.f, 0.f, 0.f ), ndk_helper::Vec3( 0.f, 1.f, 0.f ) );
if( camera_ )
{
camera_->Update();
mat_view_ = camera_->GetTransformMatrix() * mat_view_ * camera_->GetRotationMatrix();
}
}
//--------------------------------------------------------------------------------
// Render
//--------------------------------------------------------------------------------
void MoreTeapotsRenderer::Render()
{
// Bind the VBO
glBindBuffer( GL_ARRAY_BUFFER, vbo_ );
int32_t iStride = sizeof(TEAPOT_VERTEX);
// Pass the vertex data
glVertexAttribPointer( ATTRIB_VERTEX, 3, GL_FLOAT, GL_FALSE, iStride, BUFFER_OFFSET( 0 ) );
glEnableVertexAttribArray( ATTRIB_VERTEX );
glVertexAttribPointer( ATTRIB_NORMAL, 3, GL_FLOAT, GL_FALSE, iStride,
BUFFER_OFFSET( 3 * sizeof(GLfloat) ) );
glEnableVertexAttribArray( ATTRIB_NORMAL );
// Bind the IB
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo_ );
glUseProgram( shader_param_.program_ );
TEAPOT_MATERIALS material = { { 1.0f, 1.0f, 1.0f, 10.f }, { 0.1f, 0.1f, 0.1f }, };
//Update uniforms
//
//using glUniform3fv here was troublesome..
//
glUniform4f( shader_param_.material_specular_, material.specular_color[0],
material.specular_color[1], material.specular_color[2], material.specular_color[3] );
glUniform3f( shader_param_.material_ambient_, material.ambient_color[0],
material.ambient_color[1], material.ambient_color[2] );
glUniform3f( shader_param_.light0_, 100.f, -200.f, -600.f );
if( geometry_instancing_support_ )
{
//
//Geometry instancing, new feature in GLES3.0
//
//Update UBO
glBindBuffer( GL_UNIFORM_BUFFER, ubo_ );
float* p = (float*) glMapBufferRange( GL_UNIFORM_BUFFER, 0,
teapot_x_ * teapot_y_ * teapot_z_ * (ubo_matrix_stride_ * 2) * sizeof(float),
GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT );
float* pMVPMat = p;
float* pMVMat = p + teapot_x_ * teapot_y_ * teapot_z_ * ubo_matrix_stride_;
for( int32_t i = 0; i < teapot_x_ * teapot_y_ * teapot_z_; ++i )
{
//Rotation
float fX, fY;
vec_current_rotations_[i] += vec_rotations_[i];
vec_current_rotations_[i].Value( fX, fY );
ndk_helper::Mat4 mat_rotation = ndk_helper::Mat4::RotationX( fX )
* ndk_helper::Mat4::RotationY( fY );
// Feed Projection and Model View matrices to the shaders
ndk_helper::Mat4 mat_v = mat_view_ * vec_mat_models_[i] * mat_rotation;
ndk_helper::Mat4 mat_vp = mat_projection_ * mat_v;
memcpy( pMVPMat, mat_vp.Ptr(), sizeof(mat_v) );
pMVPMat += ubo_matrix_stride_;
memcpy( pMVMat, mat_v.Ptr(), sizeof(mat_v) );
pMVMat += ubo_matrix_stride_;
}
glUnmapBuffer( GL_UNIFORM_BUFFER );
//Instanced rendering
glDrawElementsInstanced( GL_TRIANGLES, num_indices_, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0),
teapot_x_ * teapot_y_ * teapot_z_ );
}
else
{
//Regular rendering pass
for( int32_t i = 0; i < teapot_x_ * teapot_y_ * teapot_z_; ++i )
{
//Set diffuse
float x, y, z;
vec_colors_[i].Value( x, y, z );
glUniform4f( shader_param_.material_diffuse_, x, y, z, 1.f );
//Rotation
vec_current_rotations_[i] += vec_rotations_[i];
vec_current_rotations_[i].Value( x, y );
ndk_helper::Mat4 mat_rotation = ndk_helper::Mat4::RotationX( x )
* ndk_helper::Mat4::RotationY( y );
// Feed Projection and Model View matrices to the shaders
ndk_helper::Mat4 mat_v = mat_view_ * vec_mat_models_[i] * mat_rotation;
ndk_helper::Mat4 mat_vp = mat_projection_ * mat_v;
glUniformMatrix4fv( shader_param_.matrix_projection_, 1, GL_FALSE, mat_vp.Ptr() );
glUniformMatrix4fv( shader_param_.matrix_view_, 1, GL_FALSE, mat_v.Ptr() );
glDrawElements( GL_TRIANGLES, num_indices_, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0) );
}
}
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
}
//--------------------------------------------------------------------------------
// LoadShaders
//--------------------------------------------------------------------------------
bool MoreTeapotsRenderer::LoadShaders( SHADER_PARAMS* params,
const char* strVsh,
const char* strFsh )
{
//
//Shader load for GLES2
//In GLES2.0, shader attribute locations need to be explicitly specified before linking
//
GLuint program;
GLuint vertShader, fragShader;
char *vertShaderPathname, *fragShaderPathname;
// Create shader program
program = glCreateProgram();
LOGI( "Created Shader %d", program );
// Create and compile vertex shader
if( !ndk_helper::shader::CompileShader( &vertShader, GL_VERTEX_SHADER, strVsh ) )
{
LOGI( "Failed to compile vertex shader" );
glDeleteProgram( program );
return false;
}
// Create and compile fragment shader
if( !ndk_helper::shader::CompileShader( &fragShader, GL_FRAGMENT_SHADER, strFsh ) )
{
LOGI( "Failed to compile fragment shader" );
glDeleteProgram( program );
return false;
}
// Attach vertex shader to program
glAttachShader( program, vertShader );
// Attach fragment shader to program
glAttachShader( program, fragShader );
// Bind attribute locations
// this needs to be done prior to linking
glBindAttribLocation( program, ATTRIB_VERTEX, "myVertex" );
glBindAttribLocation( program, ATTRIB_NORMAL, "myNormal" );
// Link program
if( !ndk_helper::shader::LinkProgram( program ) )
{
LOGI( "Failed to link program: %d", program );
if( vertShader )
{
glDeleteShader( vertShader );
vertShader = 0;
}
if( fragShader )
{
glDeleteShader( fragShader );
fragShader = 0;
}
if( program )
{
glDeleteProgram( program );
}
return false;
}
// Get uniform locations
params->matrix_projection_ = glGetUniformLocation( program, "uPMatrix" );
params->matrix_view_ = glGetUniformLocation( program, "uMVMatrix" );
params->light0_ = glGetUniformLocation( program, "vLight0" );
params->material_diffuse_ = glGetUniformLocation( program, "vMaterialDiffuse" );
params->material_ambient_ = glGetUniformLocation( program, "vMaterialAmbient" );
params->material_specular_ = glGetUniformLocation( program, "vMaterialSpecular" );
// Release vertex and fragment shaders
if( vertShader )
glDeleteShader( vertShader );
if( fragShader )
glDeleteShader( fragShader );
params->program_ = program;
return true;
}
bool MoreTeapotsRenderer::LoadShadersES3( SHADER_PARAMS* params,
const char* strVsh,
const char* strFsh,
std::map<std::string, std::string>&shaderParams )
{
//
//Shader load for GLES3
//In GLES3.0, shader attribute index can be described in a shader code directly with layout() attribute
//
GLuint program;
GLuint vertShader, fragShader;
char *vertShaderPathname, *fragShaderPathname;
// Create shader program
program = glCreateProgram();
LOGI( "Created Shader %d", program );
// Create and compile vertex shader
if( !ndk_helper::shader::CompileShader( &vertShader, GL_VERTEX_SHADER, strVsh, shaderParams ) )
{
LOGI( "Failed to compile vertex shader" );
glDeleteProgram( program );
return false;
}
// Create and compile fragment shader
if( !ndk_helper::shader::CompileShader( &fragShader, GL_FRAGMENT_SHADER, strFsh,
shaderParams ) )
{
LOGI( "Failed to compile fragment shader" );
glDeleteProgram( program );
return false;
}
// Attach vertex shader to program
glAttachShader( program, vertShader );
// Attach fragment shader to program
glAttachShader( program, fragShader );
// Link program
if( !ndk_helper::shader::LinkProgram( program ) )
{
LOGI( "Failed to link program: %d", program );
if( vertShader )
{
glDeleteShader( vertShader );
vertShader = 0;
}
if( fragShader )
{
glDeleteShader( fragShader );
fragShader = 0;
}
if( program )
{
glDeleteProgram( program );
}
return false;
}
// Get uniform locations
params->light0_ = glGetUniformLocation( program, "vLight0" );
params->material_ambient_ = glGetUniformLocation( program, "vMaterialAmbient" );
params->material_specular_ = glGetUniformLocation( program, "vMaterialSpecular" );
// Release vertex and fragment shaders
if( vertShader )
glDeleteShader( vertShader );
if( fragShader )
glDeleteShader( fragShader );
params->program_ = program;
return true;
}
//--------------------------------------------------------------------------------
// Bind
//--------------------------------------------------------------------------------
bool MoreTeapotsRenderer::Bind( ndk_helper::TapCamera* camera )
{
camera_ = camera;
return true;
}
//--------------------------------------------------------------------------------
// Helper functions
//--------------------------------------------------------------------------------
std::string MoreTeapotsRenderer::ToString( const int32_t i )
{
char str[64];
snprintf( str, sizeof(str), "%d", i );
return std::string( str );
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//--------------------------------------------------------------------------------
// MoreTeapotsRenderer.h
// Renderer for teapots
//--------------------------------------------------------------------------------
#ifndef _MoreTeapotsRenderer_H
#define _MoreTeapotsRenderer_H
//--------------------------------------------------------------------------------
// Include files
//--------------------------------------------------------------------------------
#include <jni.h>
#include <errno.h>
#include <vector>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#include <android/native_window_jni.h>
#include <cpu-features.h>
#define CLASS_NAME "android/app/NativeActivity"
#define APPLICATION_CLASS_NAME "com/sample/moreteapotss/MoreTeapotsApplication"
#include "NDKHelper.h"
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
struct TEAPOT_VERTEX
{
float pos[3];
float normal[3];
};
enum SHADER_ATTRIBUTES
{
ATTRIB_VERTEX, ATTRIB_NORMAL, ATTRIB_COLOR, ATTRIB_UV
};
struct SHADER_PARAMS
{
GLuint program_;
GLuint light0_;
GLuint material_diffuse_;
GLuint material_ambient_;
GLuint material_specular_;
GLuint matrix_projection_;
GLuint matrix_view_;
};
struct TEAPOT_MATERIALS
{
float specular_color[4];
float ambient_color[3];
};
class MoreTeapotsRenderer
{
int32_t num_indices_;
int32_t num_vertices_;
GLuint ibo_;
GLuint vbo_;
GLuint ubo_;
SHADER_PARAMS shader_param_;
bool LoadShaders( SHADER_PARAMS* params,
const char* strVsh,
const char* strFsh );
bool LoadShadersES3( SHADER_PARAMS* params,
const char* strVsh,
const char* strFsh,
std::map<std::string, std::string>&shaderParameters );
ndk_helper::Mat4 mat_projection_;
ndk_helper::Mat4 mat_view_;
std::vector<ndk_helper::Mat4> vec_mat_models_;
std::vector<ndk_helper::Vec3> vec_colors_;
std::vector<ndk_helper::Vec2> vec_rotations_;
std::vector<ndk_helper::Vec2> vec_current_rotations_;
ndk_helper::TapCamera* camera_;
int32_t teapot_x_;
int32_t teapot_y_;
int32_t teapot_z_;
int32_t ubo_matrix_stride_;
int32_t ubo_vector_stride_;
bool geometry_instancing_support_;
bool arb_support_;
std::string ToString( const int32_t i );
public:
MoreTeapotsRenderer();
virtual ~MoreTeapotsRenderer();
void Init( const int32_t numX,
const int32_t numY,
const int32_t numZ );
void Render();
void Update( float dTime );
bool Bind( ndk_helper::TapCamera* camera );
void Unload();
void UpdateViewport();
};
#endif

2057
MoreTeapots/jni/teapot.inl Normal file

File diff suppressed because it is too large Load Diff

3
MoreTeapots/lint.xml Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
</lint>

View File

@@ -0,0 +1,14 @@
# 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 edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=Google Inc.:Google APIs:19

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -9,8 +9,8 @@
android:id="@+id/textViewFPS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/fps"
android:gravity="right"
android:text="0.0 FPS"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/white" />

View File

@@ -1,6 +1,5 @@
<resources>
<string name="app_name">More Teapots</string>
<string name="fps">0.0 FPS</string>
</resources>

View File

@@ -0,0 +1,202 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sample.helper;
import java.io.File;
import java.io.FileInputStream;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.opengl.GLUtils;
import android.util.Log;
public class NDKHelper
{
private static Context context;
public static void setContext(Context c)
{
Log.i("NDKHelper", "setContext:" + c);
context = c;
}
//
// Load Bitmap
// Java helper is useful decoding PNG, TIFF etc rather than linking libPng
// etc separately
//
private int nextPOT(int i)
{
int pot = 1;
while (pot < i)
pot <<= 1;
return pot;
}
private Bitmap scaleBitmap(Bitmap bitmapToScale, float newWidth, float newHeight)
{
if (bitmapToScale == null)
return null;
// get the original width and height
int width = bitmapToScale.getWidth();
int height = bitmapToScale.getHeight();
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(newWidth / width, newHeight / height);
// recreate the new Bitmap and set it back
return Bitmap.createBitmap(bitmapToScale, 0, 0, bitmapToScale.getWidth(),
bitmapToScale.getHeight(), matrix, true);
}
public boolean loadTexture(String path)
{
Bitmap bitmap = null;
try
{
String str = path;
if (!path.startsWith("/"))
{
str = "/" + path;
}
File file = new File(context.getExternalFilesDir(null), str);
if (file.canRead())
{
bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
} else
{
bitmap = BitmapFactory.decodeStream(context.getResources().getAssets()
.open(path));
}
// Matrix matrix = new Matrix();
// // resize the bit map
// matrix.postScale(-1F, 1F);
//
// // recreate the new Bitmap and set it back
// bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
// bitmap.getHeight(), matrix, true);
} catch (Exception e)
{
Log.w("NDKHelper", "Coundn't load a file:" + path);
return false;
}
if (bitmap != null)
{
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
}
return true;
}
public Bitmap openBitmap(String path, boolean iScalePOT)
{
Bitmap bitmap = null;
try
{
bitmap = BitmapFactory.decodeStream(context.getResources().getAssets()
.open(path));
if (iScalePOT)
{
int originalWidth = getBitmapWidth(bitmap);
int originalHeight = getBitmapHeight(bitmap);
int width = nextPOT(originalWidth);
int height = nextPOT(originalHeight);
if (originalWidth != width || originalHeight != height)
{
// Scale it
bitmap = scaleBitmap(bitmap, width, height);
}
}
} catch (Exception e)
{
Log.w("NDKHelper", "Coundn't load a file:" + path);
}
return bitmap;
}
public int getBitmapWidth(Bitmap bmp)
{
return bmp.getWidth();
}
public int getBitmapHeight(Bitmap bmp)
{
return bmp.getHeight();
}
public void getBitmapPixels(Bitmap bmp, int[] pixels)
{
int w = bmp.getWidth();
int h = bmp.getHeight();
bmp.getPixels(pixels, 0, w, 0, 0, w, h);
}
public void closeBitmap(Bitmap bmp)
{
bmp.recycle();
}
public static String getNativeLibraryDirectory(Context appContext)
{
ApplicationInfo ai = context.getApplicationInfo();
Log.w("NDKHelper", "ai.nativeLibraryDir:" + ai.nativeLibraryDir);
if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
|| (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0)
{
return ai.nativeLibraryDir;
}
return "/system/lib/";
}
public int getNativeAudioBufferSize()
{
int SDK_INT = android.os.Build.VERSION.SDK_INT;
if (SDK_INT >= 17)
{
AudioManager am = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE);
String framesPerBuffer = am
.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
return Integer.parseInt(framesPerBuffer);
} else
{
return 0;
}
}
public int getNativeAudioSampleRate()
{
return AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_SYSTEM);
}
}

View File

@@ -35,9 +35,8 @@ import android.widget.Toast;
public class MoreTeapotsApplication extends Application {
private static Context context;
public void onCreate(){
super.onCreate();
context=getApplicationContext();
NDKHelper.setContext(context);
Log.w("native-activity", "onCreate");
final PackageManager pm = getApplicationContext().getPackageManager();

View File

@@ -16,8 +16,6 @@
package com.sample.moreteapots;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.NativeActivity;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -53,7 +51,6 @@ public class MoreTeapotsNativeActivity extends NativeActivity {
}
@TargetApi(19)
protected void onResume() {
super.onResume();
@@ -76,10 +73,14 @@ public class MoreTeapotsNativeActivity extends NativeActivity {
protected void onPause()
{
super.onPause();
if (_popupWindow != null) {
_popupWindow.dismiss();
_popupWindow = null;
}
}
// Our popup window, you will call it from your C/C++ code later
@TargetApi(19)
void setImmersiveSticky() {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
@@ -94,7 +95,6 @@ public class MoreTeapotsNativeActivity extends NativeActivity {
PopupWindow _popupWindow;
TextView _label;
@SuppressLint("InflateParams")
public void showUI()
{
if( _popupWindow != null )
@@ -120,7 +120,7 @@ public class MoreTeapotsNativeActivity extends NativeActivity {
_activity.setContentView(mainLayout, params);
// Show our UI over NativeActivity window
_popupWindow.showAtLocation(mainLayout, Gravity.TOP | Gravity.START, 10, 10);
_popupWindow.showAtLocation(mainLayout, Gravity.TOP | Gravity.LEFT, 10, 10);
_popupWindow.update();
_label = (TextView)popupView.findViewById(R.id.textViewFPS);

155
README.md
View File

@@ -1,142 +1,51 @@
# Android NDK Samples
NDK Samples
===========
This repository contains sample apps that use the [Android NDK].
This repository contains samples for [Android NDK][0].
For an explanation of the layout of this repository, see
[ARCHITECTURE.md](ARCHITECTURE.md).
Pre-requisites
--------------
## Build and run
- [Android NDK][0]
[![build](https://github.com/android/ndk-samples/actions/workflows/build.yml/badge.svg)](https://github.com/android/ndk-samples/actions)
Getting Started
---------------
1. Clone the repository
2. Open the whole project in Android Studio
3. Install CMake 4.1.0 via the SDK Manager (must be done manually until
https://issuetracker.google.com/443137057 is fixed).
4. Select the sample you want to run in the top bar (you may need to sync gradle
first)
5. Click the play button to run the sample
These samples use the NDK build system, you can build them by
following the instructions in the
[NDK documentation](https://developer.android.com/tools/sdk/ndk/index.html#Samples).
You can also build the samples from the command line if you prefer. Use
`./gradlew build` to build everything (if you're on Windows, use `.\gradlew.bat`
instead of `./gradlew`). For individual tasks, see `./gradlew tasks`. To see the
tasks for an individual sample, run the `tasks` task for that directory. For
example, `./gradlew :camera:basic:tasks` will show the tasks for the
`camera/basic` app.
Support
-------
## I just want something to copy from as a starting point
- [Google+ Community](https://plus.google.com/communities/105153134372062985968)
- [Stack Overflow](http://stackoverflow.com/questions/tagged/android)
The samples in this repository are generally not a good starting point for a
production quality app. They aim to demonstrate individual NDK APIs, but often
make sacrifices to be succinct that make them unsuitable for a production app.
This is gradually changing, but for now you should not do this.
If you've found an error in this sample, please [file an issue](https://github.com/googlesamples/android-ndk/issues/new).
[Now in Android](https://github.com/android/nowinandroid/) is an excellent
resource for production quality apps in general, but does not touch on NDK-
specific issues. https://github.com/DanAlbert/ndk-app-template can help some
with that until this repository is able to.
Patches are encouraged, and may be submitted by [forking this project](https://github.com/googlesamples/android-ndk/fork) and
submitting a pull request through GitHub. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
You're most likely best served by using the New Project wizard in Android Studio
to create a new app, then using those resources and the samples here as a
reference. Android Studio's "Native C++" template is a good starting point for
typical applications that need to use some C++ via JNI. The "Game Activity"
template is a good starting point for game-like apps (that is, apps that do not
use the Android UI, but instead render their own UI using OpenGL or Vulkan).
## Best practices shown here
There are a few best practices shown throughout this repository that should be
explained, but there's no central place to discuss those in the code, so we'll
discuss those here.
### `RegisterNatives`
We prefer using `RegisterNatives()` via `JNI_OnLoad()` over the name-based
matching that Studio's New Project Wizard will typically create, e.g. JNI
functions that follow the pattern `JNIEXPORT void JNICALL
Java_com_etc_ClassName_methodName()`. That approach to matching C/C++ functions
to their Java `native` function (or Kotlin `external fun`) makes for a shorter
demo when there are only a small number of functions, but it has a number of
disadvantages. See the [JNI tips] guide for details.
[JNI tips]: https://developer.android.com/ndk/guides/jni-tips#native-libraries
### Version scripts
All of the app libraries shown here are built using a version script. This is a
file that explicitly lists which symbols should be exported from the library,
and hides all the others. Version scripts function similarly to
`-fvisibility=hidden`, but can go a step further and are capable of hiding
symbols in static libraries that are used by your app. Hiding as many symbols as
possible results in smaller binaries that load faster, as there are fewer
relocations required and LTO can do a better job. They also run faster as
same-library function calls do not need to be made through the PLT. There are no
good reasons to not use a version script for your NDK code. See the NDK
documentation on [controlling symbol visibility] for more information.
You can find these in each sample as the `lib<name>.map.txt` file (where
`<name>` is the name of the library passed to `add_app_library()` in the
`CMakeLists.txt` file). The build plumbing that uses the version scripts is in
the definition of `add_app_library()` in `cmake/AppLibrary.cmake`.
[controlling symbol visibility]: https://developer.android.com/ndk/guides/symbol-visibility
## Additional documentation
- [Add Native Code to Your Project](https://developer.android.com/studio/projects/add-native-code.html)
- [Configure NDK for Android Studio/Gradle Plugin](https://developer.android.com/studio/projects/configure-agp-ndk)
- [CMake for NDK](https://developer.android.com/ndk/guides/cmake.html)
## Support
If you've found an issue with a sample and you know how to fix it, please
[send us a PR!](CONTRIBUTING.md).
If you need to report a bug, where it needs to be filed depends on the type of
issue:
- Problems with the samples themselves:
https://github.com/googlesamples/android-ndk/issues
- Problems with the OS APIs: http://b.android.com (usually the Framework
component)
- Problems with NDK (that is, the compiler):
https://github.com/android/ndk/issues
For questions about using the NDK or the platform APIs, you can ask on:
- [The NDK mailing list](https://groups.google.com/g/android-ndk) (best if
you're not sure where else to ask)
- The [Discussions](https://github.com/android/ndk-samples/discussions) tab of
this repo (best for questions about the samples themselves)
- The NDK's [Discussions](https://github.com/android/ndk/discussions) (best for
questions about the NDK compilers and build systems)
- [Stack Overflow](https://stackoverflow.com/questions/tagged/android)
## Additional NDK samples:
- [Google Play Game Samples with Android Studio](https://github.com/playgameservices/cpp-android-basic-samples)
- [Google Android Vulkan Tutorials](https://github.com/googlesamples/android-vulkan-tutorials)
- [Android Vulkan API Basic Samples](https://github.com/googlesamples/vulkan-basic-samples)
- [Android High Performance Audio](https://github.com/googlesamples/android-audio-high-performance)
## License
License
-------
Copyright 2015 The Android Open Source Project, Inc.
Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for
additional information regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
license agreements. See the NOTICE file distributed with this work for
additional information regarding copyright ownership. The ASF licenses this
file to you under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
https://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
[LICENSE](LICENSE)
[Android NDK]: https://developer.android.com/ndk
[0]: https://developer.android.com/tools/sdk/ndk/

View File

@@ -1,8 +0,0 @@
Android Studio/Gradle DSL References
| Name | Function | Type | Options | Default |
| -------------- | ------------------- | :--: | ------------ | ------- |
| debuggable | Debugging Java code | bool | true / false | |
| ndk.debuggable | Debugging JNI code | bool | true / false | |
Notation: dot(".") notation is same as closure ("{}") notation

View File

@@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sample.teapot"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="19" />
<uses-feature android:glEsVersion="0x00020000"></uses-feature>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:hasCode="true"
android:name="com.sample.teapot.TeapotApplication"
>
<!-- Our activity is the built-in NativeActivity framework class.
This will take care of integrating with our NDK code. -->
<activity android:name="com.sample.teapot.TeapotNativeActivity"
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<!-- Tell NativeActivity the name of or .so -->
<meta-data android:name="android.app.lib_name"
android:value="TeapotNativeActivity" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -19,7 +19,7 @@
#define USE_PHONG (1)
uniform lowp vec3 vMaterialAmbient;
uniform lowp vec4 vMaterialSpecular;
uniform mediump vec4 vMaterialSpecular;
varying lowp vec4 colorDiffuse;

20
Teapot/jni/Android.mk Normal file
View File

@@ -0,0 +1,20 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := TeapotNativeActivity
LOCAL_SRC_FILES := TeapotNativeActivity.cpp \
TeapotRenderer.cpp \
LOCAL_C_INCLUDES :=
LOCAL_CFLAGS :=
LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv2
LOCAL_STATIC_LIBRARIES := cpufeatures android_native_app_glue ndk_helper
include $(BUILD_SHARED_LIBRARY)
$(call import-module,android/ndk_helper)
$(call import-module,android/native_app_glue)
$(call import-module,android/cpufeatures)

View File

@@ -0,0 +1,4 @@
APP_PLATFORM := android-9
APP_ABI := all
APP_STL := stlport_static

View File

@@ -0,0 +1,488 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//--------------------------------------------------------------------------------
// Include files
//--------------------------------------------------------------------------------
#include <jni.h>
#include <errno.h>
#include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#include <android/native_window_jni.h>
#include <cpu-features.h>
#include "TeapotRenderer.h"
#include "NDKHelper.h"
//-------------------------------------------------------------------------
//Preprocessor
//-------------------------------------------------------------------------
#define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper function
//-------------------------------------------------------------------------
//Shared state for our app.
//-------------------------------------------------------------------------
struct android_app;
class Engine
{
TeapotRenderer renderer_;
ndk_helper::GLContext* gl_context_;
bool initialized_resources_;
bool has_focus_;
ndk_helper::DoubletapDetector doubletap_detector_;
ndk_helper::PinchDetector pinch_detector_;
ndk_helper::DragDetector drag_detector_;
ndk_helper::PerfMonitor monitor_;
ndk_helper::TapCamera tap_camera_;
android_app* app_;
ASensorManager* sensor_manager_;
const ASensor* accelerometer_sensor_;
ASensorEventQueue* sensor_event_queue_;
void UpdateFPS( float fFPS );
void ShowUI();
void TransformPosition( ndk_helper::Vec2& vec );
public:
static void HandleCmd( struct android_app* app,
int32_t cmd );
static int32_t HandleInput( android_app* app,
AInputEvent* event );
Engine();
~Engine();
void SetState( android_app* state );
int InitDisplay();
void LoadResources();
void UnloadResources();
void DrawFrame();
void TermDisplay();
void TrimMemory();
bool IsReady();
void UpdatePosition( AInputEvent* event,
int32_t iIndex,
float& fX,
float& fY );
void InitSensors();
void ProcessSensors( int32_t id );
void SuspendSensors();
void ResumeSensors();
};
//-------------------------------------------------------------------------
//Ctor
//-------------------------------------------------------------------------
Engine::Engine() :
initialized_resources_( false ),
has_focus_( false ),
app_( NULL ),
sensor_manager_( NULL ),
accelerometer_sensor_( NULL ),
sensor_event_queue_( NULL )
{
gl_context_ = ndk_helper::GLContext::GetInstance();
}
//-------------------------------------------------------------------------
//Dtor
//-------------------------------------------------------------------------
Engine::~Engine()
{
}
/**
* Load resources
*/
void Engine::LoadResources()
{
renderer_.Init();
renderer_.Bind( &tap_camera_ );
}
/**
* Unload resources
*/
void Engine::UnloadResources()
{
renderer_.Unload();
}
/**
* Initialize an EGL context for the current display.
*/
int Engine::InitDisplay()
{
if( !initialized_resources_ )
{
gl_context_->Init( app_->window );
LoadResources();
initialized_resources_ = true;
}
else
{
// initialize OpenGL ES and EGL
if( EGL_SUCCESS != gl_context_->Resume( app_->window ) )
{
UnloadResources();
LoadResources();
}
}
ShowUI();
// Initialize GL state.
glEnable( GL_CULL_FACE );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
//Note that screen size might have been changed
glViewport( 0, 0, gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() );
renderer_.UpdateViewport();
tap_camera_.SetFlip( 1.f, -1.f, -1.f );
tap_camera_.SetPinchTransformFactor( 2.f, 2.f, 8.f );
return 0;
}
/**
* Just the current frame in the display.
*/
void Engine::DrawFrame()
{
float fFPS;
if( monitor_.Update( fFPS ) )
{
UpdateFPS( fFPS );
}
renderer_.Update( monitor_.GetCurrentTime() );
// Just fill the screen with a color.
glClearColor( 0.5f, 0.5f, 0.5f, 1.f );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
renderer_.Render();
// Swap
if( EGL_SUCCESS != gl_context_->Swap() )
{
UnloadResources();
LoadResources();
}
}
/**
* Tear down the EGL context currently associated with the display.
*/
void Engine::TermDisplay()
{
gl_context_->Suspend();
}
void Engine::TrimMemory()
{
LOGI( "Trimming memory" );
gl_context_->Invalidate();
}
/**
* Process the next input event.
*/
int32_t Engine::HandleInput( android_app* app,
AInputEvent* event )
{
Engine* eng = (Engine*) app->userData;
if( AInputEvent_getType( event ) == AINPUT_EVENT_TYPE_MOTION )
{
ndk_helper::GESTURE_STATE doubleTapState = eng->doubletap_detector_.Detect( event );
ndk_helper::GESTURE_STATE dragState = eng->drag_detector_.Detect( event );
ndk_helper::GESTURE_STATE pinchState = eng->pinch_detector_.Detect( event );
//Double tap detector has a priority over other detectors
if( doubleTapState == ndk_helper::GESTURE_STATE_ACTION )
{
//Detect double tap
eng->tap_camera_.Reset( true );
}
else
{
//Handle drag state
if( dragState & ndk_helper::GESTURE_STATE_START )
{
//Otherwise, start dragging
ndk_helper::Vec2 v;
eng->drag_detector_.GetPointer( v );
eng->TransformPosition( v );
eng->tap_camera_.BeginDrag( v );
}
else if( dragState & ndk_helper::GESTURE_STATE_MOVE )
{
ndk_helper::Vec2 v;
eng->drag_detector_.GetPointer( v );
eng->TransformPosition( v );
eng->tap_camera_.Drag( v );
}
else if( dragState & ndk_helper::GESTURE_STATE_END )
{
eng->tap_camera_.EndDrag();
}
//Handle pinch state
if( pinchState & ndk_helper::GESTURE_STATE_START )
{
//Start new pinch
ndk_helper::Vec2 v1;
ndk_helper::Vec2 v2;
eng->pinch_detector_.GetPointers( v1, v2 );
eng->TransformPosition( v1 );
eng->TransformPosition( v2 );
eng->tap_camera_.BeginPinch( v1, v2 );
}
else if( pinchState & ndk_helper::GESTURE_STATE_MOVE )
{
//Multi touch
//Start new pinch
ndk_helper::Vec2 v1;
ndk_helper::Vec2 v2;
eng->pinch_detector_.GetPointers( v1, v2 );
eng->TransformPosition( v1 );
eng->TransformPosition( v2 );
eng->tap_camera_.Pinch( v1, v2 );
}
}
return 1;
}
return 0;
}
/**
* Process the next main command.
*/
void Engine::HandleCmd( struct android_app* app,
int32_t cmd )
{
Engine* eng = (Engine*) app->userData;
switch( cmd )
{
case APP_CMD_SAVE_STATE:
break;
case APP_CMD_INIT_WINDOW:
// The window is being shown, get it ready.
if( app->window != NULL )
{
eng->InitDisplay();
eng->DrawFrame();
}
break;
case APP_CMD_TERM_WINDOW:
// The window is being hidden or closed, clean it up.
eng->TermDisplay();
eng->has_focus_ = false;
break;
case APP_CMD_STOP:
break;
case APP_CMD_GAINED_FOCUS:
eng->ResumeSensors();
//Start animation
eng->has_focus_ = true;
break;
case APP_CMD_LOST_FOCUS:
eng->SuspendSensors();
// Also stop animating.
eng->has_focus_ = false;
eng->DrawFrame();
break;
case APP_CMD_LOW_MEMORY:
//Free up GL resources
eng->TrimMemory();
break;
}
}
//-------------------------------------------------------------------------
//Sensor handlers
//-------------------------------------------------------------------------
void Engine::InitSensors()
{
sensor_manager_ = ASensorManager_getInstance();
accelerometer_sensor_ = ASensorManager_getDefaultSensor( sensor_manager_,
ASENSOR_TYPE_ACCELEROMETER );
sensor_event_queue_ = ASensorManager_createEventQueue( sensor_manager_, app_->looper,
LOOPER_ID_USER, NULL, NULL );
}
void Engine::ProcessSensors( int32_t id )
{
// If a sensor has data, process it now.
if( id == LOOPER_ID_USER )
{
if( accelerometer_sensor_ != NULL )
{
ASensorEvent event;
while( ASensorEventQueue_getEvents( sensor_event_queue_, &event, 1 ) > 0 )
{
}
}
}
}
void Engine::ResumeSensors()
{
// When our app gains focus, we start monitoring the accelerometer.
if( accelerometer_sensor_ != NULL )
{
ASensorEventQueue_enableSensor( sensor_event_queue_, accelerometer_sensor_ );
// We'd like to get 60 events per second (in us).
ASensorEventQueue_setEventRate( sensor_event_queue_, accelerometer_sensor_,
(1000L / 60) * 1000 );
}
}
void Engine::SuspendSensors()
{
// When our app loses focus, we stop monitoring the accelerometer.
// This is to avoid consuming battery while not being used.
if( accelerometer_sensor_ != NULL )
{
ASensorEventQueue_disableSensor( sensor_event_queue_, accelerometer_sensor_ );
}
}
//-------------------------------------------------------------------------
//Misc
//-------------------------------------------------------------------------
void Engine::SetState( android_app* state )
{
app_ = state;
doubletap_detector_.SetConfiguration( app_->config );
drag_detector_.SetConfiguration( app_->config );
pinch_detector_.SetConfiguration( app_->config );
}
bool Engine::IsReady()
{
if( has_focus_ )
return true;
return false;
}
void Engine::TransformPosition( ndk_helper::Vec2& vec )
{
vec = ndk_helper::Vec2( 2.0f, 2.0f ) * vec
/ ndk_helper::Vec2( gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() )
- ndk_helper::Vec2( 1.f, 1.f );
}
void Engine::ShowUI()
{
JNIEnv *jni;
app_->activity->vm->AttachCurrentThread( &jni, NULL );
//Default class retrieval
jclass clazz = jni->GetObjectClass( app_->activity->clazz );
jmethodID methodID = jni->GetMethodID( clazz, "showUI", "()V" );
jni->CallVoidMethod( app_->activity->clazz, methodID );
app_->activity->vm->DetachCurrentThread();
return;
}
void Engine::UpdateFPS( float fFPS )
{
JNIEnv *jni;
app_->activity->vm->AttachCurrentThread( &jni, NULL );
//Default class retrieval
jclass clazz = jni->GetObjectClass( app_->activity->clazz );
jmethodID methodID = jni->GetMethodID( clazz, "updateFPS", "(F)V" );
jni->CallVoidMethod( app_->activity->clazz, methodID, fFPS );
app_->activity->vm->DetachCurrentThread();
return;
}
Engine g_engine;
/**
* This is the main entry point of a native application that is using
* android_native_app_glue. It runs in its own thread, with its own
* event loop for receiving input events and doing other things.
*/
void android_main( android_app* state )
{
app_dummy();
g_engine.SetState( state );
//Init helper functions
ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME );
state->userData = &g_engine;
state->onAppCmd = Engine::HandleCmd;
state->onInputEvent = Engine::HandleInput;
#ifdef USE_NDK_PROFILER
monstartup("libTeapotNativeActivity.so");
#endif
// Prepare to monitor accelerometer
g_engine.InitSensors();
// loop waiting for stuff to do.
while( 1 )
{
// Read all pending events.
int id;
int events;
android_poll_source* source;
// If not animating, we will block forever waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while( (id = ALooper_pollAll( g_engine.IsReady() ? 0 : -1, NULL, &events, (void**) &source ))
>= 0 )
{
// Process this event.
if( source != NULL )
source->process( state, source );
g_engine.ProcessSensors( id );
// Check if we are exiting.
if( state->destroyRequested != 0 )
{
g_engine.TermDisplay();
return;
}
}
if( g_engine.IsReady() )
{
// Drawing is throttled to the screen update rate, so there
// is no need to do timing here.
g_engine.DrawFrame();
}
}
}

View File

@@ -0,0 +1,286 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//--------------------------------------------------------------------------------
// TeapotRenderer.cpp
// Render a teapot
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
// Include files
//--------------------------------------------------------------------------------
#include "TeapotRenderer.h"
//--------------------------------------------------------------------------------
// Teapot model data
//--------------------------------------------------------------------------------
#include "teapot.inl"
//--------------------------------------------------------------------------------
// Ctor
//--------------------------------------------------------------------------------
TeapotRenderer::TeapotRenderer()
{
}
//--------------------------------------------------------------------------------
// Dtor
//--------------------------------------------------------------------------------
TeapotRenderer::~TeapotRenderer()
{
Unload();
}
void TeapotRenderer::Init()
{
//Settings
glFrontFace( GL_CCW );
//Load shader
LoadShaders( &shader_param_, "Shaders/VS_ShaderPlain.vsh",
"Shaders/ShaderPlain.fsh" );
//Create Index buffer
num_indices_ = sizeof(teapotIndices) / sizeof(teapotIndices[0]);
glGenBuffers( 1, &ibo_ );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo_ );
glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof(teapotIndices), teapotIndices,
GL_STATIC_DRAW );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
//Create VBO
num_vertices_ = sizeof(teapotPositions) / sizeof(teapotPositions[0]) / 3;
int32_t iStride = sizeof(TEAPOT_VERTEX);
int32_t iIndex = 0;
TEAPOT_VERTEX* p = new TEAPOT_VERTEX[num_vertices_];
for( int32_t i = 0; i < num_vertices_; ++i )
{
p[i].pos[0] = teapotPositions[iIndex];
p[i].pos[1] = teapotPositions[iIndex + 1];
p[i].pos[2] = teapotPositions[iIndex + 2];
p[i].normal[0] = teapotNormals[iIndex];
p[i].normal[1] = teapotNormals[iIndex + 1];
p[i].normal[2] = teapotNormals[iIndex + 2];
iIndex += 3;
}
glGenBuffers( 1, &vbo_ );
glBindBuffer( GL_ARRAY_BUFFER, vbo_ );
glBufferData( GL_ARRAY_BUFFER, iStride * num_vertices_, p, GL_STATIC_DRAW );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
delete[] p;
UpdateViewport();
mat_model_ = ndk_helper::Mat4::Translation( 0, 0, -15.f );
ndk_helper::Mat4 mat = ndk_helper::Mat4::RotationX( M_PI / 3 );
mat_model_ = mat * mat_model_;
}
void TeapotRenderer::UpdateViewport()
{
//Init Projection matrices
int32_t viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport );
float fAspect = (float) viewport[2] / (float) viewport[3];
const float CAM_NEAR = 5.f;
const float CAM_FAR = 10000.f;
bool bRotate = false;
mat_projection_ = ndk_helper::Mat4::Perspective( fAspect, 1.f, CAM_NEAR, CAM_FAR );
}
void TeapotRenderer::Unload()
{
if( vbo_ )
{
glDeleteBuffers( 1, &vbo_ );
vbo_ = 0;
}
if( ibo_ )
{
glDeleteBuffers( 1, &ibo_ );
ibo_ = 0;
}
if( shader_param_.program_ )
{
glDeleteProgram( shader_param_.program_ );
shader_param_.program_ = 0;
}
}
void TeapotRenderer::Update( float fTime )
{
const float CAM_X = 0.f;
const float CAM_Y = 0.f;
const float CAM_Z = 700.f;
mat_view_ = ndk_helper::Mat4::LookAt( ndk_helper::Vec3( CAM_X, CAM_Y, CAM_Z ),
ndk_helper::Vec3( 0.f, 0.f, 0.f ), ndk_helper::Vec3( 0.f, 1.f, 0.f ) );
if( camera_ )
{
camera_->Update();
mat_view_ = camera_->GetTransformMatrix() * mat_view_
* camera_->GetRotationMatrix() * mat_model_;
}
else
{
mat_view_ = mat_view_ * mat_model_;
}
}
void TeapotRenderer::Render()
{
//
// Feed Projection and Model View matrices to the shaders
ndk_helper::Mat4 mat_vp = mat_projection_ * mat_view_;
// Bind the VBO
glBindBuffer( GL_ARRAY_BUFFER, vbo_ );
int32_t iStride = sizeof(TEAPOT_VERTEX);
// Pass the vertex data
glVertexAttribPointer( ATTRIB_VERTEX, 3, GL_FLOAT, GL_FALSE, iStride,
BUFFER_OFFSET( 0 ) );
glEnableVertexAttribArray( ATTRIB_VERTEX );
glVertexAttribPointer( ATTRIB_NORMAL, 3, GL_FLOAT, GL_FALSE, iStride,
BUFFER_OFFSET( 3 * sizeof(GLfloat) ) );
glEnableVertexAttribArray( ATTRIB_NORMAL );
// Bind the IB
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ibo_ );
glUseProgram( shader_param_.program_ );
TEAPOT_MATERIALS material = { { 1.0f, 0.5f, 0.5f }, { 1.0f, 1.0f, 1.0f, 10.f }, {
0.1f, 0.1f, 0.1f }, };
//Update uniforms
glUniform4f( shader_param_.material_diffuse_, material.diffuse_color[0],
material.diffuse_color[1], material.diffuse_color[2], 1.f );
glUniform4f( shader_param_.material_specular_, material.specular_color[0],
material.specular_color[1], material.specular_color[2],
material.specular_color[3] );
//
//using glUniform3fv here was troublesome
//
glUniform3f( shader_param_.material_ambient_, material.ambient_color[0],
material.ambient_color[1], material.ambient_color[2] );
glUniformMatrix4fv( shader_param_.matrix_projection_, 1, GL_FALSE, mat_vp.Ptr() );
glUniformMatrix4fv( shader_param_.matrix_view_, 1, GL_FALSE, mat_view_.Ptr() );
glUniform3f( shader_param_.light0_, 100.f, -200.f, -600.f );
glDrawElements( GL_TRIANGLES, num_indices_, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0) );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
}
bool TeapotRenderer::LoadShaders( SHADER_PARAMS* params,
const char* strVsh,
const char* strFsh )
{
GLuint program;
GLuint vert_shader, frag_shader;
char *vert_shader_pathname, *frag_shader_pathname;
// Create shader program
program = glCreateProgram();
LOGI( "Created Shader %d", program );
// Create and compile vertex shader
if( !ndk_helper::shader::CompileShader( &vert_shader, GL_VERTEX_SHADER, strVsh ) )
{
LOGI( "Failed to compile vertex shader" );
glDeleteProgram( program );
return false;
}
// Create and compile fragment shader
if( !ndk_helper::shader::CompileShader( &frag_shader, GL_FRAGMENT_SHADER, strFsh ) )
{
LOGI( "Failed to compile fragment shader" );
glDeleteProgram( program );
return false;
}
// Attach vertex shader to program
glAttachShader( program, vert_shader );
// Attach fragment shader to program
glAttachShader( program, frag_shader );
// Bind attribute locations
// this needs to be done prior to linking
glBindAttribLocation( program, ATTRIB_VERTEX, "myVertex" );
glBindAttribLocation( program, ATTRIB_NORMAL, "myNormal" );
glBindAttribLocation( program, ATTRIB_UV, "myUV" );
// Link program
if( !ndk_helper::shader::LinkProgram( program ) )
{
LOGI( "Failed to link program: %d", program );
if( vert_shader )
{
glDeleteShader( vert_shader );
vert_shader = 0;
}
if( frag_shader )
{
glDeleteShader( frag_shader );
frag_shader = 0;
}
if( program )
{
glDeleteProgram( program );
}
return false;
}
// Get uniform locations
params->matrix_projection_ = glGetUniformLocation( program, "uPMatrix" );
params->matrix_view_ = glGetUniformLocation( program, "uMVMatrix" );
params->light0_ = glGetUniformLocation( program, "vLight0" );
params->material_diffuse_ = glGetUniformLocation( program, "vMaterialDiffuse" );
params->material_ambient_ = glGetUniformLocation( program, "vMaterialAmbient" );
params->material_specular_ = glGetUniformLocation( program, "vMaterialSpecular" );
// Release vertex and fragment shaders
if( vert_shader )
glDeleteShader( vert_shader );
if( frag_shader )
glDeleteShader( frag_shader );
params->program_ = program;
return true;
}
bool TeapotRenderer::Bind( ndk_helper::TapCamera* camera )
{
camera_ = camera;
return true;
}

View File

@@ -24,75 +24,82 @@
//--------------------------------------------------------------------------------
// Include files
//--------------------------------------------------------------------------------
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <android/sensor.h>
#include <android_native_app_glue.h>
#include <errno.h>
#include <jni.h>
#include <errno.h>
#include <vector>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue.h>
#include <android/native_window_jni.h>
#include <cpu-features.h>
#define CLASS_NAME "android/app/NativeActivity"
#define APPLICATION_CLASS_NAME "com/sample/teapot/TeapotApplication"
#include "NDKHelper.h"
struct TEAPOT_VERTEX {
float pos[3];
float normal[3];
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
struct TEAPOT_VERTEX
{
float pos[3];
float normal[3];
};
enum SHADER_ATTRIBUTES {
ATTRIB_VERTEX,
ATTRIB_NORMAL,
ATTRIB_UV,
enum SHADER_ATTRIBUTES
{
ATTRIB_VERTEX, ATTRIB_NORMAL, ATTRIB_UV,
};
struct SHADER_PARAMS {
GLuint program_;
GLuint light0_;
GLuint material_diffuse_;
GLuint material_ambient_;
GLuint material_specular_;
struct SHADER_PARAMS
{
GLuint program_;
GLuint light0_;
GLuint material_diffuse_;
GLuint material_ambient_;
GLuint material_specular_;
GLuint matrix_projection_;
GLuint matrix_view_;
GLuint matrix_projection_;
GLuint matrix_view_;
};
struct TEAPOT_MATERIALS {
float diffuse_color[3];
float specular_color[4];
float ambient_color[3];
struct TEAPOT_MATERIALS
{
float diffuse_color[3];
float specular_color[4];
float ambient_color[3];
};
class TeapotRenderer {
int32_t num_indices_;
int32_t num_vertices_;
GLuint ibo_;
GLuint vbo_;
class TeapotRenderer
{
int32_t num_indices_;
int32_t num_vertices_;
GLuint ibo_;
GLuint vbo_;
SHADER_PARAMS shader_param_;
bool LoadShaders(SHADER_PARAMS* params, const char* strVsh,
const char* strFsh);
SHADER_PARAMS shader_param_;
bool LoadShaders( SHADER_PARAMS* params, const char* strVsh, const char* strFsh );
ndk_helper::Mat4 mat_projection_;
ndk_helper::Mat4 mat_view_;
ndk_helper::Mat4 mat_model_;
ndk_helper::Mat4 mat_projection_;
ndk_helper::Mat4 mat_view_;
ndk_helper::Mat4 mat_model_;
ndk_helper::TapCamera* camera_;
public:
TeapotRenderer();
virtual ~TeapotRenderer();
void Init();
void Render(float r, float g, float b);
void Update(double time);
bool Bind(ndk_helper::TapCamera* camera);
void Unload();
void UpdateViewport();
ndk_helper::TapCamera* camera_;
public:
TeapotRenderer();
virtual ~TeapotRenderer();
void Init();
void Render();
void Update( float dTime );
bool Bind( ndk_helper::TapCamera* camera );
void Unload();
void UpdateViewport();
};
#endif

2057
Teapot/jni/teapot.inl Normal file

File diff suppressed because it is too large Load Diff

3
Teapot/lint.xml Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
</lint>

14
Teapot/project.properties Normal file
View File

@@ -0,0 +1,14 @@
# 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 edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=Google Inc.:Google APIs:19

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -9,8 +9,8 @@
android:id="@+id/textViewFPS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:text="@string/fps"
android:gravity="right"
android:text="0.0 FPS"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@android:color/white" />

View File

@@ -1,6 +1,5 @@
<resources>
<string name="app_name">Teapot</string>
<string name="fps">0.0 FPS</string>
</resources>

View File

@@ -0,0 +1,203 @@
/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sample.helper;
import java.io.File;
import java.io.FileInputStream;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.opengl.GLUtils;
import android.util.Log;
public class NDKHelper
{
private static Context context;
public static void setContext(Context c)
{
Log.i("NDKHelper", "setContext:" + c);
context = c;
}
//
// Load Bitmap
// Java helper is useful decoding PNG, TIFF etc rather than linking libPng
// etc separately
//
private int nextPOT(int i)
{
int pot = 1;
while (pot < i)
pot <<= 1;
return pot;
}
private Bitmap scaleBitmap(Bitmap bitmapToScale, float newWidth, float newHeight)
{
if (bitmapToScale == null)
return null;
// get the original width and height
int width = bitmapToScale.getWidth();
int height = bitmapToScale.getHeight();
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(newWidth / width, newHeight / height);
// recreate the new Bitmap and set it back
return Bitmap.createBitmap(bitmapToScale, 0, 0, bitmapToScale.getWidth(),
bitmapToScale.getHeight(), matrix, true);
}
public boolean loadTexture(String path)
{
Bitmap bitmap = null;
try
{
String str = path;
if (!path.startsWith("/"))
{
str = "/" + path;
}
File file = new File(context.getExternalFilesDir(null), str);
if (file.canRead())
{
bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
} else
{
bitmap = BitmapFactory.decodeStream(context.getResources().getAssets()
.open(path));
}
// Matrix matrix = new Matrix();
// // resize the bit map
// matrix.postScale(-1F, 1F);
//
// // recreate the new Bitmap and set it back
// bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
// bitmap.getHeight(), matrix, true);
} catch (Exception e)
{
Log.w("NDKHelper", "Coundn't load a file:" + path);
return false;
}
if (bitmap != null)
{
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
}
return true;
}
public Bitmap openBitmap(String path, boolean iScalePOT)
{
Bitmap bitmap = null;
try
{
bitmap = BitmapFactory.decodeStream(context.getResources().getAssets()
.open(path));
if (iScalePOT)
{
int originalWidth = getBitmapWidth(bitmap);
int originalHeight = getBitmapHeight(bitmap);
int width = nextPOT(originalWidth);
int height = nextPOT(originalHeight);
if (originalWidth != width || originalHeight != height)
{
// Scale it
bitmap = scaleBitmap(bitmap, width, height);
}
}
} catch (Exception e)
{
Log.w("NDKHelper", "Coundn't load a file:" + path);
}
return bitmap;
}
public int getBitmapWidth(Bitmap bmp)
{
return bmp.getWidth();
}
public int getBitmapHeight(Bitmap bmp)
{
return bmp.getHeight();
}
public void getBitmapPixels(Bitmap bmp, int[] pixels)
{
int w = bmp.getWidth();
int h = bmp.getHeight();
bmp.getPixels(pixels, 0, w, 0, 0, w, h);
}
public void closeBitmap(Bitmap bmp)
{
bmp.recycle();
}
public static String getNativeLibraryDirectory(Context appContext)
{
ApplicationInfo ai = context.getApplicationInfo();
Log.w("NDKHelper", "ai.nativeLibraryDir:" + ai.nativeLibraryDir);
if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
|| (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0)
{
return ai.nativeLibraryDir;
}
return "/system/lib/";
}
public int getNativeAudioBufferSize()
{
int SDK_INT = android.os.Build.VERSION.SDK_INT;
if (SDK_INT >= 17)
{
AudioManager am = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE);
String framesPerBuffer = am
.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
return Integer.parseInt(framesPerBuffer);
} else
{
return 0;
}
}
public int getNativeAudioSampleRate()
{
return AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_SYSTEM);
}
}

View File

@@ -32,7 +32,6 @@ import android.widget.Toast;
public class TeapotApplication extends Application {
public void onCreate(){
super.onCreate();
Log.w("native-activity", "onCreate");
final PackageManager pm = getApplicationContext().getPackageManager();

View File

@@ -16,8 +16,6 @@
package com.sample.teapot;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.NativeActivity;
import android.os.Bundle;
import android.view.Gravity;
@@ -51,7 +49,6 @@ public class TeapotNativeActivity extends NativeActivity {
}
@TargetApi(19)
protected void onResume() {
super.onResume();
@@ -73,7 +70,6 @@ public class TeapotNativeActivity extends NativeActivity {
}
// Our popup window, you will call it from your C/C++ code later
@TargetApi(19)
void setImmersiveSticky() {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
@@ -88,7 +84,6 @@ public class TeapotNativeActivity extends NativeActivity {
PopupWindow _popupWindow;
TextView _label;
@SuppressLint("InflateParams")
public void showUI()
{
if( _popupWindow != null )
@@ -114,7 +109,7 @@ public class TeapotNativeActivity extends NativeActivity {
_activity.setContentView(mainLayout, params);
// Show our UI over NativeActivity window
_popupWindow.showAtLocation(mainLayout, Gravity.TOP | Gravity.START, 10, 10);
_popupWindow.showAtLocation(mainLayout, Gravity.TOP | Gravity.LEFT, 10, 10);
_popupWindow.update();
_label = (TextView)popupView.findViewById(R.id.textViewFPS);
@@ -125,6 +120,10 @@ public class TeapotNativeActivity extends NativeActivity {
protected void onPause()
{
super.onPause();
if (_popupWindow != null) {
_popupWindow.dismiss();
_popupWindow = null;
}
}
public void updateFPS(final float fFPS)

View File

@@ -1,6 +0,0 @@
# Sample removed
This sample has been removed because the API it demonstrated (OpenSLES) is
deprecated. New apps should instead use [Oboe], which has its own samples.
[Oboe]: https://github.com/google/oboe

1
base/.gitignore vendored
View File

@@ -1 +0,0 @@
/build

View File

@@ -1,32 +0,0 @@
plugins {
id("ndksamples.android.library")
}
android {
namespace = "com.android.ndk.samples.base"
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
}
}
buildFeatures {
prefabPublishing = true
}
prefab {
create("base") {
headers = "src/main/cpp/include"
}
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
}

View File

@@ -1,14 +0,0 @@
cmake_minimum_required(VERSION 3.22.1)
project(Base LANGUAGES CXX)
include(AppLibrary)
add_app_library(base
STATIC
logging.cpp
)
target_compile_features(base PRIVATE cxx_std_23)
target_compile_options(base PRIVATE -Wno-vla-cxx-extension)
target_include_directories(base PUBLIC include)
target_link_libraries(base PUBLIC log)

View File

@@ -1,39 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <errno.h>
namespace ndksamples::base {
class ErrnoRestorer {
public:
ErrnoRestorer() : saved_errno_(errno) {}
ErrnoRestorer(const ErrnoRestorer&) = delete;
~ErrnoRestorer() { errno = saved_errno_; }
ErrnoRestorer& operator=(const ErrnoRestorer&) = delete;
// Allow this object to be used as part of && operation.
explicit operator bool() const { return true; }
private:
const int saved_errno_;
};
} // namespace ndksamples::base

View File

@@ -1,483 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
/**
* @file logging.h
* @brief Logging and assertion utilities.
*
* This is a modified version of AOSP's android-base/logging.h:
* https://cs.android.com/android/platform/superproject/main/+/main:system/libbase/include/android-base/logging.h
*
* The original file contained a lot of dependencies for things we don't need
* (kernel logging, support for non-Android platforms, non-logd loggers, etc).
* That's all been removed so we don't need to pull in those dependencies.
*
* If you copy from this sample, you may want to replace this with something
* like absl, which provides a very similar (if not identical) interface for all
* platforms. absl was not used here because we didn't need much, and it was
* preferable to avoid an additional dependency.
*/
//
// Google-style C++ logging.
//
// This header provides a C++ stream interface to logging.
//
// To log:
//
// LOG(INFO) << "Some text; " << some_value;
//
// Replace `INFO` with any severity from `enum LogSeverity`.
// Most devices filter out VERBOSE logs by default, run
// `adb shell setprop log.tag.<TAG> V` to see them in adb logcat.
//
// To log the result of a failed function and include the string
// representation of `errno` at the end:
//
// PLOG(ERROR) << "Write failed";
//
// The output will be something like `Write failed: I/O error`.
// Remember this as 'P' as in perror(3).
//
// To output your own types, simply implement operator<< as normal.
//
// By default, output goes to logcat on Android and stderr on the host.
// A process can use `SetLogger` to decide where all logging goes.
// Implementations are provided for logcat, stderr, and dmesg.
//
// By default, the process' name is used as the log tag.
// Code can choose a specific log tag by defining LOG_TAG
// before including this header.
// This header also provides assertions:
//
// CHECK(must_be_true);
// CHECK_EQ(a, b) << z_is_interesting_too;
#include <base/errno_restorer.h>
#include <base/macros.h>
#include <functional>
#include <memory>
#include <optional>
#include <ostream>
#include <string_view>
// Note: DO NOT USE DIRECTLY. Use LOG_TAG instead.
#ifdef _LOG_TAG_INTERNAL
#error "_LOG_TAG_INTERNAL must not be defined"
#endif
#ifdef LOG_TAG
#define _LOG_TAG_INTERNAL LOG_TAG
#else
#define _LOG_TAG_INTERNAL nullptr
#endif
namespace ndksamples::base {
enum LogSeverity {
VERBOSE,
DEBUG,
INFO,
WARNING,
ERROR,
FATAL_WITHOUT_ABORT, // For loggability tests, this is considered identical
// to FATAL.
FATAL,
};
enum LogId {
DEFAULT,
MAIN,
SYSTEM,
RADIO,
CRASH,
};
using LogFunction = std::function<void(
LogId /*log_buffer_id*/, LogSeverity /*severity*/, const char* /*tag*/,
const char* /*file*/, unsigned int /*line*/, const char* /*message*/)>;
using AbortFunction = std::function<void(const char* /*abort_message*/)>;
void DefaultAborter(const char* abort_message);
void SetDefaultTag(const std::string& tag);
// The LogdLogger sends chunks of up to ~4000 bytes at a time to logd. It does
// not prevent other threads from writing to logd between sending each chunk, so
// other threads may interleave their messages. If preventing interleaving is
// required, then a custom logger that takes a lock before calling this logger
// should be provided.
class LogdLogger {
public:
explicit LogdLogger(LogId default_log_id = ndksamples::base::MAIN);
void operator()(LogId, LogSeverity, const char* tag, const char* file,
unsigned int line, const char* message);
private:
LogId default_log_id_;
};
// Configure logging based on ANDROID_LOG_TAGS environment variable.
// We need to parse a string that looks like
//
// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i
//
// The tag (or '*' for the global level) comes first, followed by a colon and a
// letter indicating the minimum priority level we're expected to log. This can
// be used to reveal or conceal logs with specific tags.
#define INIT_LOGGING_DEFAULT_LOGGER LogdLogger()
void InitLogging(const std::optional<std::string_view> default_tag = {},
std::optional<LogSeverity> log_level = {},
LogFunction&& logger = INIT_LOGGING_DEFAULT_LOGGER,
AbortFunction&& aborter = DefaultAborter);
#undef INIT_LOGGING_DEFAULT_LOGGER
// Replace the current logger and return the old one.
LogFunction SetLogger(LogFunction&& logger);
// Replace the current aborter and return the old one.
AbortFunction SetAborter(AbortFunction&& aborter);
// A helper macro that produces an expression that accepts both a qualified name
// and an unqualified name for a LogSeverity, and returns a LogSeverity value.
// Note: DO NOT USE DIRECTLY. This is an implementation detail.
#define SEVERITY_LAMBDA(severity) \
([&]() { \
using ::ndksamples::base::VERBOSE; \
using ::ndksamples::base::DEBUG; \
using ::ndksamples::base::INFO; \
using ::ndksamples::base::WARNING; \
using ::ndksamples::base::ERROR; \
using ::ndksamples::base::FATAL_WITHOUT_ABORT; \
using ::ndksamples::base::FATAL; \
return (severity); \
}())
#define ABORT_AFTER_LOG_FATAL
#define ABORT_AFTER_LOG_EXPR_IF(c, x) (x)
#define MUST_LOG_MESSAGE(severity) false
#define ABORT_AFTER_LOG_FATAL_EXPR(x) ABORT_AFTER_LOG_EXPR_IF(true, x)
// Defines whether the given severity will be logged or silently swallowed.
#define WOULD_LOG(severity) \
(UNLIKELY(::ndksamples::base::ShouldLog(SEVERITY_LAMBDA(severity), \
_LOG_TAG_INTERNAL)) || \
MUST_LOG_MESSAGE(severity))
// Get an ostream that can be used for logging at the given severity and to the
// default destination.
//
// Notes:
// 1) This will not check whether the severity is high enough. One should use
// WOULD_LOG to filter
// usage manually.
// 2) This does not save and restore errno.
#define LOG_STREAM(severity) \
::ndksamples::base::LogMessage( \
__FILE__, __LINE__, SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, -1) \
.stream()
// Logs a message to logcat on Android otherwise to stderr. If the severity is
// FATAL it also causes an abort. For example:
//
// LOG(FATAL) << "We didn't expect to reach here";
#define LOG(severity) LOGGING_PREAMBLE(severity) && LOG_STREAM(severity)
// Checks if we want to log something, and sets up appropriate RAII objects if
// so.
// Note: DO NOT USE DIRECTLY. This is an implementation detail.
#define LOGGING_PREAMBLE(severity) \
(WOULD_LOG(severity) && \
ABORT_AFTER_LOG_EXPR_IF( \
(SEVERITY_LAMBDA(severity)) == ::ndksamples::base::FATAL, true) && \
::ndksamples::base::ErrnoRestorer())
// A variant of LOG that also logs the current errno value. To be used when
// library calls fail.
#define PLOG(severity) \
LOGGING_PREAMBLE(severity) && \
::ndksamples::base::LogMessage(__FILE__, __LINE__, \
SEVERITY_LAMBDA(severity), \
_LOG_TAG_INTERNAL, errno) \
.stream()
// Marker that code is yet to be implemented.
#define UNIMPLEMENTED(level) \
LOG(level) << __PRETTY_FUNCTION__ << " unimplemented "
// Check whether condition x holds and LOG(FATAL) if not. The value of the
// expression x is only evaluated once. Extra logging can be appended using <<
// after. For example:
//
// CHECK(false == true) results in a log message of
// "Check failed: false == true".
#define CHECK(x) \
LIKELY((x)) || ABORT_AFTER_LOG_FATAL_EXPR(false) || \
::ndksamples::base::LogMessage(__FILE__, __LINE__, \
::ndksamples::base::FATAL, \
_LOG_TAG_INTERNAL, -1) \
.stream() \
<< "Check failed: " #x << " "
// clang-format off
// Helper for CHECK_xx(x,y) macros.
#define CHECK_OP(LHS, RHS, OP) \
for (auto _values = ::ndksamples::base::MakeEagerEvaluator(LHS, RHS); \
UNLIKELY(!(_values.lhs.v OP _values.rhs.v)); \
/* empty */) \
ABORT_AFTER_LOG_FATAL \
::ndksamples::base::LogMessage(__FILE__, __LINE__, ::ndksamples::base::FATAL, _LOG_TAG_INTERNAL, -1) \
.stream() \
<< "Check failed: " << #LHS << " " << #OP << " " << #RHS << " (" #LHS "=" \
<< ::ndksamples::base::LogNullGuard<decltype(_values.lhs.v)>::Guard(_values.lhs.v) \
<< ", " #RHS "=" \
<< ::ndksamples::base::LogNullGuard<decltype(_values.rhs.v)>::Guard(_values.rhs.v) \
<< ") "
// clang-format on
// Check whether a condition holds between x and y, LOG(FATAL) if not. The value
// of the expressions x and y is evaluated once. Extra logging can be appended
// using << after. For example:
//
// CHECK_NE(0 == 1, false) results in
// "Check failed: false != false (0==1=false, false=false) ".
#define CHECK_EQ(x, y) CHECK_OP(x, y, ==)
#define CHECK_NE(x, y) CHECK_OP(x, y, !=)
#define CHECK_LE(x, y) CHECK_OP(x, y, <=)
#define CHECK_LT(x, y) CHECK_OP(x, y, <)
#define CHECK_GE(x, y) CHECK_OP(x, y, >=)
#define CHECK_GT(x, y) CHECK_OP(x, y, >)
// clang-format off
// Helper for CHECK_STRxx(s1,s2) macros.
#define CHECK_STROP(s1, s2, sense) \
while (UNLIKELY((strcmp(s1, s2) == 0) != (sense))) \
ABORT_AFTER_LOG_FATAL \
::ndksamples::base::LogMessage(__FILE__, __LINE__, ::ndksamples::base::FATAL, \
_LOG_TAG_INTERNAL, -1) \
.stream() \
<< "Check failed: " << "\"" << (s1) << "\"" \
<< ((sense) ? " == " : " != ") << "\"" << (s2) << "\""
// clang-format on
// Check for string (const char*) equality between s1 and s2, LOG(FATAL) if not.
#define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true)
#define CHECK_STRNE(s1, s2) CHECK_STROP(s1, s2, false)
// Perform the pthread function call(args), LOG(FATAL) on error.
#define CHECK_PTHREAD_CALL(call, args, what) \
do { \
int rc = call args; \
if (rc != 0) { \
errno = rc; \
ABORT_AFTER_LOG_FATAL \
PLOG(FATAL) << #call << " failed for " << (what); \
} \
} while (false)
// DCHECKs are debug variants of CHECKs only enabled in debug builds. Generally
// CHECK should be used unless profiling identifies a CHECK as being in
// performance critical code.
#if defined(NDEBUG) && !defined(__clang_analyzer__)
static constexpr bool kEnableDChecks = false;
#else
static constexpr bool kEnableDChecks = true;
#endif
#define DCHECK(x) \
if (::ndksamples::base::kEnableDChecks) CHECK(x)
#define DCHECK_EQ(x, y) \
if (::ndksamples::base::kEnableDChecks) CHECK_EQ(x, y)
#define DCHECK_NE(x, y) \
if (::ndksamples::base::kEnableDChecks) CHECK_NE(x, y)
#define DCHECK_LE(x, y) \
if (::ndksamples::base::kEnableDChecks) CHECK_LE(x, y)
#define DCHECK_LT(x, y) \
if (::ndksamples::base::kEnableDChecks) CHECK_LT(x, y)
#define DCHECK_GE(x, y) \
if (::ndksamples::base::kEnableDChecks) CHECK_GE(x, y)
#define DCHECK_GT(x, y) \
if (::ndksamples::base::kEnableDChecks) CHECK_GT(x, y)
#define DCHECK_STREQ(s1, s2) \
if (::ndksamples::base::kEnableDChecks) CHECK_STREQ(s1, s2)
#define DCHECK_STRNE(s1, s2) \
if (::ndksamples::base::kEnableDChecks) CHECK_STRNE(s1, s2)
namespace log_detail {
// Temporary storage for a single eagerly evaluated check expression operand.
template <typename T>
struct Storage {
template <typename U>
explicit constexpr Storage(U&& u) : v(std::forward<U>(u)) {}
explicit Storage(const Storage& t) = delete;
explicit Storage(Storage&& t) = delete;
T v;
};
// Partial specialization for smart pointers to avoid copying.
template <typename T>
struct Storage<std::unique_ptr<T>> {
explicit constexpr Storage(const std::unique_ptr<T>& ptr) : v(ptr.get()) {}
const T* v;
};
template <typename T>
struct Storage<std::shared_ptr<T>> {
explicit constexpr Storage(const std::shared_ptr<T>& ptr) : v(ptr.get()) {}
const T* v;
};
// Type trait that checks if a type is a (potentially const) char pointer.
template <typename T>
struct IsCharPointer {
using Pointee = std::remove_cv_t<std::remove_pointer_t<T>>;
static constexpr bool value =
std::is_pointer_v<T> &&
(std::is_same_v<Pointee, char> || std::is_same_v<Pointee, signed char> ||
std::is_same_v<Pointee, unsigned char>);
};
// Counterpart to Storage that depends on both operands. This is used to prevent
// char pointers being treated as strings in the log output - they might point
// to buffers of unprintable binary data.
template <typename LHS, typename RHS>
struct StorageTypes {
static constexpr bool voidptr =
IsCharPointer<LHS>::value && IsCharPointer<RHS>::value;
using LHSType = std::conditional_t<voidptr, const void*, LHS>;
using RHSType = std::conditional_t<voidptr, const void*, RHS>;
};
// Temporary class created to evaluate the LHS and RHS, used with
// MakeEagerEvaluator to infer the types of LHS and RHS.
template <typename LHS, typename RHS>
struct EagerEvaluator {
template <typename A, typename B>
constexpr EagerEvaluator(A&& l, B&& r)
: lhs(std::forward<A>(l)), rhs(std::forward<B>(r)) {}
const Storage<typename StorageTypes<LHS, RHS>::LHSType> lhs;
const Storage<typename StorageTypes<LHS, RHS>::RHSType> rhs;
};
} // namespace log_detail
// Converts std::nullptr_t and null char pointers to the string "null"
// when writing the failure message.
template <typename T>
struct LogNullGuard {
static const T& Guard(const T& v) { return v; }
};
template <>
struct LogNullGuard<std::nullptr_t> {
static const char* Guard(const std::nullptr_t&) { return "(null)"; }
};
template <>
struct LogNullGuard<char*> {
static const char* Guard(const char* v) { return v ? v : "(null)"; }
};
template <>
struct LogNullGuard<const char*> {
static const char* Guard(const char* v) { return v ? v : "(null)"; }
};
// Helper function for CHECK_xx.
template <typename LHS, typename RHS>
constexpr auto MakeEagerEvaluator(LHS&& lhs, RHS&& rhs) {
return log_detail::EagerEvaluator<std::decay_t<LHS>, std::decay_t<RHS>>(
std::forward<LHS>(lhs), std::forward<RHS>(rhs));
}
// Data for the log message, not stored in LogMessage to avoid increasing the
// stack size.
class LogMessageData;
// A LogMessage is a temporarily scoped object used by LOG and the unlikely part
// of a CHECK. The destructor will abort if the severity is FATAL.
class LogMessage {
public:
// LogId has been deprecated, but this constructor must exist for prebuilts.
LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity,
const char* tag, int error);
LogMessage(const char* file, unsigned int line, LogSeverity severity,
const char* tag, int error);
DISALLOW_COPY_AND_ASSIGN(LogMessage);
~LogMessage();
// Returns the stream associated with the message, the LogMessage performs
// output when it goes out of scope.
std::ostream& stream();
// The routine that performs the actual logging.
static void LogLine(const char* file, unsigned int line, LogSeverity severity,
const char* tag, const char* msg);
private:
const std::unique_ptr<LogMessageData> data_;
};
// Get the minimum severity level for logging.
LogSeverity GetMinimumLogSeverity();
// Set the minimum severity level for logging, returning the old severity.
LogSeverity SetMinimumLogSeverity(LogSeverity new_severity);
// Return whether or not a log message with the associated tag should be logged.
bool ShouldLog(LogSeverity severity, const char* tag);
// Allows to temporarily change the minimum severity level for logging.
class ScopedLogSeverity {
public:
explicit ScopedLogSeverity(LogSeverity level);
~ScopedLogSeverity();
private:
LogSeverity old_;
};
} // namespace ndksamples::base
namespace std { // NOLINT(cert-dcl58-cpp)
// Emit a warning of ostream<< with std::string*. The intention was most likely
// to print *string.
//
// Note: for this to work, we need to have this in a namespace.
// Note: using a pragma because "-Wgcc-compat" (included in "-Weverything")
// complains about
// diagnose_if.
// Note: to print the pointer, use "<< static_cast<const void*>(string_pointer)"
// instead. Note: a not-recommended alternative is to let Clang ignore the
// warning by adding
// -Wno-user-defined-warnings to CPPFLAGS.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgcc-compat"
#define OSTREAM_STRING_POINTER_USAGE_WARNING \
__attribute__(( \
diagnose_if(true, "Unexpected logging of string pointer", "warning")))
inline OSTREAM_STRING_POINTER_USAGE_WARNING std::ostream& operator<<(
std::ostream& stream, const std::string* string_pointer) {
return stream << static_cast<const void*>(string_pointer);
}
#pragma clang diagnostic pop
} // namespace std

View File

@@ -1,91 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stddef.h> // for size_t
#include <utility>
// A macro to disallow the copy constructor and operator= functions
// This must be placed in the private: declarations for a class.
//
// For disallowing only assign or copy, delete the relevant operator or
// constructor, for example:
// void operator=(const TypeName&) = delete;
// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken
// semantically, one should either use disallow both or neither. Try to
// avoid these in new code.
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
void operator=(const TypeName&) = delete
// A macro to disallow all the implicit constructors, namely the
// default constructor, copy constructor and operator= functions.
//
// This should be used in the private: declarations for a class
// that wants to prevent anyone from instantiating it. This is
// especially useful for classes containing only static methods.
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
TypeName() = delete; \
DISALLOW_COPY_AND_ASSIGN(TypeName)
// The arraysize(arr) macro returns the # of elements in an array arr.
// The expression is a compile-time constant, and therefore can be
// used in defining new arrays, for example. If you use arraysize on
// a pointer by mistake, you will get a compile-time error.
//
// One caveat is that arraysize() doesn't accept any array of an
// anonymous type or a type defined inside a function. In these rare
// cases, you have to use the unsafe ARRAYSIZE_UNSAFE() macro below. This is
// due to a limitation in C++'s template system. The limitation might
// eventually be removed, but it hasn't happened yet.
// This template function declaration is used in defining arraysize.
// Note that the function doesn't need an implementation, as we only
// use its type.
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N]; // NOLINT(readability/casting)
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
#define SIZEOF_MEMBER(t, f) sizeof(std::declval<t>().f)
// Changing this definition will cause you a lot of pain. A majority of
// vendor code defines LIKELY and UNLIKELY this way, and includes
// this header through an indirect path.
#define LIKELY(exp) (__builtin_expect((exp) != 0, true))
#define UNLIKELY(exp) (__builtin_expect((exp) != 0, false))
/// True if the (runtime) version of the OS is at least x.
///
/// Clang is very particular about how __builtin_available is used. Logical
/// operations (including negation) may not be combined with
/// __builtin_available, so to negate this check you must do:
///
/// if (API_AT_LEAST(x)) {
/// } else {
/// // do negated stuff
/// }
#define API_AT_LEAST(x) __builtin_available(android x, *)
/// Marks a function as not callable on OS versions older than x.
///
/// This is a minor abuse of Clang's __attribute__((availability)), so the
/// diagnostic for this will be a little odd, but it allows us to extract
/// functions from code that already has an API_AT_LEAST guard without rewriting
/// the guard in every called function.
#define REQUIRES_API(x) __INTRODUCED_IN(x)

View File

@@ -1,294 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "base/logging.h"
#include <android/log.h>
#include <android/set_abort_message.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libgen.h>
#include <stdlib.h>
#include <time.h>
#include <atomic>
#include <iostream>
#include <limits>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "logging_splitters.h"
namespace ndksamples::base {
static const char* GetFileBasename(const char* file) {
// We can't use basename(3) even on Unix because the Mac doesn't
// have a non-modifying basename.
const char* last_slash = strrchr(file, '/');
if (last_slash != nullptr) {
return last_slash + 1;
}
return file;
}
static int32_t LogIdTolog_id_t(LogId log_id) {
switch (log_id) {
case MAIN:
return LOG_ID_MAIN;
case SYSTEM:
return LOG_ID_SYSTEM;
case RADIO:
return LOG_ID_RADIO;
case CRASH:
return LOG_ID_CRASH;
case DEFAULT:
default:
return LOG_ID_DEFAULT;
}
}
static int32_t LogSeverityToPriority(LogSeverity severity) {
switch (severity) {
case VERBOSE:
return ANDROID_LOG_VERBOSE;
case DEBUG:
return ANDROID_LOG_DEBUG;
case INFO:
return ANDROID_LOG_INFO;
case WARNING:
return ANDROID_LOG_WARN;
case ERROR:
return ANDROID_LOG_ERROR;
case FATAL_WITHOUT_ABORT:
case FATAL:
default:
return ANDROID_LOG_FATAL;
}
}
static LogFunction& Logger() {
static auto& logger = *new LogFunction(LogdLogger());
return logger;
}
static AbortFunction& Aborter() {
static auto& aborter = *new AbortFunction(DefaultAborter);
return aborter;
}
// Only used for Q fallback.
static std::recursive_mutex& TagLock() {
static auto& tag_lock = *new std::recursive_mutex();
return tag_lock;
}
static std::string* gDefaultTag;
void SetDefaultTag(const std::string_view tag) {
std::lock_guard<std::recursive_mutex> lock(TagLock());
if (gDefaultTag != nullptr) {
delete gDefaultTag;
gDefaultTag = nullptr;
}
if (!tag.empty()) {
gDefaultTag = new std::string(tag);
}
}
static bool gInitialized = false;
// Only used for Q fallback.
static LogSeverity gMinimumLogSeverity = INFO;
void DefaultAborter(const char* abort_message) {
android_set_abort_message(abort_message);
abort();
}
static void LogdLogChunk(LogId id, LogSeverity severity, const char* tag,
const char* message) {
int32_t lg_id = LogIdTolog_id_t(id);
int32_t priority = LogSeverityToPriority(severity);
__android_log_buf_print(lg_id, priority, tag, "%s", message);
}
LogdLogger::LogdLogger(LogId default_log_id)
: default_log_id_(default_log_id) {}
void LogdLogger::operator()(LogId id, LogSeverity severity, const char* tag,
const char* file, unsigned int line,
const char* message) {
if (id == DEFAULT) {
id = default_log_id_;
}
SplitByLogdChunks(id, severity, tag, file, line, message, LogdLogChunk);
}
void InitLogging(const std::optional<std::string_view> default_tag,
std::optional<LogSeverity> log_level, LogFunction&& logger,
AbortFunction&& aborter) {
SetLogger(std::forward<LogFunction>(logger));
SetAborter(std::forward<AbortFunction>(aborter));
if (gInitialized) {
return;
}
gInitialized = true;
if (default_tag.has_value()) {
SetDefaultTag(default_tag.value());
}
const char* tags = getenv("ANDROID_LOG_TAGS");
if (tags == nullptr) {
return;
}
if (log_level.has_value()) {
SetMinimumLogSeverity(log_level.value());
}
}
LogFunction SetLogger(LogFunction&& logger) {
LogFunction old_logger = std::move(Logger());
Logger() = std::move(logger);
return old_logger;
}
AbortFunction SetAborter(AbortFunction&& aborter) {
AbortFunction old_aborter = std::move(Aborter());
Aborter() = std::move(aborter);
return old_aborter;
}
// This indirection greatly reduces the stack impact of having lots of
// checks/logging in a function.
class LogMessageData {
public:
LogMessageData(const char* file, unsigned int line, LogSeverity severity,
const char* tag, int error)
: file_(GetFileBasename(file)),
line_number_(line),
severity_(severity),
tag_(tag),
error_(error) {}
DISALLOW_COPY_AND_ASSIGN(LogMessageData);
const char* GetFile() const { return file_; }
unsigned int GetLineNumber() const { return line_number_; }
LogSeverity GetSeverity() const { return severity_; }
const char* GetTag() const { return tag_; }
int GetError() const { return error_; }
std::ostream& GetBuffer() { return buffer_; }
std::string ToString() const { return buffer_.str(); }
private:
std::ostringstream buffer_;
const char* const file_;
const unsigned int line_number_;
const LogSeverity severity_;
const char* const tag_;
const int error_;
};
LogMessage::LogMessage(const char* file, unsigned int line, LogId,
LogSeverity severity, const char* tag, int error)
: LogMessage(file, line, severity, tag, error) {}
LogMessage::LogMessage(const char* file, unsigned int line,
LogSeverity severity, const char* tag, int error)
: data_(new LogMessageData(file, line, severity, tag, error)) {}
LogMessage::~LogMessage() {
// Check severity again. This is duplicate work wrt/ LOG macros, but not
// LOG_STREAM.
if (!WOULD_LOG(data_->GetSeverity())) {
return;
}
// Finish constructing the message.
if (data_->GetError() != -1) {
data_->GetBuffer() << ": " << strerror(data_->GetError());
}
std::string msg(data_->ToString());
if (data_->GetSeverity() == FATAL) {
// Set the bionic abort message early to avoid liblog doing it
// with the individual lines, so that we get the whole message.
android_set_abort_message(msg.c_str());
}
LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(),
data_->GetTag(), msg.c_str());
// Abort if necessary.
if (data_->GetSeverity() == FATAL) {
Aborter()(msg.c_str());
}
}
std::ostream& LogMessage::stream() { return data_->GetBuffer(); }
void LogMessage::LogLine(const char* file, unsigned int line,
LogSeverity severity, const char* tag,
const char* message) {
if (tag == nullptr) {
std::lock_guard<std::recursive_mutex> lock(TagLock());
if (gDefaultTag == nullptr) {
gDefaultTag = new std::string(getprogname());
}
Logger()(DEFAULT, severity, gDefaultTag->c_str(), file, line, message);
} else {
Logger()(DEFAULT, severity, tag, file, line, message);
}
}
LogSeverity GetMinimumLogSeverity() { return gMinimumLogSeverity; }
bool ShouldLog(LogSeverity severity, const char*) {
return severity >= gMinimumLogSeverity;
}
LogSeverity SetMinimumLogSeverity(LogSeverity new_severity) {
LogSeverity old_severity = gMinimumLogSeverity;
gMinimumLogSeverity = new_severity;
return old_severity;
}
ScopedLogSeverity::ScopedLogSeverity(LogSeverity new_severity) {
old_ = SetMinimumLogSeverity(new_severity);
}
ScopedLogSeverity::~ScopedLogSeverity() { SetMinimumLogSeverity(old_); }
} // namespace ndksamples::base

View File

@@ -1,148 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <inttypes.h>
#include <time.h>
#include <format>
#include "base/logging.h"
#define LOGGER_ENTRY_MAX_PAYLOAD 4068 // This constant is not in the NDK.
namespace ndksamples::base {
// This splits the message up line by line, by calling log_function with a
// pointer to the start of each line and the size up to the newline character.
// It sends size = -1 for the final line.
template <typename F, typename... Args>
static void SplitByLines(const char* msg, const F& log_function,
Args&&... args) {
const char* newline = strchr(msg, '\n');
while (newline != nullptr) {
log_function(msg, newline - msg, args...);
msg = newline + 1;
newline = strchr(msg, '\n');
}
log_function(msg, -1, args...);
}
// This splits the message up into chunks that logs can process delimited by new
// lines. It calls log_function with the exact null terminated message that
// should be sent to logd. Note, despite the loops and snprintf's, if severity
// is not fatal and there are no new lines, this function simply calls
// log_function with msg without any extra overhead.
template <typename F>
static void SplitByLogdChunks(LogId log_id, LogSeverity severity,
const char* tag, const char* file,
unsigned int line, const char* msg,
const F& log_function) {
// The maximum size of a payload, after the log header that logd will accept
// is LOGGER_ENTRY_MAX_PAYLOAD, so subtract the other elements in the payload
// to find the size of the string that we can log in each pass. The protocol
// is documented in liblog/README.protocol.md. Specifically we subtract a byte
// for the priority, the length of the tag + its null terminator, and an
// additional byte for the null terminator on the payload. We subtract an
// additional 32 bytes for slack, similar to java/android/util/Log.java.
ptrdiff_t max_size = LOGGER_ENTRY_MAX_PAYLOAD - strlen(tag) - 35;
if (max_size <= 0) {
abort();
}
// If we're logging a fatal message, we'll append the file and line numbers.
bool add_file =
file != nullptr && (severity == FATAL || severity == FATAL_WITHOUT_ABORT);
std::string file_header;
if (add_file) {
file_header = std::format("{}:{}]", file, line);
}
int file_header_size = file_header.size();
__attribute__((uninitialized)) char logd_chunk[max_size + 1];
ptrdiff_t chunk_position = 0;
auto call_log_function = [&]() {
log_function(log_id, severity, tag, logd_chunk);
chunk_position = 0;
};
auto write_to_logd_chunk = [&](const char* message, int length) {
int size_written = 0;
const char* new_line = chunk_position > 0 ? "\n" : "";
if (add_file) {
size_written = snprintf(logd_chunk + chunk_position,
sizeof(logd_chunk) - chunk_position, "%s%s%.*s",
new_line, file_header.c_str(), length, message);
} else {
size_written = snprintf(logd_chunk + chunk_position,
sizeof(logd_chunk) - chunk_position, "%s%.*s",
new_line, length, message);
}
// This should never fail, if it does and we set size_written to 0, which
// will skip this line and move to the next one.
if (size_written < 0) {
size_written = 0;
}
chunk_position += size_written;
};
const char* newline = strchr(msg, '\n');
while (newline != nullptr) {
// If we have data in the buffer and this next line doesn't fit, write the
// buffer.
if (chunk_position != 0 &&
chunk_position + (newline - msg) + 1 + file_header_size > max_size) {
call_log_function();
}
// Otherwise, either the next line fits or we have any empty buffer and too
// large of a line to ever fit, in both cases, we add it to the buffer and
// continue.
write_to_logd_chunk(msg, newline - msg);
msg = newline + 1;
newline = strchr(msg, '\n');
}
// If we have left over data in the buffer and we can fit the rest of msg, add
// it to the buffer then write the buffer.
if (chunk_position != 0 &&
chunk_position + static_cast<int>(strlen(msg)) + 1 + file_header_size <=
max_size) {
write_to_logd_chunk(msg, -1);
call_log_function();
} else {
// If the buffer is not empty and we can't fit the rest of msg into it,
// write its contents.
if (chunk_position != 0) {
call_log_function();
}
// Then write the rest of the msg.
if (add_file) {
snprintf(logd_chunk, sizeof(logd_chunk), "%s%s", file_header.c_str(),
msg);
log_function(log_id, severity, tag, logd_chunk);
} else {
log_function(log_id, severity, tag, msg);
}
}
}
} // namespace ndksamples::base

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.plasma"
android:versionCode="1"
android:versionName="1.0">
<application android:label="@string/app_name" android:debuggable="true">
<activity android:name=".Plasma"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="8"/>
</manifest>

View File

@@ -1,10 +0,0 @@
# Bitmap Plasma
Bitmap Plasma is an Android sample that uses JNI to render a plasma effect in an
Android
[Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html)
from C code.
## Screenshots
![screenshot](screenshot.png)

View File

@@ -1,23 +0,0 @@
plugins {
id "ndksamples.android.application"
}
android {
namespace 'com.example.plasma'
defaultConfig {
applicationId 'com.example.plasma'
}
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
buildFeatures {
prefab true
}
}
dependencies {
implementation project(":base")
}

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0">
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<activity android:name=".Plasma"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -1,15 +0,0 @@
cmake_minimum_required(VERSION 3.22.1)
project(BitmapPlasma LANGUAGES CXX)
include(AppLibrary)
find_package(base CONFIG REQUIRED)
add_app_library(plasma SHARED jni.cpp plasma.cpp)
target_link_libraries(plasma
base::base
android
jnigraphics
log
m
)

View File

@@ -1,26 +0,0 @@
// Copyright (C) 2025 The Android Open Source Project
// SPDX-License-Identifier: Apache-2.0
#include <base/macros.h>
#include <jni.h>
#include "plasma.h"
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* _Nonnull vm, void* _Nullable) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jclass c = env->FindClass("com/example/plasma/PlasmaView");
if (c == nullptr) return JNI_ERR;
static const JNINativeMethod methods[] = {
{"renderPlasma", "(Landroid/graphics/Bitmap;J)V",
reinterpret_cast<void*>(RenderPlasma)},
};
int rc = env->RegisterNatives(c, methods, arraysize(methods));
if (rc != JNI_OK) return rc;
return JNI_VERSION_1_6;
}

View File

@@ -1,6 +0,0 @@
LIBPLASMA {
global:
JNI_OnLoad;
local:
*;
};

View File

@@ -1,365 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android/bitmap.h>
#include <android/log.h>
#include <jni.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define LOG_TAG "libplasma"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
/* Set to 1 to enable debug log traces. */
#define DEBUG 0
/* Set to 1 to optimize memory stores when generating plasma. */
#define OPTIMIZE_WRITES 1
/* Return current time in milliseconds */
static double now_ms(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000. + tv.tv_usec / 1000.;
}
/* We're going to perform computations for every pixel of the target
* bitmap. floating-point operations are very slow on ARMv5, and not
* too bad on ARMv7 with the exception of trigonometric functions.
*
* For better performance on all platforms, we're going to use fixed-point
* arithmetic and all kinds of tricks
*/
typedef int32_t Fixed;
#define FIXED_BITS 16
#define FIXED_ONE (1 << FIXED_BITS)
#define FIXED_AVERAGE(x, y) (((x) + (y)) >> 1)
#define FIXED_FROM_INT(x) ((x) << FIXED_BITS)
#define FIXED_TO_INT(x) ((x) >> FIXED_BITS)
#define FIXED_FROM_FLOAT(x) ((Fixed)((x) * FIXED_ONE))
#define FIXED_TO_FLOAT(x) ((x) / (1. * FIXED_ONE))
#define FIXED_MUL(x, y) (((int64_t)(x) * (y)) >> FIXED_BITS)
#define FIXED_DIV(x, y) (((int64_t)(x) * FIXED_ONE) / (y))
#define FIXED_DIV2(x) ((x) >> 1)
#define FIXED_AVERAGE(x, y) (((x) + (y)) >> 1)
#define FIXED_FRAC(x) ((x) & ((1 << FIXED_BITS) - 1))
#define FIXED_TRUNC(x) ((x) & ~((1 << FIXED_BITS) - 1))
#define FIXED_FROM_INT_FLOAT(x, f) (Fixed)((x) * (FIXED_ONE * (f)))
typedef int32_t Angle;
#define ANGLE_BITS 9
#if ANGLE_BITS < 8
#error ANGLE_BITS must be at least 8
#endif
#define ANGLE_2PI (1 << ANGLE_BITS)
#define ANGLE_PI (1 << (ANGLE_BITS - 1))
#define ANGLE_PI2 (1 << (ANGLE_BITS - 2))
#define ANGLE_PI4 (1 << (ANGLE_BITS - 3))
#define ANGLE_FROM_FLOAT(x) (Angle)((x) * ANGLE_PI / M_PI)
#define ANGLE_TO_FLOAT(x) ((x) * M_PI / ANGLE_PI)
#if ANGLE_BITS <= FIXED_BITS
#define ANGLE_FROM_FIXED(x) (Angle)((x) >> (FIXED_BITS - ANGLE_BITS))
#define ANGLE_TO_FIXED(x) (Fixed)((x) << (FIXED_BITS - ANGLE_BITS))
#else
#define ANGLE_FROM_FIXED(x) (Angle)((x) << (ANGLE_BITS - FIXED_BITS))
#define ANGLE_TO_FIXED(x) (Fixed)((x) >> (ANGLE_BITS - FIXED_BITS))
#endif
static Fixed angle_sin_tab[ANGLE_2PI + 1];
static void init_angles(void) {
int nn;
for (nn = 0; nn < ANGLE_2PI + 1; nn++) {
double radians = nn * M_PI / ANGLE_PI;
angle_sin_tab[nn] = FIXED_FROM_FLOAT(sin(radians));
}
}
static __inline__ Fixed angle_sin(Angle a) {
return angle_sin_tab[(uint32_t)a & (ANGLE_2PI - 1)];
}
static __inline__ Fixed fixed_sin(Fixed f) {
return angle_sin(ANGLE_FROM_FIXED(f));
}
/* Color palette used for rendering the plasma */
#define PALETTE_BITS 8
#define PALETTE_SIZE (1 << PALETTE_BITS)
#if PALETTE_BITS > FIXED_BITS
#error PALETTE_BITS must be smaller than FIXED_BITS
#endif
static uint16_t palette[PALETTE_SIZE];
static uint16_t make565(int red, int green, int blue) {
return (uint16_t)(((red << 8) & 0xf800) | ((green << 3) & 0x07e0) |
((blue >> 3) & 0x001f));
}
static void init_palette(void) {
int nn, mm = 0;
/* fun with colors */
for (nn = 0; nn < PALETTE_SIZE / 4; nn++) {
int jj = (nn - mm) * 4 * 255 / PALETTE_SIZE;
palette[nn] = make565(255, jj, 255 - jj);
}
for (mm = nn; nn < PALETTE_SIZE / 2; nn++) {
int jj = (nn - mm) * 4 * 255 / PALETTE_SIZE;
palette[nn] = make565(255 - jj, 255, jj);
}
for (mm = nn; nn < PALETTE_SIZE * 3 / 4; nn++) {
int jj = (nn - mm) * 4 * 255 / PALETTE_SIZE;
palette[nn] = make565(0, 255 - jj, 255);
}
for (mm = nn; nn < PALETTE_SIZE; nn++) {
int jj = (nn - mm) * 4 * 255 / PALETTE_SIZE;
palette[nn] = make565(jj, 0, 255);
}
}
static __inline__ uint16_t palette_from_fixed(Fixed x) {
if (x < 0) x = -x;
if (x >= FIXED_ONE) x = FIXED_ONE - 1;
int idx = FIXED_FRAC(x) >> (FIXED_BITS - PALETTE_BITS);
return palette[idx & (PALETTE_SIZE - 1)];
}
/* Angles expressed as fixed point radians */
static void init_tables(void) {
init_palette();
init_angles();
}
static void fill_plasma(AndroidBitmapInfo* info, void* pixels, double t) {
Fixed yt1 = FIXED_FROM_FLOAT(t / 1230.);
Fixed yt2 = yt1;
Fixed xt10 = FIXED_FROM_FLOAT(t / 3000.);
Fixed xt20 = xt10;
#define YT1_INCR FIXED_FROM_FLOAT(1 / 100.)
#define YT2_INCR FIXED_FROM_FLOAT(1 / 163.)
for (uint32_t yy = 0; yy < info->height; yy++) {
uint16_t* line = (uint16_t*)pixels;
Fixed base = fixed_sin(yt1) + fixed_sin(yt2);
Fixed xt1 = xt10;
Fixed xt2 = xt20;
yt1 += YT1_INCR;
yt2 += YT2_INCR;
#define XT1_INCR FIXED_FROM_FLOAT(1 / 173.)
#define XT2_INCR FIXED_FROM_FLOAT(1 / 242.)
#if OPTIMIZE_WRITES
/* optimize memory writes by generating one aligned 32-bit store
* for every pair of pixels.
*/
uint16_t* line_end = line + info->width;
if (line < line_end) {
if (((uint32_t)(uintptr_t)line & 3) != 0) {
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
xt1 += XT1_INCR;
xt2 += XT2_INCR;
line[0] = palette_from_fixed(ii >> 2);
line++;
}
while (line + 2 <= line_end) {
Fixed i1 = base + fixed_sin(xt1) + fixed_sin(xt2);
xt1 += XT1_INCR;
xt2 += XT2_INCR;
Fixed i2 = base + fixed_sin(xt1) + fixed_sin(xt2);
xt1 += XT1_INCR;
xt2 += XT2_INCR;
uint32_t pixel = ((uint32_t)palette_from_fixed(i1 >> 2) << 16) |
(uint32_t)palette_from_fixed(i2 >> 2);
((uint32_t*)line)[0] = pixel;
line += 2;
}
if (line < line_end) {
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
line[0] = palette_from_fixed(ii >> 2);
line++;
}
}
#else /* !OPTIMIZE_WRITES */
int xx;
for (xx = 0; xx < info->width; xx++) {
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
xt1 += XT1_INCR;
xt2 += XT2_INCR;
line[xx] = palette_from_fixed(ii / 4);
}
#endif /* !OPTIMIZE_WRITES */
// go to next line
pixels = (char*)pixels + info->stride;
}
}
/* simple stats management */
typedef struct {
double renderTime;
double frameTime;
} FrameStats;
#define MAX_FRAME_STATS 200
#define MAX_PERIOD_MS 1500
typedef struct {
double firstTime;
double lastTime;
double frameTime;
int firstFrame;
int numFrames;
FrameStats frames[MAX_FRAME_STATS];
} Stats;
static void stats_init(Stats* s) {
s->lastTime = now_ms();
s->firstTime = 0.;
s->firstFrame = 0;
s->numFrames = 0;
}
static void stats_startFrame(Stats* s) { s->frameTime = now_ms(); }
static void stats_endFrame(Stats* s) {
double now = now_ms();
double renderTime = now - s->frameTime;
double frameTime = now - s->lastTime;
int nn;
if (now - s->firstTime >= MAX_PERIOD_MS) {
if (s->numFrames > 0) {
double minRender, maxRender, avgRender;
double minFrame, maxFrame, avgFrame;
int count;
nn = s->firstFrame;
minRender = maxRender = avgRender = s->frames[nn].renderTime;
minFrame = maxFrame = avgFrame = s->frames[nn].frameTime;
for (count = s->numFrames; count > 0; count--) {
nn += 1;
if (nn >= MAX_FRAME_STATS) nn -= MAX_FRAME_STATS;
double render = s->frames[nn].renderTime;
if (render < minRender) minRender = render;
if (render > maxRender) maxRender = render;
double frame = s->frames[nn].frameTime;
if (frame < minFrame) minFrame = frame;
if (frame > maxFrame) maxFrame = frame;
avgRender += render;
avgFrame += frame;
}
avgRender /= s->numFrames;
avgFrame /= s->numFrames;
LOGI(
"frame/s (avg,min,max) = (%.1f,%.1f,%.1f) "
"render time ms (avg,min,max) = (%.1f,%.1f,%.1f)\n",
1000. / avgFrame, 1000. / maxFrame, 1000. / minFrame, avgRender,
minRender, maxRender);
}
s->numFrames = 0;
s->firstFrame = 0;
s->firstTime = now;
}
nn = s->firstFrame + s->numFrames;
if (nn >= MAX_FRAME_STATS) nn -= MAX_FRAME_STATS;
s->frames[nn].renderTime = renderTime;
s->frames[nn].frameTime = frameTime;
if (s->numFrames < MAX_FRAME_STATS) {
s->numFrames += 1;
} else {
s->firstFrame += 1;
if (s->firstFrame >= MAX_FRAME_STATS) s->firstFrame -= MAX_FRAME_STATS;
}
s->lastTime = now;
}
void RenderPlasma(JNIEnv* env, jclass, jobject bitmap, jlong time_ms) {
AndroidBitmapInfo info;
void* pixels;
int ret;
static Stats stats;
static int init;
if (!init) {
init_tables();
stats_init(&stats);
init = 1;
}
if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}
if (info.format != ANDROID_BITMAP_FORMAT_RGB_565) {
LOGE("Bitmap format is not RGB_565 !");
return;
}
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
stats_startFrame(&stats);
/* Now fill the values with a nice little plasma */
fill_plasma(&info, pixels, time_ms);
AndroidBitmap_unlockPixels(env, bitmap);
stats_endFrame(&stats);
}

View File

@@ -1,8 +0,0 @@
// Copyright (C) 2025 The Android Open Source Project
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <jni.h>
void RenderPlasma(JNIEnv* env, jclass, jobject bitmap, jlong time_ms);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -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-8

View File

@@ -0,0 +1,9 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := plasma
LOCAL_SRC_FILES := plasma.c
LOCAL_LDLIBS := -lm -llog -ljnigraphics
include $(BUILD_SHARED_LIBRARY)

View File

@@ -0,0 +1,2 @@
APP_ABI := all
APP_PLATFORM := android-8

399
bitmap-plasma/jni/plasma.c Normal file
View File

@@ -0,0 +1,399 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jni.h>
#include <time.h>
#include <android/log.h>
#include <android/bitmap.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define LOG_TAG "libplasma"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
/* Set to 1 to enable debug log traces. */
#define DEBUG 0
/* Set to 1 to optimize memory stores when generating plasma. */
#define OPTIMIZE_WRITES 1
/* Return current time in milliseconds */
static double now_ms(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec*1000. + tv.tv_usec/1000.;
}
/* We're going to perform computations for every pixel of the target
* bitmap. floating-point operations are very slow on ARMv5, and not
* too bad on ARMv7 with the exception of trigonometric functions.
*
* For better performance on all platforms, we're going to use fixed-point
* arithmetic and all kinds of tricks
*/
typedef int32_t Fixed;
#define FIXED_BITS 16
#define FIXED_ONE (1 << FIXED_BITS)
#define FIXED_AVERAGE(x,y) (((x) + (y)) >> 1)
#define FIXED_FROM_INT(x) ((x) << FIXED_BITS)
#define FIXED_TO_INT(x) ((x) >> FIXED_BITS)
#define FIXED_FROM_FLOAT(x) ((Fixed)((x)*FIXED_ONE))
#define FIXED_TO_FLOAT(x) ((x)/(1.*FIXED_ONE))
#define FIXED_MUL(x,y) (((int64_t)(x) * (y)) >> FIXED_BITS)
#define FIXED_DIV(x,y) (((int64_t)(x) * FIXED_ONE) / (y))
#define FIXED_DIV2(x) ((x) >> 1)
#define FIXED_AVERAGE(x,y) (((x) + (y)) >> 1)
#define FIXED_FRAC(x) ((x) & ((1 << FIXED_BITS)-1))
#define FIXED_TRUNC(x) ((x) & ~((1 << FIXED_BITS)-1))
#define FIXED_FROM_INT_FLOAT(x,f) (Fixed)((x)*(FIXED_ONE*(f)))
typedef int32_t Angle;
#define ANGLE_BITS 9
#if ANGLE_BITS < 8
# error ANGLE_BITS must be at least 8
#endif
#define ANGLE_2PI (1 << ANGLE_BITS)
#define ANGLE_PI (1 << (ANGLE_BITS-1))
#define ANGLE_PI2 (1 << (ANGLE_BITS-2))
#define ANGLE_PI4 (1 << (ANGLE_BITS-3))
#define ANGLE_FROM_FLOAT(x) (Angle)((x)*ANGLE_PI/M_PI)
#define ANGLE_TO_FLOAT(x) ((x)*M_PI/ANGLE_PI)
#if ANGLE_BITS <= FIXED_BITS
# define ANGLE_FROM_FIXED(x) (Angle)((x) >> (FIXED_BITS - ANGLE_BITS))
# define ANGLE_TO_FIXED(x) (Fixed)((x) << (FIXED_BITS - ANGLE_BITS))
#else
# define ANGLE_FROM_FIXED(x) (Angle)((x) << (ANGLE_BITS - FIXED_BITS))
# define ANGLE_TO_FIXED(x) (Fixed)((x) >> (ANGLE_BITS - FIXED_BITS))
#endif
static Fixed angle_sin_tab[ANGLE_2PI+1];
static void init_angles(void)
{
int nn;
for (nn = 0; nn < ANGLE_2PI+1; nn++) {
double radians = nn*M_PI/ANGLE_PI;
angle_sin_tab[nn] = FIXED_FROM_FLOAT(sin(radians));
}
}
static __inline__ Fixed angle_sin( Angle a )
{
return angle_sin_tab[(uint32_t)a & (ANGLE_2PI-1)];
}
static __inline__ Fixed angle_cos( Angle a )
{
return angle_sin(a + ANGLE_PI2);
}
static __inline__ Fixed fixed_sin( Fixed f )
{
return angle_sin(ANGLE_FROM_FIXED(f));
}
static __inline__ Fixed fixed_cos( Fixed f )
{
return angle_cos(ANGLE_FROM_FIXED(f));
}
/* Color palette used for rendering the plasma */
#define PALETTE_BITS 8
#define PALETTE_SIZE (1 << PALETTE_BITS)
#if PALETTE_BITS > FIXED_BITS
# error PALETTE_BITS must be smaller than FIXED_BITS
#endif
static uint16_t palette[PALETTE_SIZE];
static uint16_t make565(int red, int green, int blue)
{
return (uint16_t)( ((red << 8) & 0xf800) |
((green << 2) & 0x03e0) |
((blue >> 3) & 0x001f) );
}
static void init_palette(void)
{
int nn, mm = 0;
/* fun with colors */
for (nn = 0; nn < PALETTE_SIZE/4; nn++) {
int jj = (nn-mm)*4*255/PALETTE_SIZE;
palette[nn] = make565(255, jj, 255-jj);
}
for ( mm = nn; nn < PALETTE_SIZE/2; nn++ ) {
int jj = (nn-mm)*4*255/PALETTE_SIZE;
palette[nn] = make565(255-jj, 255, jj);
}
for ( mm = nn; nn < PALETTE_SIZE*3/4; nn++ ) {
int jj = (nn-mm)*4*255/PALETTE_SIZE;
palette[nn] = make565(0, 255-jj, 255);
}
for ( mm = nn; nn < PALETTE_SIZE; nn++ ) {
int jj = (nn-mm)*4*255/PALETTE_SIZE;
palette[nn] = make565(jj, 0, 255);
}
}
static __inline__ uint16_t palette_from_fixed( Fixed x )
{
if (x < 0) x = -x;
if (x >= FIXED_ONE) x = FIXED_ONE-1;
int idx = FIXED_FRAC(x) >> (FIXED_BITS - PALETTE_BITS);
return palette[idx & (PALETTE_SIZE-1)];
}
/* Angles expressed as fixed point radians */
static void init_tables(void)
{
init_palette();
init_angles();
}
static void fill_plasma( AndroidBitmapInfo* info, void* pixels, double t )
{
Fixed yt1 = FIXED_FROM_FLOAT(t/1230.);
Fixed yt2 = yt1;
Fixed xt10 = FIXED_FROM_FLOAT(t/3000.);
Fixed xt20 = xt10;
#define YT1_INCR FIXED_FROM_FLOAT(1/100.)
#define YT2_INCR FIXED_FROM_FLOAT(1/163.)
int yy;
for (yy = 0; yy < info->height; yy++) {
uint16_t* line = (uint16_t*)pixels;
Fixed base = fixed_sin(yt1) + fixed_sin(yt2);
Fixed xt1 = xt10;
Fixed xt2 = xt20;
yt1 += YT1_INCR;
yt2 += YT2_INCR;
#define XT1_INCR FIXED_FROM_FLOAT(1/173.)
#define XT2_INCR FIXED_FROM_FLOAT(1/242.)
#if OPTIMIZE_WRITES
/* optimize memory writes by generating one aligned 32-bit store
* for every pair of pixels.
*/
uint16_t* line_end = line + info->width;
if (line < line_end) {
if (((uint32_t)(uintptr_t)line & 3) != 0) {
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
xt1 += XT1_INCR;
xt2 += XT2_INCR;
line[0] = palette_from_fixed(ii >> 2);
line++;
}
while (line + 2 <= line_end) {
Fixed i1 = base + fixed_sin(xt1) + fixed_sin(xt2);
xt1 += XT1_INCR;
xt2 += XT2_INCR;
Fixed i2 = base + fixed_sin(xt1) + fixed_sin(xt2);
xt1 += XT1_INCR;
xt2 += XT2_INCR;
uint32_t pixel = ((uint32_t)palette_from_fixed(i1 >> 2) << 16) |
(uint32_t)palette_from_fixed(i2 >> 2);
((uint32_t*)line)[0] = pixel;
line += 2;
}
if (line < line_end) {
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
line[0] = palette_from_fixed(ii >> 2);
line++;
}
}
#else /* !OPTIMIZE_WRITES */
int xx;
for (xx = 0; xx < info->width; xx++) {
Fixed ii = base + fixed_sin(xt1) + fixed_sin(xt2);
xt1 += XT1_INCR;
xt2 += XT2_INCR;
line[xx] = palette_from_fixed(ii / 4);
}
#endif /* !OPTIMIZE_WRITES */
// go to next line
pixels = (char*)pixels + info->stride;
}
}
/* simple stats management */
typedef struct {
double renderTime;
double frameTime;
} FrameStats;
#define MAX_FRAME_STATS 200
#define MAX_PERIOD_MS 1500
typedef struct {
double firstTime;
double lastTime;
double frameTime;
int firstFrame;
int numFrames;
FrameStats frames[ MAX_FRAME_STATS ];
} Stats;
static void
stats_init( Stats* s )
{
s->lastTime = now_ms();
s->firstTime = 0.;
s->firstFrame = 0;
s->numFrames = 0;
}
static void
stats_startFrame( Stats* s )
{
s->frameTime = now_ms();
}
static void
stats_endFrame( Stats* s )
{
double now = now_ms();
double renderTime = now - s->frameTime;
double frameTime = now - s->lastTime;
int nn;
if (now - s->firstTime >= MAX_PERIOD_MS) {
if (s->numFrames > 0) {
double minRender, maxRender, avgRender;
double minFrame, maxFrame, avgFrame;
int count;
nn = s->firstFrame;
minRender = maxRender = avgRender = s->frames[nn].renderTime;
minFrame = maxFrame = avgFrame = s->frames[nn].frameTime;
for (count = s->numFrames; count > 0; count-- ) {
nn += 1;
if (nn >= MAX_FRAME_STATS)
nn -= MAX_FRAME_STATS;
double render = s->frames[nn].renderTime;
if (render < minRender) minRender = render;
if (render > maxRender) maxRender = render;
double frame = s->frames[nn].frameTime;
if (frame < minFrame) minFrame = frame;
if (frame > maxFrame) maxFrame = frame;
avgRender += render;
avgFrame += frame;
}
avgRender /= s->numFrames;
avgFrame /= s->numFrames;
LOGI("frame/s (avg,min,max) = (%.1f,%.1f,%.1f) "
"render time ms (avg,min,max) = (%.1f,%.1f,%.1f)\n",
1000./avgFrame, 1000./maxFrame, 1000./minFrame,
avgRender, minRender, maxRender);
}
s->numFrames = 0;
s->firstFrame = 0;
s->firstTime = now;
}
nn = s->firstFrame + s->numFrames;
if (nn >= MAX_FRAME_STATS)
nn -= MAX_FRAME_STATS;
s->frames[nn].renderTime = renderTime;
s->frames[nn].frameTime = frameTime;
if (s->numFrames < MAX_FRAME_STATS) {
s->numFrames += 1;
} else {
s->firstFrame += 1;
if (s->firstFrame >= MAX_FRAME_STATS)
s->firstFrame -= MAX_FRAME_STATS;
}
s->lastTime = now;
}
JNIEXPORT void JNICALL Java_com_example_plasma_PlasmaView_renderPlasma(JNIEnv * env, jobject obj, jobject bitmap, jlong time_ms)
{
AndroidBitmapInfo info;
void* pixels;
int ret;
static Stats stats;
static int init;
if (!init) {
init_tables();
stats_init(&stats);
init = 1;
}
if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}
if (info.format != ANDROID_BITMAP_FORMAT_RGB_565) {
LOGE("Bitmap format is not RGB_565 !");
return;
}
if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
stats_startFrame(&stats);
/* Now fill the values with a nice little plasma */
fill_plasma(&info, pixels, time_ms );
AndroidBitmap_unlockPixels(env, bitmap);
stats_endFrame(&stats);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 KiB

View File

@@ -15,9 +15,7 @@
*/
package com.example.plasma;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.content.Context;
import android.view.View;
@@ -28,33 +26,26 @@ import android.view.WindowManager;
public class Plasma extends Activity
{
// Called when the activity is first created.
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Display display = getWindowManager().getDefaultDisplay();
Point displaySize = new Point();
display.getSize(displaySize);
setContentView(new PlasmaView(this, displaySize.x, displaySize.y));
setContentView(new PlasmaView(this, display.getWidth(), display.getHeight()));
}
// load our native library
/* load our native library */
static {
System.loadLibrary("plasma");
}
}
// Custom view for rendering plasma.
//
// Note: suppressing lint wrarning for ViewConstructor since it is
// manually set from the activity and not used in any layout.
@SuppressLint("ViewConstructor")
class PlasmaView extends View {
private Bitmap mBitmap;
private long mStartTime;
// implementend by libplasma.so
/* implementend by libplasma.so */
private static native void renderPlasma(Bitmap bitmap, long time_ms);
public PlasmaView(Context context, int width, int height) {
@@ -64,14 +55,10 @@ class PlasmaView extends View {
}
@Override protected void onDraw(Canvas canvas) {
//canvas.drawColor(0xFFCCCCCC);
renderPlasma(mBitmap, System.currentTimeMillis() - mStartTime);
canvas.drawBitmap(mBitmap, 0, 0, null);
// force a redraw, with a different time-based pattern.
invalidate();
}
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
}
}

View File

@@ -1 +0,0 @@
/bin

View File

@@ -1,11 +0,0 @@
# Convention plugins
This directory contains [convention plugins] used by the NDK samples. These are
used to remove Gradle boiler plate from individual samples in favor of common
configuration here. Using convention plugins for single module projects is
overkill, but any non-trivial app will likely need their own eventually. See
[Now In Android's build-logic][nia-build-logic] for a more thorough example of
building convention plugins for Android projects.
[convention plugins]: https://docs.gradle.org/current/samples/sample_convention_plugins.html
[nia-build-logic]: https://github.com/android/nowinandroid/blob/main/build-logic/README.md

View File

@@ -1,40 +0,0 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("java-gradle-plugin")
`kotlin-dsl`
alias(libs.plugins.jetbrains.kotlin.jvm)
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
}
dependencies {
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
}
gradlePlugin {
plugins {
register("androidApplication") {
id = "ndksamples.android.application"
implementationClass = "com.android.ndk.samples.buildlogic.AndroidApplicationConventionPlugin"
}
register("androidLibrary") {
id = "ndksamples.android.library"
implementationClass = "com.android.ndk.samples.buildlogic.AndroidLibraryConventionPlugin"
}
register("kotlinAndroid") {
id = "ndksamples.android.kotlin"
implementationClass = "com.android.ndk.samples.buildlogic.KotlinConventionPlugin"
}
}
}

View File

@@ -1,13 +0,0 @@
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "build-logic"

View File

@@ -1,72 +0,0 @@
package com.android.ndk.samples.buildlogic
import com.android.build.api.dsl.ApplicationExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.application")
}
extensions.configure<ApplicationExtension> {
compileSdk = Versions.COMPILE_SDK
ndkVersion = Versions.NDK
externalNativeBuild {
cmake {
version = Versions.CMAKE
}
}
defaultConfig {
minSdk = Versions.MIN_SDK
targetSdk = Versions.TARGET_SDK
externalNativeBuild {
cmake {
arguments.add("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
arguments.add("-DCMAKE_MODULE_PATH=${rootDir.resolve("cmake")}")
}
}
ndk {
// riscv64 isn't a supported Android ABI yet (August 2025), but we're
// enabling it here as part of that experiment. Until it's a supported ABI,
// don't include this in your app, as Play will block uploads of APKs which
// contain riscv64 libraries.
abiFilters.addAll(
listOf(
"arm64-v8a",
"armeabi-v7a",
"riscv64",
"x86",
"x86_64",
)
)
}
}
compileOptions {
sourceCompatibility = Versions.JAVA
targetCompatibility = Versions.JAVA
}
// Studio will not automatically pass logcat through ndk-stack, so we need to avoid
// stripping debug binaries if we want the crash trace to be readable.
buildTypes {
debug {
packaging {
jniLibs {
keepDebugSymbols += "**/*.so"
}
}
}
}
}
}
}
}

View File

@@ -1,74 +0,0 @@
package com.android.ndk.samples.buildlogic
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.library")
}
extensions.configure<LibraryExtension> {
compileSdk = Versions.COMPILE_SDK
ndkVersion = Versions.NDK
externalNativeBuild {
cmake {
version = Versions.CMAKE
}
}
defaultConfig {
minSdk = Versions.MIN_SDK
lint {
targetSdk = Versions.TARGET_SDK
}
testOptions {
targetSdk = Versions.TARGET_SDK
}
externalNativeBuild {
cmake {
arguments.add("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
arguments.add("-DCMAKE_MODULE_PATH=${rootDir.resolve("cmake")}")
}
}
ndk {
// riscv64 isn't a supported Android ABI yet (August 2025), but we're
// enabling it here as part of that experiment. Until it's a supported ABI,
// don't include this in your app, as Play will block uploads of APKs which
// contain riscv64 libraries.
abiFilters.addAll(
listOf(
"arm64-v8a",
"armeabi-v7a",
"riscv64",
"x86",
"x86_64",
)
)
}
}
compileOptions {
sourceCompatibility = Versions.JAVA
targetCompatibility = Versions.JAVA
}
// Studio will not automatically pass logcat through ndk-stack, so we need to avoid
// stripping debug binaries if we want the crash trace to be readable.
buildTypes {
debug {
packaging {
jniLibs {
keepDebugSymbols += "**/*.so"
}
}
}
}
}
}
}
}

View File

@@ -1,27 +0,0 @@
package com.android.ndk.samples.buildlogic
import com.android.build.api.dsl.ApplicationExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
class KotlinConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("org.jetbrains.kotlin.android")
}
extensions.configure<ApplicationExtension> {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = Versions.JAVA.toString()
}
}
}
}
}
}

View File

@@ -1,12 +0,0 @@
package com.android.ndk.samples.buildlogic
import org.gradle.api.JavaVersion
object Versions {
const val COMPILE_SDK = 35
const val TARGET_SDK = 35
const val MIN_SDK = 21
const val NDK = "28.2.13676358" // r28c
const val CMAKE = "4.1.0"
val JAVA = JavaVersion.VERSION_1_8
}

View File

@@ -1,6 +0,0 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,23 +0,0 @@
# NdkCamera Sample
Two API samples:
- texture-view: Preview NDK camera image with
[Android TextureView](https://developer.android.com/reference/android/view/TextureView.html)
- basic: A basic NdkCamera sample to preview camera images with AReadImage and
take jpeg photos. Exposure and sensitivity are adjustable for preview, however
capturing photos is in auto mode (it could be adjustable with similar method
as used for preview).
## Other Resources
- Getting familiar with the 5 Camera2 objects
![Camera2 API Model](Camera2ProgrammingModel.png)
- [Camera2 blogs](https://medium.com/androiddevelopers/camera-enumeration-on-android-9a053b910cb5)
- [Camera2 Java documentation](https://developer.android.com/reference/android/hardware/camera2/package-summary)
## Screenshots
![screenshot](ndkCamera.png)

View File

@@ -1,31 +0,0 @@
plugins {
id "ndksamples.android.application"
}
android {
namespace 'com.sample.camera.basic'
defaultConfig {
applicationId 'com.sample.camera.basic'
minSdkVersion 24
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
}
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
buildFeatures {
prefab true
}
}
dependencies {
implementation project(":base")
implementation libs.appcompat
implementation project(":camera:camera-utils")
}

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0">
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:screenOrientation="sensorLandscape"
android:configChanges="keyboardHidden|orientation|screenSize"
android:hasCode="true">
<activity android:name="com.sample.camera.basic.CameraActivity"
android:label="@string/app_name"
android:exported="true">
<meta-data android:name="android.app.lib_name"
android:value="ndk_camera" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -1,46 +0,0 @@
#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
cmake_minimum_required(VERSION 3.22.1)
project(CameraBasic LANGUAGES C CXX)
include(AppLibrary)
include(AndroidNdkModules)
find_package(base REQUIRED CONFIG)
find_package(camera-utils REQUIRED CONFIG)
android_ndk_import_module_native_app_glue()
add_app_library(ndk_camera SHARED
${CMAKE_CURRENT_SOURCE_DIR}/android_main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/camera_engine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/camera_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/camera_listeners.cpp
${CMAKE_CURRENT_SOURCE_DIR}/image_reader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/camera_ui.cpp
)
target_link_libraries(ndk_camera
PRIVATE
base::base
camera-utils::camera-utils
android
log
m
$<LINK_LIBRARY:WHOLE_ARCHIVE,native_app_glue>
camera2ndk
mediandk
)

Some files were not shown because too many files have changed in this diff Show More