diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp index 2089fe1b0d..174433e9b3 100644 --- a/android/filament-android/src/main/cpp/View.cpp +++ b/android/filament-android/src/main/cpp/View.cpp @@ -22,8 +22,7 @@ using namespace filament; extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetName(JNIEnv* env, jclass, - jlong nativeView, jstring name_) { +Java_com_google_android_filament_View_nSetName(JNIEnv* env, jclass, jlong nativeView, jstring name_) { View* view = (View*) nativeView; const char* name = env->GetStringUTFChars(name_, 0); view->setName(name); @@ -31,16 +30,14 @@ Java_com_google_android_filament_View_nSetName(JNIEnv* env, jclass, } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetScene(JNIEnv*, jclass, - jlong nativeView, jlong nativeScene) { +Java_com_google_android_filament_View_nSetScene(JNIEnv*, jclass, jlong nativeView, jlong nativeScene) { View* view = (View*) nativeView; Scene* scene = (Scene*) nativeScene; view->setScene(scene); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetCamera(JNIEnv*, jclass, - jlong nativeView, jlong nativeCamera) { +Java_com_google_android_filament_View_nSetCamera(JNIEnv*, jclass, jlong nativeView, jlong nativeCamera) { View* view = (View*) nativeView; Camera* camera = (Camera*) nativeCamera; view->setCamera(camera); @@ -54,16 +51,14 @@ Java_com_google_android_filament_View_nSetViewport(JNIEnv*, jclass, } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetClearColor(JNIEnv*, jclass, - jlong nativeView, +Java_com_google_android_filament_View_nSetClearColor(JNIEnv*, jclass, jlong nativeView, jfloat linearR, jfloat linearG, jfloat linearB, jfloat linearA) { View* view = (View*) nativeView; view->setClearColor({linearR, linearG, linearB, linearA}); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nGetClearColor(JNIEnv* env, jclass, - jlong nativeView, jfloatArray out_) { +Java_com_google_android_filament_View_nGetClearColor(JNIEnv* env, jclass, jlong nativeView, jfloatArray out_) { View* view = (View*) nativeView; jfloat* out = env->GetFloatArrayElements(out_, NULL); auto linearColor = view->getClearColor(); @@ -82,71 +77,62 @@ Java_com_google_android_filament_View_nSetClearTargets(JNIEnv*, jclass, } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetVisibleLayers(JNIEnv*, jclass, - jlong nativeView, jint select, jint value) { +Java_com_google_android_filament_View_nSetVisibleLayers(JNIEnv*, jclass, jlong nativeView, jint select, jint value) { View* view = (View*) nativeView; view->setVisibleLayers((uint8_t) select, (uint8_t) value); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetShadowsEnabled(JNIEnv*, jclass, - jlong nativeView, jboolean enabled) { +Java_com_google_android_filament_View_nSetShadowsEnabled(JNIEnv*, jclass, jlong nativeView, jboolean enabled) { View* view = (View*) nativeView; view->setShadowsEnabled(enabled); } extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nSetRenderTarget(JNIEnv*, jclass, - jlong nativeView, jlong nativeTarget) { + jlong nativeView, jlong nativeTarget, jint flags) { View* view = (View*) nativeView; - view->setRenderTarget((RenderTarget*) nativeTarget); + view->setRenderTarget((RenderTarget*) nativeTarget, (View::TargetBufferFlags) flags); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetSampleCount(JNIEnv*, jclass, - jlong nativeView, jint count) { +Java_com_google_android_filament_View_nSetSampleCount(JNIEnv*, jclass, jlong nativeView, jint count) { View* view = (View*) nativeView; view->setSampleCount((uint8_t) count); } extern "C" JNIEXPORT jint JNICALL -Java_com_google_android_filament_View_nGetSampleCount(JNIEnv*, jclass, - jlong nativeView) { +Java_com_google_android_filament_View_nGetSampleCount(JNIEnv*, jclass, jlong nativeView) { View* view = (View*) nativeView; return view->getSampleCount(); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetAntiAliasing(JNIEnv*, jclass, - jlong nativeView, jint type) { +Java_com_google_android_filament_View_nSetAntiAliasing(JNIEnv*, jclass, jlong nativeView, jint type) { View* view = (View*) nativeView; view->setAntiAliasing(View::AntiAliasing(type)); } extern "C" JNIEXPORT jint JNICALL -Java_com_google_android_filament_View_nGetAntiAliasing(JNIEnv*, jclass, - jlong nativeView) { +Java_com_google_android_filament_View_nGetAntiAliasing(JNIEnv*, jclass, jlong nativeView) { View* view = (View*) nativeView; return (jint) view->getAntiAliasing(); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetToneMapping(JNIEnv*, jclass, - jlong nativeView, jint type) { +Java_com_google_android_filament_View_nSetToneMapping(JNIEnv*, jclass, jlong nativeView, jint type) { View* view = (View*) nativeView; view->setToneMapping(View::ToneMapping(type)); } extern "C" JNIEXPORT jint JNICALL -Java_com_google_android_filament_View_nGetToneMapping(JNIEnv*, jclass, - jlong nativeView) { +Java_com_google_android_filament_View_nGetToneMapping(JNIEnv*, jclass, jlong nativeView) { View* view = (View*) nativeView; return (jint) view->getToneMapping(); } extern "C" JNIEXPORT void JNICALL -Java_com_google_android_filament_View_nSetDithering(JNIEnv*, jclass, - jlong nativeView, jint dithering) { +Java_com_google_android_filament_View_nSetDithering(JNIEnv*, jclass, jlong nativeView, jint dithering) { View* view = (View*) nativeView; view->setDithering((View::Dithering) dithering); } @@ -220,7 +206,7 @@ Java_com_google_android_filament_View_nIsFrontFaceWindingInverted(JNIEnv*, extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_View_nSetAmbientOcclusion(JNIEnv*, jclass, jlong nativeView, jint ordinal) { View* view = (View*) nativeView; - view->setAmbientOcclusion((View::AmbientOcclusion)ordinal); + view->setAmbientOcclusion((View::AmbientOcclusion) ordinal); } extern "C" JNIEXPORT jint JNICALL diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java index a5de86fc9a..fe7d2f127d 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/View.java +++ b/android/filament-android/src/main/java/com/google/android/filament/View.java @@ -21,6 +21,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Size; +import java.util.EnumSet; + import static com.google.android.filament.Colors.LinearColor; /** @@ -247,7 +249,7 @@ public class View { /** * Options to control fog in the scene * - * @see setFogOptions + * @see View#setFogOptions */ public static class FogOptions { /** @@ -377,6 +379,69 @@ public class View { TEMPORAL } + /** + * Used to select buffers. + */ + public enum TargetBufferFlags { + /** + * Color 0 buffer selected. + */ + COLOR0(0x1), + /** + * Color 1 buffer selected. + */ + COLOR1(0x2), + /** + * Color 2 buffer selected. + */ + COLOR2(0x4), + /** + * Color 3 buffer selected. + */ + COLOR3(0x8), + /** + * Depth buffer selected. + */ + DEPTH(0x10), + /** + * Stencil buffer selected. + */ + STENCIL(0x20); + + /* + * No buffer selected + */ + public static EnumSet NONE = EnumSet.noneOf(TargetBufferFlags.class); + + /* + * All color buffers selected + */ + public static EnumSet ALL_COLOR = + EnumSet.of(COLOR0, COLOR1, COLOR2, COLOR3); + /** + * Depth and stencil buffer selected. + */ + public static EnumSet DEPTH_STENCIL = EnumSet.of(DEPTH, STENCIL); + /** + * All buffers are selected. + */ + public static EnumSet ALL = EnumSet.range(COLOR0, STENCIL); + + private int mFlags; + + TargetBufferFlags(int flags) { + mFlags = flags; + } + + static int flags(EnumSet flags) { + int result = 0; + for (TargetBufferFlags flag : flags) { + result |= flag.mFlags; + } + return result; + } + } + View(long nativeView) { mNativeObject = nativeView; } @@ -565,12 +630,33 @@ public class View { * By default, the view's associated render target is null, which corresponds to the * SwapChain associated with the engine. *

+ *

+ * This method discards the content of all the buffers in the render target. See + * {@link #setRenderTarget(RenderTarget, EnumSet)} if you need more precise control. + *

* * @param target render target associated with view, or null for the swap chain */ public void setRenderTarget(@Nullable RenderTarget target) { + setRenderTarget(target, TargetBufferFlags.ALL); + } + + /** + * Specifies an offscreen render target to render into. + * + *

+ * By default, the view's associated render target is null, which corresponds to the + * SwapChain associated with the engine. + *

+ * + * @param target render target associated with view, or null for the swap chain + * @param flags buffers that need to be discarded before rendering. + */ + public void setRenderTarget(@Nullable RenderTarget target, + @NonNull EnumSet flags) { mRenderTarget = target; - nSetRenderTarget(getNativeObject(), target != null ? target.getNativeObject() : 0); + nSetRenderTarget(getNativeObject(), target != null ? target.getNativeObject() : 0, + TargetBufferFlags.flags(flags)); } /** @@ -924,7 +1010,7 @@ public class View { private static native void nSetClearTargets(long nativeView, boolean color, boolean depth, boolean stencil); private static native void nSetVisibleLayers(long nativeView, int select, int value); private static native void nSetShadowsEnabled(long nativeView, boolean enabled); - private static native void nSetRenderTarget(long nativeView, long nativeRenderTarget); + private static native void nSetRenderTarget(long nativeView, long nativeRenderTarget, int flags); private static native void nSetSampleCount(long nativeView, int count); private static native int nGetSampleCount(long nativeView); private static native void nSetAntiAliasing(long nativeView, int type); diff --git a/android/samples/sample-multi-view/.gitignore b/android/samples/sample-multi-view/.gitignore new file mode 100644 index 0000000000..71e173b003 --- /dev/null +++ b/android/samples/sample-multi-view/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +/.idea/caches +/.idea/gradle.xml +.DS_Store +/build +/captures +/src/main/assets +.externalNativeBuild diff --git a/android/samples/sample-multi-view/build.gradle b/android/samples/sample-multi-view/build.gradle new file mode 100644 index 0000000000..5461b6cfb7 --- /dev/null +++ b/android/samples/sample-multi-view/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: FilamentToolsPlugin + +project.ext.isSample = true + +filamentTools { + materialInputDir = file("src/main/materials") + materialOutputDir = file("src/main/assets/materials") +} + +clean.doFirst { + delete "src/main/assets" +} +android { + compileSdkVersion versions.compileSdk + defaultConfig { + applicationId "com.google.android.filament.multiview" + minSdkVersion versions.minSdk + targetSdkVersion versions.targetSdk + } + + // We use the .filamat extension for materials compiled with matc + // Telling aapt to not compress them allows to load them efficiently + aaptOptions { + noCompress 'filamat', 'ktx' + } +} + +dependencies { + implementation deps.kotlin + implementation project(':filament-android') +} diff --git a/android/samples/sample-multi-view/src/main/AndroidManifest.xml b/android/samples/sample-multi-view/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..da1d1786e3 --- /dev/null +++ b/android/samples/sample-multi-view/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/android/samples/sample-multi-view/src/main/java/com/google/android/filament/multiview/MainActivity.kt b/android/samples/sample-multi-view/src/main/java/com/google/android/filament/multiview/MainActivity.kt new file mode 100644 index 0000000000..8d5dffcb59 --- /dev/null +++ b/android/samples/sample-multi-view/src/main/java/com/google/android/filament/multiview/MainActivity.kt @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2018 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.google.android.filament.multiview + +import android.animation.ValueAnimator +import android.app.Activity +import android.opengl.Matrix +import android.os.Bundle +import android.view.Choreographer +import android.view.Surface +import android.view.SurfaceView +import android.view.animation.LinearInterpolator + +import com.google.android.filament.* +import com.google.android.filament.RenderableManager.* +import com.google.android.filament.VertexBuffer.* +import com.google.android.filament.android.DisplayHelper +import com.google.android.filament.android.UiHelper + +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.channels.Channels +import java.util.* + +class MainActivity : Activity() { + // Make sure to initialize Filament first + // This loads the JNI library needed by most API calls + companion object { + init { + Filament.init() + } + } + + // The View we want to render into + private lateinit var surfaceView: SurfaceView + // UiHelper is provided by Filament to manage SurfaceView and SurfaceTexture + private lateinit var uiHelper: UiHelper + // Choreographer is used to schedule new frames + private lateinit var choreographer: Choreographer + + // Engine creates and destroys Filament resources + // Each engine must be accessed from a single thread of your choosing + // Resources cannot be shared across engines + private lateinit var engine: Engine + // A renderer instance is tied to a single surface (SurfaceView, TextureView, etc.) + private lateinit var renderer: Renderer + // A scene holds all the renderable, lights, etc. to be drawn + private lateinit var scene: Scene + // A view defines a viewport, a scene and a camera for rendering + private lateinit var view0: View + private lateinit var view1: View + private lateinit var view2: View + private lateinit var view3: View + // Should be pretty obvious :) + private lateinit var camera: Camera + + private lateinit var material: Material + private lateinit var materialInstance: MaterialInstance + private lateinit var vertexBuffer: VertexBuffer + private lateinit var indexBuffer: IndexBuffer + + // Filament entity representing a renderable object + @Entity private var renderable = 0 + @Entity private var light = 0 + + // A swap chain is Filament's representation of a surface + private var swapChain: SwapChain? = null + + // Performs the rendering and schedules new frames + private val frameScheduler = FrameCallback() + + private val animator = ValueAnimator.ofFloat(0.0f, 360.0f) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + surfaceView = SurfaceView(this) + setContentView(surfaceView) + + choreographer = Choreographer.getInstance() + + setupSurfaceView() + setupFilament() + setupViews() + setupScene() + } + + private fun setupSurfaceView() { + uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK) + uiHelper.renderCallback = SurfaceCallback() + + // NOTE: To choose a specific rendering resolution, add the following line: + // uiHelper.setDesiredSize(1280, 720) + + uiHelper.attachTo(surfaceView) + } + + private fun setupFilament() { + engine = Engine.create() + renderer = engine.createRenderer() + scene = engine.createScene() + view0 = engine.createView() + view1 = engine.createView() + view2 = engine.createView() + view3 = engine.createView() + camera = engine.createCamera() + } + + private fun setupViews() { + // To use multi-view properly with mobile GPUs, we need to make sure to tell + // Filament to not discard buffers after the first view we render + view1.setRenderTarget(null, View.TargetBufferFlags.NONE) + view2.setRenderTarget(null, View.TargetBufferFlags.NONE) + view3.setRenderTarget(null, View.TargetBufferFlags.NONE) + + // Clears are not restricted by the scissor so we want to disable clear targets + // except for the first view we render + view1.setClearTargets(false, false, false) + view2.setClearTargets(false, false, false) + view3.setClearTargets(false, false, false) + + view0.setClearColor(0.035f, 0.035f, 0.035f, 1.0f) + + view0.camera = camera + view1.camera = camera + view2.camera = camera + view3.camera = camera + + view0.scene = scene + view1.scene = scene + view2.scene = scene + view3.scene = scene + } + + private fun setupScene() { + loadMaterial() + setupMaterial() + createMesh() + + // To create a renderable we first create a generic entity + renderable = EntityManager.get().create() + + // We then create a renderable component on that entity + // A renderable is made of several primitives; in this case we declare only 1 + // If we wanted each face of the cube to have a different material, we could + // declare 6 primitives (1 per face) and give each of them a different material + // instance, setup with different parameters + RenderableManager.Builder(1) + // Overall bounding box of the renderable + .boundingBox(Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f)) + // Sets the mesh data of the first primitive, 6 faces of 6 indices each + .geometry(0, PrimitiveType.TRIANGLES, vertexBuffer, indexBuffer, 0, 6 * 6) + // Sets the material of the first primitive + .material(0, materialInstance) + .build(engine, renderable) + + // Add the entity to the scene to render it + scene.addEntity(renderable) + + // We now need a light, let's create a directional light + light = EntityManager.get().create() + + // Create a color from a temperature (5,500K) + val (r, g, b) = Colors.cct(5_500.0f) + LightManager.Builder(LightManager.Type.DIRECTIONAL) + .color(r, g, b) + // Intensity of the sun in lux on a clear day + .intensity(110_000.0f) + // The direction is normalized on our behalf + .direction(0.0f, -0.5f, -1.0f) + .castShadows(true) + .build(engine, light) + + // Add the entity to the scene to light it + scene.addEntity(light) + + // Set the exposure on the camera, this exposure follows the sunny f/16 rule + // Since we've defined a light that has the same intensity as the sun, it + // guarantees a proper exposure + camera.setExposure(16.0f, 1.0f / 125.0f, 100.0f) + + // Move the camera back to see the object + camera.lookAt(0.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0) + + startAnimation() + } + + private fun loadMaterial() { + readUncompressedAsset("materials/lit.filamat").let { + material = Material.Builder().payload(it, it.remaining()).build(engine) + } + } + + private fun setupMaterial() { + // Create an instance of the material to set different parameters on it + materialInstance = material.createInstance() + // Specify that our color is in sRGB so the conversion to linear + // is done automatically for us. If you already have a linear color + // you can pass it directly, or use Colors.RgbType.LINEAR + materialInstance.setParameter("baseColor", Colors.RgbType.SRGB, 1.0f, 0.85f, 0.57f) + // The default value is always 0, but it doesn't hurt to be clear about our intentions + // Here we are defining a dielectric material + materialInstance.setParameter("metallic", 0.0f) + // We increase the roughness to spread the specular highlights + materialInstance.setParameter("roughness", 0.3f) + } + + private fun createMesh() { + val floatSize = 4 + val shortSize = 2 + // A vertex is a position + a tangent frame: + // 3 floats for XYZ position, 4 floats for normal+tangents (quaternion) + val vertexSize = 3 * floatSize + 4 * floatSize + + // Define a vertex and a function to put a vertex in a ByteBuffer + @Suppress("ArrayInDataClass") + data class Vertex(val x: Float, val y: Float, val z: Float, val tangents: FloatArray) + fun ByteBuffer.put(v: Vertex): ByteBuffer { + putFloat(v.x) + putFloat(v.y) + putFloat(v.z) + v.tangents.forEach { putFloat(it) } + return this + } + + // 6 faces, 4 vertices per face + val vertexCount = 6 * 4 + + // Create tangent frames, one per face + val tfPX = FloatArray(4) + val tfNX = FloatArray(4) + val tfPY = FloatArray(4) + val tfNY = FloatArray(4) + val tfPZ = FloatArray(4) + val tfNZ = FloatArray(4) + + MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, tfPX) + MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, tfNX) + MathUtils.packTangentFrame(-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, tfPY) + MathUtils.packTangentFrame(-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, tfNY) + MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, tfPZ) + MathUtils.packTangentFrame( 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, tfNZ) + + val vertexData = ByteBuffer.allocate(vertexCount * vertexSize) + // It is important to respect the native byte order + .order(ByteOrder.nativeOrder()) + // Face -Z + .put(Vertex(-1.0f, -1.0f, -1.0f, tfNZ)) + .put(Vertex(-1.0f, 1.0f, -1.0f, tfNZ)) + .put(Vertex( 1.0f, 1.0f, -1.0f, tfNZ)) + .put(Vertex( 1.0f, -1.0f, -1.0f, tfNZ)) + // Face +X + .put(Vertex( 1.0f, -1.0f, -1.0f, tfPX)) + .put(Vertex( 1.0f, 1.0f, -1.0f, tfPX)) + .put(Vertex( 1.0f, 1.0f, 1.0f, tfPX)) + .put(Vertex( 1.0f, -1.0f, 1.0f, tfPX)) + // Face +Z + .put(Vertex(-1.0f, -1.0f, 1.0f, tfPZ)) + .put(Vertex( 1.0f, -1.0f, 1.0f, tfPZ)) + .put(Vertex( 1.0f, 1.0f, 1.0f, tfPZ)) + .put(Vertex(-1.0f, 1.0f, 1.0f, tfPZ)) + // Face -X + .put(Vertex(-1.0f, -1.0f, 1.0f, tfNX)) + .put(Vertex(-1.0f, 1.0f, 1.0f, tfNX)) + .put(Vertex(-1.0f, 1.0f, -1.0f, tfNX)) + .put(Vertex(-1.0f, -1.0f, -1.0f, tfNX)) + // Face -Y + .put(Vertex(-1.0f, -1.0f, 1.0f, tfNY)) + .put(Vertex(-1.0f, -1.0f, -1.0f, tfNY)) + .put(Vertex( 1.0f, -1.0f, -1.0f, tfNY)) + .put(Vertex( 1.0f, -1.0f, 1.0f, tfNY)) + // Face +Y + .put(Vertex(-1.0f, 1.0f, -1.0f, tfPY)) + .put(Vertex(-1.0f, 1.0f, 1.0f, tfPY)) + .put(Vertex( 1.0f, 1.0f, 1.0f, tfPY)) + .put(Vertex( 1.0f, 1.0f, -1.0f, tfPY)) + // Make sure the cursor is pointing in the right place in the byte buffer + .flip() + + // Declare the layout of our mesh + vertexBuffer = VertexBuffer.Builder() + .bufferCount(1) + .vertexCount(vertexCount) + // Because we interleave position and color data we must specify offset and stride + // We could use de-interleaved data by declaring two buffers and giving each + // attribute a different buffer index + .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize) + .attribute(VertexAttribute.TANGENTS, 0, AttributeType.FLOAT4, 3 * floatSize, vertexSize) + .build(engine) + + // Feed the vertex data to the mesh + // We only set 1 buffer because the data is interleaved + vertexBuffer.setBufferAt(engine, 0, vertexData) + + // Create the indices + val indexData = ByteBuffer.allocate(6 * 2 * 3 * shortSize) + .order(ByteOrder.nativeOrder()) + repeat(6) { + val i = (it * 4).toShort() + indexData + .putShort(i).putShort((i + 1).toShort()).putShort((i + 2).toShort()) + .putShort(i).putShort((i + 2).toShort()).putShort((i + 3).toShort()) + } + indexData.flip() + + // 6 faces, 2 triangles per face, + indexBuffer = IndexBuffer.Builder() + .indexCount(vertexCount * 2) + .bufferType(IndexBuffer.Builder.IndexType.USHORT) + .build(engine) + indexBuffer.setBuffer(engine, indexData) + } + + private fun startAnimation() { + // Animate the triangle + animator.interpolator = LinearInterpolator() + animator.duration = 6000 + animator.repeatMode = ValueAnimator.RESTART + animator.repeatCount = ValueAnimator.INFINITE + animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { + val transformMatrix = FloatArray(16) + override fun onAnimationUpdate(a: ValueAnimator) { + Matrix.setRotateM(transformMatrix, 0, a.animatedValue as Float, 0.0f, 1.0f, 0.0f) + val tcm = engine.transformManager + tcm.setTransform(tcm.getInstance(renderable), transformMatrix) + } + }) + animator.start() + } + + override fun onResume() { + super.onResume() + choreographer.postFrameCallback(frameScheduler) + animator.start() + } + + override fun onPause() { + super.onPause() + choreographer.removeFrameCallback(frameScheduler) + animator.cancel() + } + + override fun onDestroy() { + super.onDestroy() + + // Stop the animation and any pending frame + choreographer.removeFrameCallback(frameScheduler) + animator.cancel(); + + // Always detach the surface before destroying the engine + uiHelper.detach() + + // Cleanup all resources + engine.destroyEntity(light) + engine.destroyEntity(renderable) + engine.destroyRenderer(renderer) + engine.destroyVertexBuffer(vertexBuffer) + engine.destroyIndexBuffer(indexBuffer) + engine.destroyMaterialInstance(materialInstance) + engine.destroyMaterial(material) + engine.destroyView(view0) + engine.destroyView(view1) + engine.destroyView(view2) + engine.destroyView(view3) + engine.destroyScene(scene) + engine.destroyCamera(camera) + + // Engine.destroyEntity() destroys Filament related resources only + // (components), not the entity itself + val entityManager = EntityManager.get() + entityManager.destroy(light) + entityManager.destroy(renderable) + + // Destroying the engine will free up any resource you may have forgotten + // to destroy, but it's recommended to do the cleanup properly + engine.destroy() + } + + inner class FrameCallback : Choreographer.FrameCallback { + override fun doFrame(frameTimeNanos: Long) { + // Schedule the next frame + choreographer.postFrameCallback(this) + + // This check guarantees that we have a swap chain + if (uiHelper.isReadyToRender) { + // If beginFrame() returns false you should skip the frame + // This means you are sending frames too quickly to the GPU + if (renderer.beginFrame(swapChain!!, frameTimeNanos)) { + renderer.render(view0) + renderer.render(view1) + renderer.render(view2) + renderer.render(view3) + renderer.endFrame() + } + } + } + } + + inner class SurfaceCallback : UiHelper.RendererCallback { + override fun onNativeWindowChanged(surface: Surface) { + swapChain?.let { engine.destroySwapChain(it) } + swapChain = engine.createSwapChain(surface) + renderer.setDisplayInfo(DisplayHelper.getDisplayInfo(surfaceView.display, Renderer.DisplayInfo())) + } + + override fun onDetachedFromSurface() { + swapChain?.let { + engine.destroySwapChain(it) + // Required to ensure we don't return before Filament is done executing the + // destroySwapChain command, otherwise Android might destroy the Surface + // too early + engine.flushAndWait() + swapChain = null + } + } + + override fun onResized(width: Int, height: Int) { + val aspect = width.toDouble() / height.toDouble() + camera.setProjection(45.0, aspect, 0.1, 20.0, Camera.Fov.VERTICAL) + + view0.viewport = Viewport(0, 0, width / 2, height / 2) + view1.viewport = Viewport(width / 2, 0, width / 2, height / 2) + view2.viewport = Viewport(0, height / 2, width / 2, height / 2) + view3.viewport = Viewport(width / 2, height / 2, width / 2, height / 2) + } + } + + private fun readUncompressedAsset(assetName: String): ByteBuffer { + assets.openFd(assetName).use { fd -> + val input = fd.createInputStream() + val dst = ByteBuffer.allocate(fd.length.toInt()) + + val src = Channels.newChannel(input) + src.read(dst) + src.close() + + return dst.apply { rewind() } + } + } +} diff --git a/android/samples/sample-multi-view/src/main/materials/lit.mat b/android/samples/sample-multi-view/src/main/materials/lit.mat new file mode 100644 index 0000000000..3510980af7 --- /dev/null +++ b/android/samples/sample-multi-view/src/main/materials/lit.mat @@ -0,0 +1,53 @@ +// Simple lit material that defines 3 parameters: +// - baseColor +// - roughness +// - metallic +// +// These parameters can be used by the application to change the appearance of the material. +// +// This source material must be compiled to a binary material using the matc tool. +// The command used to compile this material is: +// matc -p mobile -a opengl -o app/src/main/assets/lit.filamat app/src/materials/lit.mat +// +// See build.gradle for an example of how to compile materials automatically +// Please refer to the documentation for more information about matc and the materials system. + +material { + name : lit, + + // Dynamic lighting is enabled on this material + shadingModel : lit, + + // We don't need to declare a "requires" array, lit materials + // always requires the "tangents" vertex attribute (the normal + // is required for lighting, tangent/bitangent for normal mapping + // and anisotropy) + + // List of parameters exposed by this material + parameters : [ + // The color must be passed in linear space, not sRGB + { + type : float3, + name : baseColor + }, + { + type : float, + name : roughness + }, + { + type : float, + name : metallic + } + ], +} + +fragment { + void material(inout MaterialInputs material) { + prepareMaterial(material); + + // Nothing fancy here, we simply copy the parameters + material.baseColor.rgb = materialParams.baseColor; + material.roughness = materialParams.roughness; + material.metallic = materialParams.metallic; + } +} diff --git a/android/samples/sample-multi-view/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/samples/sample-multi-view/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..b1517edf49 --- /dev/null +++ b/android/samples/sample-multi-view/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/android/samples/sample-multi-view/src/main/res/drawable/ic_launcher_background.xml b/android/samples/sample-multi-view/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..88e31872fe --- /dev/null +++ b/android/samples/sample-multi-view/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/samples/sample-multi-view/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..6d5e5d094c --- /dev/null +++ b/android/samples/sample-multi-view/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/samples/sample-multi-view/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..6d5e5d094c --- /dev/null +++ b/android/samples/sample-multi-view/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-hdpi/ic_launcher.png b/android/samples/sample-multi-view/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..a2f5908281 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/samples/sample-multi-view/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000..1b52399808 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-mdpi/ic_launcher.png b/android/samples/sample-multi-view/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ff10afd6e1 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/samples/sample-multi-view/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000..115a4c768a Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/samples/sample-multi-view/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..dcd3cd8083 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/samples/sample-multi-view/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..459ca609d3 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/samples/sample-multi-view/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8ca12fe024 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/samples/sample-multi-view/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..8e19b410a1 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/samples/sample-multi-view/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b824ebdd48 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/samples/sample-multi-view/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/samples/sample-multi-view/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..4c19a13c23 Binary files /dev/null and b/android/samples/sample-multi-view/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-multi-view/src/main/res/values/colors.xml b/android/samples/sample-multi-view/src/main/res/values/colors.xml new file mode 100644 index 0000000000..5a077b3a78 --- /dev/null +++ b/android/samples/sample-multi-view/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/android/samples/sample-multi-view/src/main/res/values/strings.xml b/android/samples/sample-multi-view/src/main/res/values/strings.xml new file mode 100644 index 0000000000..1307379a0a --- /dev/null +++ b/android/samples/sample-multi-view/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Multi View + diff --git a/android/samples/sample-multi-view/src/main/res/values/styles.xml b/android/samples/sample-multi-view/src/main/res/values/styles.xml new file mode 100644 index 0000000000..a7a06158ff --- /dev/null +++ b/android/samples/sample-multi-view/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/android/settings.gradle b/android/settings.gradle index f54b169606..ec70d9de47 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -11,6 +11,7 @@ include ':samples:sample-hello-triangle' include ':samples:sample-image-based-lighting' include ':samples:sample-lit-cube' include ':samples:sample-material-builder' +include ':samples:sample-multi-view' include ':samples:sample-stream-test' include ':samples:sample-texture-view' include ':samples:sample-textured-object'