diff --git a/README.md b/README.md
index c8d4c3b18ea..a5d46b834dd 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
- implementation 'com.google.android.filament:filament-android:1.9.4'
+ implementation 'com.google.android.filament:filament-android:1.9.5'
}
```
@@ -63,7 +63,7 @@ A much smaller alternative to `filamat-android` that can only generate OpenGL sh
iOS projects can use CocoaPods to install the latest release:
```
-pod 'Filament', '~> 1.9.4'
+pod 'Filament', '~> 1.9.5'
```
### Snapshots
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index b28903423cb..a3d0efb2b2f 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -3,7 +3,20 @@
This file contains one line summaries of commits that are worthy of mentioning in release notes.
A new header is inserted each time a *tag* is created.
-## Next release (v1.9.5)
+## Next release (main branch)
+
+## v1.9.5
+
+- Added a new Live Wallpaper Android sample
+- `UiHelper` now supports managing a `SurfaceHolder`
+- Fix: an internal texture resource was never destroyed
+- Fix: hang on 2-CPU machines
+- Fix: Vulkan crash when using shadow cascades
+- Linux fixes for headless SwiftShader
+- Fix null pointer dereference in `FIndirectLight`
+- Fix Windows build by avoiding nested initializers
+- Vulkan: support readPixels and headless swap chains
+- VSM improvements
## v1.9.4
diff --git a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java
index 0f93df775cf..15ef520210c 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/RenderableManager.java
@@ -268,6 +268,14 @@ public Builder culling(boolean enabled) {
/**
* Controls if this renderable casts shadows, false by default.
+ *
+ * If the View's shadow type is set to {@link View.ShadowType#VSM}, castShadows should only
+ * be disabled if either is true:
+ *
+ * - {@link RenderableManager#setReceiveShadows} is also disabled
+ * - the object is guaranteed to not cast shadows on itself or other objects (for
+ * example, a ground plane)
+ *
*/
@NonNull
public Builder castShadows(boolean enabled) {
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 3c3a83ef4e1..a3cf9719333 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
@@ -1168,6 +1168,14 @@ public void setDynamicLightingOptions(float zLightNear, float zLightFar) {
*
* The ShadowType affects all the shadows seen within the View.
*
+ *
+ * {@link ShadowType#VSM} imposes a restriction on marking renderables as only shadow receivers
+ * (but not casters). To ensure correct shadowing with VSM, all shadow participant renderables
+ * should be marked as both receivers and casters. Objects that are guaranteed to not cast
+ * shadows on themselves or other objects (such as flat ground planes) can be set to not cast
+ * shadows, which might improve shadow quality.
+ *
+ *
* Warning: This API is still experimental and subject to change.
*/
public void setShadowType(ShadowType type) {
diff --git a/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java b/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java
index a170b12adfa..ae1bdcd7c0b 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/android/UiHelper.java
@@ -30,8 +30,8 @@
import com.google.android.filament.SwapChain;
/**
- * UiHelper is a simple class that can manage either a SurfaceView or a TextureView so it can
- * be used to render into with Filament.
+ * UiHelper is a simple class that can manage either a SurfaceView, TextureView, or a SurfaceHolder
+ * so it can be used to render into with Filament.
*
* Here is a simple example with a SurfaceView. The code would be exactly the same with a
* TextureView:
@@ -191,6 +191,23 @@ public void detach() {
}
}
+ private static class SurfaceHolderHandler implements RenderSurface {
+ private SurfaceHolder mSurfaceHolder;
+
+ SurfaceHolderHandler(SurfaceHolder surface) {
+ mSurfaceHolder = surface;
+ }
+
+ @Override
+ public void resize(int width, int height) {
+ mSurfaceHolder.setFixedSize(width, height);
+ }
+
+ @Override
+ public void detach() {
+ }
+ }
+
private class TextureViewHandler implements RenderSurface {
private TextureView mTextureView;
private Surface mSurface;
@@ -258,8 +275,8 @@ public RendererCallback getRenderCallback() {
}
/**
- * Free resources associated to the native window specified in {@link #attachTo(SurfaceView)}
- * or {@link #attachTo(TextureView)}.
+ * Free resources associated to the native window specified in {@link #attachTo(SurfaceView)},
+ * {@link #attachTo(TextureView)}, or {@link #attachTo(SurfaceHolder)}.
*/
public void detach() {
destroySwapChain();
@@ -315,8 +332,8 @@ public boolean isOpaque() {
* Controls whether the render target (SurfaceView or TextureView) is opaque or not.
* The render target is considered opaque by default.
*
- * Must be called before calling {@link #attachTo(SurfaceView)}
- * or {@link #attachTo(TextureView)}.
+ * Must be called before calling {@link #attachTo(SurfaceView)}, {@link #attachTo(TextureView)},
+ * or {@link #attachTo(SurfaceHolder)}.
*
* @param opaque Indicates whether the render target should be opaque. True by default.
*/
@@ -341,6 +358,8 @@ public boolean isMediaOverlay() {
* Must be called before calling {@link #attachTo(SurfaceView)}
* or {@link #attachTo(TextureView)}.
*
+ * Has no effect when using {@link #attachTo(SurfaceHolder)}.
+ *
* @param overlay Indicates whether the render target should be rendered below the activity's
* surface when transparent.
*/
@@ -403,7 +422,9 @@ public void surfaceDestroyed(SurfaceHolder holder) {
SurfaceHolder holder = view.getHolder();
holder.addCallback(callback);
- holder.setFixedSize(mDesiredWidth, mDesiredHeight);
+ if (mDesiredWidth > 0 && mDesiredHeight > 0) {
+ holder.setFixedSize(mDesiredWidth, mDesiredHeight);
+ }
// in case the SurfaceView's surface already existed
final Surface surface = holder.getSurface();
@@ -483,6 +504,55 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
}
}
+ /**
+ * Associate UiHelper with a SurfaceHolder.
+ *
+ * As soon as a Surface is created, we'll create the
+ * EGL resources needed, and call user callbacks if needed.
+ */
+ public void attachTo(@NonNull SurfaceHolder holder) {
+ if (attach(holder)) {
+ int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
+ holder.setFormat(format);
+
+ mRenderSurface = new SurfaceHolderHandler(holder);
+
+ final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder surfaceHolder) {
+ if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
+ createSwapChain(holder.getSurface());
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ // Note: this is always called at least once after surfaceCreated()
+ if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
+ mRenderCallback.onResized(width, height);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+ if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
+ destroySwapChain();
+ }
+ };
+
+ holder.addCallback(callback);
+ if (mDesiredWidth > 0 && mDesiredHeight > 0) {
+ holder.setFixedSize(mDesiredWidth, mDesiredHeight);
+ }
+
+ // in case the SurfaceHolder's surface already existed
+ final Surface surface = holder.getSurface();
+ if (surface != null && surface.isValid()) {
+ callback.surfaceCreated(holder);
+ callback.surfaceChanged(holder, format,
+ holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
+ }
+ }
+ }
+
private boolean attach(@NonNull Object nativeWindow) {
if (mNativeWindow != null) {
// we are already attached to a native window
diff --git a/android/gradle.properties b/android/gradle.properties
index 0bb0355b4d1..0ad0816b065 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
-VERSION_NAME=1.9.4
+VERSION_NAME=1.9.5
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
diff --git a/android/samples/README.md b/android/samples/README.md
index 6b0856b44c2..12a6d43e167 100644
--- a/android/samples/README.md
+++ b/android/samples/README.md
@@ -15,6 +15,12 @@ Demonstrates how to create a light and a mesh with the attributes required for l
![Lit Cube](../../docs/images/samples/sample_lit_cube.jpg)
+### `live-wallpaper`
+
+Demonstrates how to use Filament as renderer for an Android Live Wallpaper.
+
+![Live Wallpaper](../../docs/images/samples/example_live_wallpaper.jpg)
+
### `image-based-lighting`
Demonstrates how to create image-based lights and load complex meshes:
diff --git a/android/samples/sample-live-wallpaper/.gitignore b/android/samples/sample-live-wallpaper/.gitignore
new file mode 100644
index 00000000000..71e173b003e
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/.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-live-wallpaper/build.gradle b/android/samples/sample-live-wallpaper/build.gradle
new file mode 100644
index 00000000000..ecb1988dc7b
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+project.ext.isSample = true
+
+android {
+ compileSdkVersion versions.compileSdk
+ defaultConfig {
+ applicationId "com.google.android.filament.livewallpaper"
+ minSdkVersion versions.minSdk
+ targetSdkVersion versions.targetSdk
+ }
+}
+
+dependencies {
+ implementation deps.kotlin
+ implementation project(':filament-android')
+}
diff --git a/android/samples/sample-live-wallpaper/src/main/AndroidManifest.xml b/android/samples/sample-live-wallpaper/src/main/AndroidManifest.xml
new file mode 100644
index 00000000000..20848404574
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/samples/sample-live-wallpaper/src/main/java/com/google/android/filament/livewallpaper/Wallpaper.kt b/android/samples/sample-live-wallpaper/src/main/java/com/google/android/filament/livewallpaper/Wallpaper.kt
new file mode 100644
index 00000000000..8e21649fa72
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/src/main/java/com/google/android/filament/livewallpaper/Wallpaper.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.
+ */
+
+package com.google.android.filament.livewallpaper
+
+import android.animation.ValueAnimator
+import android.app.Service
+import android.graphics.Color
+import android.graphics.PixelFormat
+import android.service.wallpaper.WallpaperService
+import android.view.Choreographer
+import android.view.Surface
+import android.view.SurfaceHolder
+import android.view.WindowManager
+import android.view.animation.LinearInterpolator
+import com.google.android.filament.*
+import com.google.android.filament.android.DisplayHelper
+import com.google.android.filament.android.UiHelper
+
+
+class FilamentLiveWallpaper : WallpaperService() {
+ // Make sure to initialize Filament first
+ // This loads the JNI library needed by most API calls
+ companion object {
+ init {
+ Filament.init()
+ }
+ }
+
+ override fun onCreateEngine(): Engine {
+ return FilamentWallpaperEngine()
+ }
+
+ private inner class FilamentWallpaperEngine : Engine() {
+
+ // UiHelper is provided by Filament to manage SurfaceHolder
+ private lateinit var uiHelper: UiHelper
+ // DisplayHelper is provided by Filament to manage the display
+ private lateinit var displayHelper: DisplayHelper
+ // 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: com.google.android.filament.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 view: View
+ // Should be pretty obvious :)
+ private lateinit var camera: Camera
+
+ // 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()
+
+ // We'll use this ValueAnimator to smoothly cycle the background between hues.
+ private val animator = ValueAnimator.ofFloat(0.0f, 360.0f)
+
+ override fun onCreate(surfaceHolder: SurfaceHolder) {
+ super.onCreate(surfaceHolder)
+ surfaceHolder.setSizeFromLayout()
+ surfaceHolder.setFormat(PixelFormat.RGBA_8888)
+
+ choreographer = Choreographer.getInstance()
+
+ displayHelper = DisplayHelper(this@FilamentLiveWallpaper)
+
+ setupUiHelper()
+ setupFilament()
+ setupView()
+ setupScene()
+ }
+
+ private fun setupUiHelper() {
+ uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK)
+ uiHelper.renderCallback = SurfaceCallback()
+ uiHelper.attachTo(surfaceHolder)
+ }
+
+ private fun setupFilament() {
+ engine = com.google.android.filament.Engine.create()
+ renderer = engine.createRenderer()
+ scene = engine.createScene()
+ view = engine.createView()
+ camera = engine.createCamera()
+ }
+
+ private fun setupView() {
+ scene.skybox = Skybox.Builder().build(engine)
+
+ // NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference
+ // view.isPostProcessingEnabled = false
+
+ // Tell the view which camera we want to use
+ view.camera = camera
+
+ // Tell the view which scene we want to render
+ view.scene = scene
+ }
+
+ private fun setupScene() {
+ // Set the exposure on the camera, this exposure follows the sunny f/16 rule
+ camera.setExposure(16.0f, 1.0f / 125.0f, 100.0f)
+
+ startAnimation()
+ }
+
+ private fun startAnimation() {
+ // Animate the color of the Skybox.
+ animator.interpolator = LinearInterpolator()
+ animator.duration = 10000
+ animator.repeatMode = ValueAnimator.RESTART
+ animator.repeatCount = ValueAnimator.INFINITE
+ animator.addUpdateListener { a ->
+ val hue = a.animatedValue as Float
+ val color = Color.HSVToColor(floatArrayOf(hue, 1.0f, 1.0f))
+ scene.skybox?.setColor(floatArrayOf(
+ Color.red(color) / 255.0f,
+ Color.green(color) / 255.0f,
+ Color.blue(color) / 255.0f,
+ 1.0f))
+ }
+ animator.start()
+ }
+
+ override fun onVisibilityChanged(visible: Boolean) {
+ super.onVisibilityChanged(visible)
+ if (visible) {
+ choreographer.postFrameCallback(frameScheduler)
+ animator.start()
+ } else {
+ 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.destroyRenderer(renderer)
+ engine.destroyView(view)
+ engine.destroyScene(scene)
+ engine.destroyCamera(camera)
+
+ // 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(view)
+ renderer.endFrame()
+ }
+ }
+ }
+ }
+
+ inner class SurfaceCallback : UiHelper.RendererCallback {
+ override fun onNativeWindowChanged(surface: Surface) {
+ swapChain?.let { engine.destroySwapChain(it) }
+ swapChain = engine.createSwapChain(surface)
+ val display =
+ (application.getSystemService(Service.WINDOW_SERVICE) as WindowManager)
+ .defaultDisplay
+ displayHelper.attach(renderer, display)
+ }
+
+ override fun onDetachedFromSurface() {
+ displayHelper.detach()
+ 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)
+
+ view.viewport = Viewport(0, 0, width, height)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/samples/sample-live-wallpaper/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/samples/sample-live-wallpaper/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000000..b1517edf496
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/samples/sample-live-wallpaper/src/main/res/drawable/ic_launcher_background.xml b/android/samples/sample-live-wallpaper/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000000..88e31872fef
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/samples/sample-live-wallpaper/src/main/res/drawable/wallpaper_thumb.png b/android/samples/sample-live-wallpaper/src/main/res/drawable/wallpaper_thumb.png
new file mode 100644
index 00000000000..f2772f51f14
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/drawable/wallpaper_thumb.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/samples/sample-live-wallpaper/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000000..6d5e5d094cb
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/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-live-wallpaper/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/samples/sample-live-wallpaper/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000000..6d5e5d094cb
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/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-live-wallpaper/src/main/res/mipmap-hdpi/ic_launcher.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000000..a2f5908281d
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..1b523998081
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-mdpi/ic_launcher.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000000..ff10afd6e18
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..115a4c768a2
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000000..dcd3cd80833
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..459ca609d3a
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000000..8ca12fe024b
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..8e19b410a1b
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000000..b824ebdd48d
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..4c19a13c239
Binary files /dev/null and b/android/samples/sample-live-wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android/samples/sample-live-wallpaper/src/main/res/values/colors.xml b/android/samples/sample-live-wallpaper/src/main/res/values/colors.xml
new file mode 100644
index 00000000000..5a077b3a78f
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/android/samples/sample-live-wallpaper/src/main/res/values/strings.xml b/android/samples/sample-live-wallpaper/src/main/res/values/strings.xml
new file mode 100644
index 00000000000..8aa3a79ecd8
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+ Filament Sample Live Wallpaper
+ Google
+
diff --git a/android/samples/sample-live-wallpaper/src/main/res/values/styles.xml b/android/samples/sample-live-wallpaper/src/main/res/values/styles.xml
new file mode 100644
index 00000000000..a7a06158ff1
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/android/samples/sample-live-wallpaper/src/main/res/xml/wallpaper.xml b/android/samples/sample-live-wallpaper/src/main/res/xml/wallpaper.xml
new file mode 100644
index 00000000000..680d4bc1e37
--- /dev/null
+++ b/android/samples/sample-live-wallpaper/src/main/res/xml/wallpaper.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/android/settings.gradle b/android/settings.gradle
index 18c19e1283e..8741c2a3b43 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -10,6 +10,7 @@ include ':samples:sample-hello-camera'
include ':samples:sample-hello-triangle'
include ':samples:sample-image-based-lighting'
include ':samples:sample-lit-cube'
+include ':samples:sample-live-wallpaper'
include ':samples:sample-material-builder'
include ':samples:sample-multi-view'
include ':samples:sample-page-curl'
diff --git a/docs/images/samples/example_live_wallpaper.jpg b/docs/images/samples/example_live_wallpaper.jpg
new file mode 100644
index 00000000000..74ad33f1aeb
Binary files /dev/null and b/docs/images/samples/example_live_wallpaper.jpg differ
diff --git a/filament/backend/src/DataReshaper.h b/filament/backend/src/DataReshaper.h
index e62289d6db1..20e53507bfa 100644
--- a/filament/backend/src/DataReshaper.h
+++ b/filament/backend/src/DataReshaper.h
@@ -19,29 +19,62 @@
#include
+#include