diff --git a/android/build.gradle b/android/build.gradle index fe03c55d59..0886b009b7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -124,6 +124,7 @@ buildscript { ext.cmakeArgs = [ "--no-warn-unused-cli", + "-DANDROID_WEAK_API_DEFS=ON", "-DANDROID_PIE=ON", "-DANDROID_PLATFORM=21", "-DANDROID_STL=c++_static", diff --git a/android/filament-android/CMakeLists.txt b/android/filament-android/CMakeLists.txt index 94ad5ef6a5..dec19c5bb2 100644 --- a/android/filament-android/CMakeLists.txt +++ b/android/filament-android/CMakeLists.txt @@ -134,6 +134,7 @@ target_include_directories(filament-jni PRIVATE ../../filament/backend/include ../../third_party/robin-map ../../third_party/perfetto + ../../libs/bluevk/include ../../libs/utils/include) # Ordering is significant in the following list. The PRIVATE qualifier prevents transitive deps. @@ -167,3 +168,9 @@ target_link_libraries(filament-jni # Force a relink when the version script is changed: set_target_properties(filament-jni PROPERTIES LINK_DEPENDS ${VERSION_SCRIPT}) + +if (FILAMENT_SUPPORTS_VULKAN) + add_definitions(-DFILAMENT_SUPPORTS_VULKAN=1) +else() + add_definitions(-DFILAMENT_SUPPORTS_VULKAN=0) +endif() diff --git a/android/filament-android/src/main/cpp/SkyBox.cpp b/android/filament-android/src/main/cpp/SkyBox.cpp index 05d0933345..6e1cc19976 100644 --- a/android/filament-android/src/main/cpp/SkyBox.cpp +++ b/android/filament-android/src/main/cpp/SkyBox.cpp @@ -63,6 +63,14 @@ Java_com_google_android_filament_Skybox_nBuilderColor(JNIEnv *, jclass, builder->color({r, g, b, a}); } +extern "C" +JNIEXPORT void JNICALL +Java_com_google_android_filament_Skybox_nBuilderPriority(JNIEnv *, jclass, + jlong nativeSkyBoxBuilder, jint priority) { + Skybox::Builder *builder = (Skybox::Builder *) nativeSkyBoxBuilder; + builder->priority(uint8_t(priority)); +} + extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Skybox_nBuilderBuild(JNIEnv *env, jclass type, jlong nativeSkyBoxBuilder, jlong nativeEngine) { diff --git a/android/filament-android/src/main/cpp/Texture.cpp b/android/filament-android/src/main/cpp/Texture.cpp index 159c5729e2..730ffd1e12 100644 --- a/android/filament-android/src/main/cpp/Texture.cpp +++ b/android/filament-android/src/main/cpp/Texture.cpp @@ -21,6 +21,11 @@ #ifdef __ANDROID__ #include +#include +#include +# if FILAMENT_SUPPORTS_VULKAN +# include +# endif #endif #include @@ -388,6 +393,57 @@ Java_com_google_android_filament_Texture_nSetExternalImage(JNIEnv*, jclass, jlon texture->setExternalImage(*engine, (void*)eglImage); } +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_Texture_nSetExternalImageByAHB(JNIEnv *env, jclass clazz, + jlong nativeTexture, jlong nativeEngine, jobject ahb) { + Texture *texture = (Texture *) nativeTexture; + Engine *engine = (Engine *) nativeEngine; + +#ifdef __ANDROID__ + Platform* platform = engine->getPlatform(); + AHardwareBuffer* nativeBuffer = nullptr; + if (__builtin_available(android 26, *)) { + nativeBuffer = AHardwareBuffer_fromHardwareBuffer(env, ahb); + } + if (!nativeBuffer) { + // either we're not on Android 26, or ahb wasn't a AHardwareBuffer + return JNI_FALSE; + } + + if (engine->getBackend() == Backend::OPENGL) { + // CAVEAT: we assume that Backend::OPENGL on Android implies PlatformEGLAndroid. +#if UTILS_HAS_RTTI + if (!dynamic_cast(platform)) { + return JNI_FALSE; + } +#endif + auto* eglPlatform = (PlatformEGLAndroid*) platform; + auto ref = eglPlatform->createExternalImage(nativeBuffer, false); + texture->setExternalImage(*engine, ref); + } + +#if FILAMENT_SUPPORTS_VULKAN + else if (engine->getBackend() == Backend::VULKAN) { + // CAVEAT: we assume that Backend::VULKAN on Android implies VulkanPlatformAndroid. +#if UTILS_HAS_RTTI + if (!dynamic_cast(platform)) { + return JNI_FALSE; + } +#endif + auto* vulkanPlatform = (VulkanPlatformAndroid*) platform; + auto ref = vulkanPlatform->createExternalImage(nativeBuffer, false); + texture->setExternalImage(*engine, ref); + } +#endif // FILAMENT_SUPPORTS_VULKAN + // success! + return JNI_TRUE; +#else + // other platforms could come here + return JNI_FALSE; +#endif // __ANDROID__ +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Texture_nSetExternalStream(JNIEnv*, jclass, jlong nativeTexture, jlong nativeEngine, jlong nativeStream) { @@ -607,3 +663,4 @@ Java_com_google_android_filament_android_TextureHelper_nSetBitmapWithCallback(JN } #endif + diff --git a/android/filament-android/src/main/java/com/google/android/filament/Skybox.java b/android/filament-android/src/main/java/com/google/android/filament/Skybox.java index e387a11cde..bffbfe42ef 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Skybox.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Skybox.java @@ -155,6 +155,24 @@ public class Skybox { return this; } + /** + * Set the rendering priority of the Skybox. By default, it is set to the lowest + * priority (7) such that the Skybox is always rendered after the opaque objects, + * to reduce overdraw when depth culling is enabled. + * + * @param priority clamped to the range [0..7], defaults to 4; 7 is lowest priority + * (rendered last). + * + * @return Builder reference for chaining calls. + * + * @see RenderableManager.Builder#priority + */ + @NonNull + public Builder priority(@IntRange(from = 0, to = 7) int priority) { + nBuilderPriority(mNativeBuilder, priority); + return this; + } + /** * Creates a Skybox object * @@ -262,6 +280,7 @@ public class Skybox { private static native void nBuilderShowSun(long nativeSkyboxBuilder, boolean show); private static native void nBuilderIntensity(long nativeSkyboxBuilder, float intensity); private static native void nBuilderColor(long nativeSkyboxBuilder, float r, float g, float b, float a); + private static native void nBuilderPriority(long nativeSkyboxBuilder, int priority); private static native long nBuilderBuild(long nativeSkyboxBuilder, long nativeEngine); private static native void nSetLayerMask(long nativeSkybox, int select, int value); private static native int nGetLayerMask(long nativeSkybox); diff --git a/android/filament-android/src/main/java/com/google/android/filament/Texture.java b/android/filament-android/src/main/java/com/google/android/filament/Texture.java index 6fe80ca1f5..6cfd306cc4 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Texture.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Texture.java @@ -71,6 +71,16 @@ import static com.google.android.filament.Texture.Type.COMPRESSED; * @see MaterialInstance#setParameter(String, Texture, TextureSampler) */ public class Texture { + + private static Class HardwareBufferClass = null; + + static { + try { + HardwareBufferClass = Class.forName("android.hardware.HardwareBuffer"); + } catch (ClassNotFoundException ignored) { + } + } + private static final Sampler[] sSamplerValues = Sampler.values(); private static final InternalFormat[] sInternalFormatValues = InternalFormat.values(); @@ -1172,6 +1182,38 @@ public class Texture { nSetExternalImage(getNativeObject(), engine.getNativeObject(), eglImage); } + /** + * Specifies the external image to associate with this Texture. + * + *

Typically, the external image is OS specific, and can be a video or camera frame. + * There are many restrictions when using an external image as a texture, such as:

+ *
    + *
  • only the level of detail (lod) 0 can be specified
  • + *
  • only nearest or linear filtering is supported
  • + *
  • the size and format of the texture is defined by the external image
  • + *
  • only the CLAMP_TO_EDGE wrap mode is supported
  • + *
+ * + * @param engine {@link Engine} this texture is associated to. Must be the + * instance passed to {@link Builder#build Builder.build()}. + * @param externalImageRef An OS specific Object. On Android it must be a + * android.hardware.HardwareBuffer + */ + public void setExternalImage(@NonNull Engine engine, Object externalImageRef) { + if (HardwareBufferClass != null) { + if (!HardwareBufferClass.isInstance(externalImageRef)) { + throw new IllegalArgumentException("externalImageRef must be a AHardwareBuffer"); + } + if (!nSetExternalImageByAHB(getNativeObject(), engine.getNativeObject(), externalImageRef)) { + throw new IllegalStateException("Error setting AHardwareBuffer as external image"); + } + } else { + throw new UnsupportedOperationException( + "setExternalImage(Engine, Object) not supported on this platform"); + } + } + + /** * Specifies the external stream to associate with this Texture. * @@ -1364,6 +1406,8 @@ public class Texture { private static native void nSetExternalImage( long nativeObject, long nativeEngine, long eglImage); + private static native boolean nSetExternalImageByAHB(long nativeTexture, long nativeObject, Object ahb); + private static native void nSetExternalStream(long nativeTexture, long nativeEngine, long nativeStream); diff --git a/android/samples/sample-texture-target/.gitignore b/android/samples/sample-texture-target/.gitignore new file mode 100644 index 0000000000..71e173b003 --- /dev/null +++ b/android/samples/sample-texture-target/.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-texture-target/build.gradle b/android/samples/sample-texture-target/build.gradle new file mode 100644 index 0000000000..84ccfbe915 --- /dev/null +++ b/android/samples/sample-texture-target/build.gradle @@ -0,0 +1,53 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'filament-tools-plugin' +} + +project.ext.isSample = true + +kotlin { + jvmToolchain(versions.jdk) +} + +filamentTools { + materialInputDir = project.layout.projectDirectory.dir("src/main/materials") + materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials") +} + +clean.doFirst { + delete "src/main/assets" +} + +android { + namespace 'com.google.android.filament.texturetarget' + + compileSdkVersion versions.compileSdk + defaultConfig { + applicationId "com.google.android.filament.texturetarget" + minSdkVersion 26 + targetSdkVersion versions.targetSdk + } + + // NOTE: This is a workaround required because the AGP task collectReleaseDependencies + // is not configuration-cache friendly yet; this is only useful for Play publication + dependenciesInfo { + includeInApk = false + } + + // 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' + } + + compileOptions { + sourceCompatibility versions.jdk + targetCompatibility versions.jdk + } +} + +dependencies { + implementation deps.kotlin + implementation project(':filament-android') +} diff --git a/android/samples/sample-texture-target/src/main/AndroidManifest.xml b/android/samples/sample-texture-target/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..642333b1fe --- /dev/null +++ b/android/samples/sample-texture-target/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/android/samples/sample-texture-target/src/main/java/com/google/android/filament/texturetarget/MainActivity.kt b/android/samples/sample-texture-target/src/main/java/com/google/android/filament/texturetarget/MainActivity.kt new file mode 100644 index 0000000000..3065038829 --- /dev/null +++ b/android/samples/sample-texture-target/src/main/java/com/google/android/filament/texturetarget/MainActivity.kt @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2024 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.texturetarget + +import android.animation.ValueAnimator +import android.app.Activity +import android.hardware.HardwareBuffer +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.PrimitiveType +import com.google.android.filament.VertexBuffer.AttributeType +import com.google.android.filament.VertexBuffer.VertexAttribute +import com.google.android.filament.android.DisplayHelper +import com.google.android.filament.android.FilamentHelper +import com.google.android.filament.android.UiHelper +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.channels.Channels +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +class MainActivity : Activity() { + companion object { + init { + Filament.init() + } + } + + private lateinit var surfaceView: SurfaceView + private lateinit var uiHelper: UiHelper + private lateinit var displayHelper: DisplayHelper + private lateinit var choreographer: Choreographer + + private lateinit var engine: Engine + private lateinit var renderer: Renderer + private lateinit var scene: Scene + private lateinit var view: View + private lateinit var camera: Camera + + private lateinit var triangleMaterial: Material + private lateinit var texturedMaterial: Material + private lateinit var triangleVertexBuffer: VertexBuffer + private lateinit var triangleIndexBuffer: IndexBuffer + private lateinit var quadVertexBuffer: VertexBuffer + private lateinit var quadIndexBuffer: IndexBuffer + + @Entity private var triangleRenderable = 0 + @Entity private var quadRenderable = 0 + + private var swapChain: SwapChain? = null + + private val frameScheduler = FrameCallback() + private val animator = ValueAnimator.ofFloat(0.0f, 360.0f) + + private var hardwareBuffer: HardwareBuffer? = null + private var texture: Texture? = null + private var renderTarget: RenderTarget? = null + + private lateinit var offscreenView: View + private lateinit var offscreenCamera: Camera + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + surfaceView = SurfaceView(this) + setContentView(surfaceView) + choreographer = Choreographer.getInstance() + displayHelper = DisplayHelper(this) + setupSurfaceView() + setupFilament() + setupView() + setupScene() + } + + private fun setupSurfaceView() { + uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK) + uiHelper.renderCallback = SurfaceCallback() + uiHelper.attachTo(surfaceView) + } + + private fun setupFilament() { + engine = Engine.create() + renderer = engine.createRenderer() + scene = engine.createScene() + view = engine.createView() + camera = engine.createCamera(engine.entityManager.create()) + offscreenView = engine.createView() + offscreenCamera = engine.createCamera(engine.entityManager.create()) + } + + private fun setupView() { + scene.skybox = Skybox.Builder() + .priority(0) + .color(0.0f, 0.0f, 1.0f, 1.0f).build(engine) + + // This is the view that will be drawn on screen. + view.camera = camera + view.scene = scene + view.isPostProcessingEnabled = false + + // This is the view that will be rendered off-screen. + offscreenView.camera = offscreenCamera + offscreenView.scene = scene + offscreenView.isPostProcessingEnabled = false + } + + private fun setupScene() { + loadMaterials() + createTriangleMesh() + createQuadMesh() + + // layer 1: skybox + // layer 2: triangle + // layer 3: quad + + triangleMaterial.defaultInstance.cullingMode = Material.CullingMode.NONE; + texturedMaterial.defaultInstance.cullingMode = Material.CullingMode.NONE; + + // The triangle is a regular renderable. + triangleRenderable = EntityManager.get().create() + RenderableManager.Builder(1) + .geometry(0, PrimitiveType.TRIANGLES, triangleVertexBuffer, triangleIndexBuffer, 0, 3) + .material(0, triangleMaterial.defaultInstance) + .culling(false) + .castShadows(false) + .receiveShadows(false) + .layerMask(7, 2) + .build(engine, triangleRenderable) + + // The quad is a regular renderable. + quadRenderable = EntityManager.get().create() + RenderableManager.Builder(1) + .geometry(0, PrimitiveType.TRIANGLES, quadVertexBuffer, quadIndexBuffer, 0, 6) + .material(0, texturedMaterial.defaultInstance) + .culling(false) + .castShadows(false) + .receiveShadows(false) + .layerMask(7, 4) + .build(engine, quadRenderable) + + // We only want to render the triangle in the offscreen view. + offscreenView.setVisibleLayers(7, 3) // render skybox + triangle + + // We only want to render the quad in the on-screen view. + view.setVisibleLayers(7, 4) // render quad only + + scene.addEntity(triangleRenderable) + scene.addEntity(quadRenderable) + + startAnimation() + } + + private fun loadMaterials() { + readUncompressedAsset("materials/baked_color.filamat").let { + triangleMaterial = Material.Builder().payload(it, it.remaining()).build(engine) + } + readUncompressedAsset("materials/textured.filamat").let { + texturedMaterial = Material.Builder().payload(it, it.remaining()).build(engine) + } + } + + private fun createTriangleMesh() { + val intSize = 4 + val floatSize = 4 + val shortSize = 2 + val vertexSize = 3 * floatSize + intSize + + data class Vertex(val x: Float, val y: Float, val z: Float, val color: Int) + fun ByteBuffer.put(v: Vertex): ByteBuffer { + putFloat(v.x) + putFloat(v.y) + putFloat(v.z) + putInt(v.color) + return this + } + + val vertexCount = 3 + val a1 = PI * 2.0 / 3.0 + val a2 = PI * 4.0 / 3.0 + + val vertexData = ByteBuffer.allocate(vertexCount * vertexSize) + .order(ByteOrder.nativeOrder()) + .put(Vertex(1.0f, 0.0f, 0.0f, 0xffff0000.toInt())) + .put(Vertex(cos(a1).toFloat(), sin(a1).toFloat(), 0.0f, 0xff00ff00.toInt())) + .put(Vertex(cos(a2).toFloat(), sin(a2).toFloat(), 0.0f, 0xff0000ff.toInt())) + .flip() + + triangleVertexBuffer = VertexBuffer.Builder() + .bufferCount(1) + .vertexCount(vertexCount) + .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize) + .attribute(VertexAttribute.COLOR, 0, AttributeType.UBYTE4, 3 * floatSize, vertexSize) + .normalized(VertexAttribute.COLOR) + .build(engine) + triangleVertexBuffer.setBufferAt(engine, 0, vertexData) + + val indexData = ByteBuffer.allocate(vertexCount * shortSize) + .order(ByteOrder.nativeOrder()) + .putShort(0) + .putShort(1) + .putShort(2) + .flip() + + triangleIndexBuffer = IndexBuffer.Builder() + .indexCount(3) + .bufferType(IndexBuffer.Builder.IndexType.USHORT) + .build(engine) + triangleIndexBuffer.setBuffer(engine, indexData) + } + + private fun createQuadMesh() { + val floatSize = 4 + val shortSize = 2 + val vertexSize = (2 * floatSize) + (2 * floatSize) // position + UV + + data class Vertex(val x: Float, val y: Float, val u: Float, val v: Float) + fun ByteBuffer.put(v: Vertex): ByteBuffer { + putFloat(v.x) + putFloat(v.y) + putFloat(v.u) + putFloat(v.v) + return this + } + + val vertexCount = 4 + val vertexData = ByteBuffer.allocate(vertexCount * vertexSize) + .order(ByteOrder.nativeOrder()) + .put(Vertex(-1.0f, -1.0f, 0.0f, 0.0f)) + .put(Vertex( 1.0f, -1.0f, 1.0f, 0.0f)) + .put(Vertex( 1.0f, 1.0f, 1.0f, 1.0f)) + .put(Vertex(-1.0f, 1.0f, 0.0f, 1.0f)) + .flip() + + quadVertexBuffer = VertexBuffer.Builder() + .bufferCount(1) + .vertexCount(vertexCount) + .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, vertexSize) + .attribute(VertexAttribute.UV0, 0, AttributeType.FLOAT2, 2 * floatSize, vertexSize) + .build(engine) + quadVertexBuffer.setBufferAt(engine, 0, vertexData) + + val indexData = ByteBuffer.allocate(6 * shortSize) + .order(ByteOrder.nativeOrder()) + .putShort(0).putShort(1).putShort(2) + .putShort(0).putShort(2).putShort(3) + .flip() + + quadIndexBuffer = IndexBuffer.Builder() + .indexCount(6) + .bufferType(IndexBuffer.Builder.IndexType.USHORT) + .build(engine) + quadIndexBuffer.setBuffer(engine, indexData) + } + + private fun startAnimation() { + animator.interpolator = LinearInterpolator() + animator.duration = 4000 + animator.repeatMode = ValueAnimator.RESTART + animator.repeatCount = ValueAnimator.INFINITE + animator.addUpdateListener { a -> + val transformMatrix = FloatArray(16) + Matrix.setRotateM(transformMatrix, 0, -(a.animatedValue as Float), 0.0f, 0.0f, 1.0f) + val tcm = engine.transformManager + tcm.setTransform(tcm.getInstance(triangleRenderable), 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() + choreographer.removeFrameCallback(frameScheduler) + animator.cancel() + uiHelper.detach() + + // Destroy all renderables. + scene.remove(triangleRenderable) + scene.remove(quadRenderable) + + // Destroy all resources. + engine.destroyEntity(triangleRenderable) + engine.destroyEntity(quadRenderable) + engine.destroyRenderer(renderer) + engine.destroyVertexBuffer(triangleVertexBuffer) + engine.destroyIndexBuffer(triangleIndexBuffer) + engine.destroyVertexBuffer(quadVertexBuffer) + engine.destroyIndexBuffer(quadIndexBuffer) + engine.destroyMaterial(triangleMaterial) + engine.destroyMaterial(texturedMaterial) + engine.destroyView(view) + engine.destroyView(offscreenView) + engine.destroyScene(scene) + engine.destroyCameraComponent(camera.entity) + engine.destroyCameraComponent(offscreenCamera.entity) + renderTarget?.let { engine.destroyRenderTarget(it) } + texture?.let { engine.destroyTexture(it) } + hardwareBuffer?.close() + + val entityManager = EntityManager.get() + entityManager.destroy(triangleRenderable) + entityManager.destroy(quadRenderable) + entityManager.destroy(camera.entity) + entityManager.destroy(offscreenCamera.entity) + + engine.destroy() + } + + inner class FrameCallback : Choreographer.FrameCallback { + override fun doFrame(frameTimeNanos: Long) { + choreographer.postFrameCallback(this) + if (uiHelper.isReadyToRender) { + if (renderer.beginFrame(swapChain!!, frameTimeNanos)) { + // Render the triangle to the texture. + renderer.render(offscreenView) + // Render the quad to the screen. + renderer.render(view) + renderer.endFrame() + } + } + } + } + + inner class SurfaceCallback : UiHelper.RendererCallback { + override fun onNativeWindowChanged(surface: Surface) { + swapChain?.let { engine.destroySwapChain(it) } + swapChain = engine.createSwapChain(surface, uiHelper.swapChainFlags) + displayHelper.attach(renderer, surfaceView.display) + } + + override fun onDetachedFromSurface() { + displayHelper.detach() + swapChain?.let { + engine.destroySwapChain(it) + engine.flushAndWait() + swapChain = null + } + } + + override fun onResized(width: Int, height: Int) { + // On-screen camera + val zoom = 1.0 + val aspect = width.toDouble() / height.toDouble() + camera.setProjection(Camera.Projection.ORTHO, -aspect * zoom, aspect * zoom, -zoom, zoom, 0.0, 10.0) + view.viewport = Viewport(0, 0, width, height) + + // Off-screen camera + val offscreenZoom = 1.5 + offscreenCamera.setProjection(Camera.Projection.ORTHO, + -aspect * offscreenZoom, aspect * offscreenZoom, + -offscreenZoom, offscreenZoom, 0.0, 10.0) + offscreenView.viewport = Viewport(0, 0, width, height) + + // If we have a render target, destroy it. + renderTarget?.let { engine.destroyRenderTarget(it) } + texture?.let { engine.destroyTexture(it) } + hardwareBuffer?.close() + + // Create a new render target. + hardwareBuffer = HardwareBuffer.create(width, height, + HardwareBuffer.RGBA_8888, 1, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) + + texture = Texture.Builder() + .width(width) + .height(height) + .usage(Texture.Usage.COLOR_ATTACHMENT or Texture.Usage.SAMPLEABLE) + .sampler(Texture.Sampler.SAMPLER_EXTERNAL) + .format(Texture.InternalFormat.RGBA8) + .external() + .build(engine) + + texture!!.setExternalImage(engine, hardwareBuffer!!) + + renderTarget = RenderTarget.Builder() + .texture(RenderTarget.AttachmentPoint.COLOR, texture!!) + .build(engine) + + offscreenView.renderTarget = renderTarget + + // Set the texture on the quad material. + texturedMaterial.defaultInstance.setParameter("texture", texture!!, + TextureSampler(TextureSampler.MinFilter.LINEAR, TextureSampler.MagFilter.LINEAR, + TextureSampler.WrapMode.CLAMP_TO_EDGE)) + + FilamentHelper.synchronizePendingFrames(engine) + } + } + + 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-texture-target/src/main/materials/baked_color.mat b/android/samples/sample-texture-target/src/main/materials/baked_color.mat new file mode 100644 index 0000000000..364c94cfeb --- /dev/null +++ b/android/samples/sample-texture-target/src/main/materials/baked_color.mat @@ -0,0 +1,18 @@ +material { + name : baked_color, + shadingModel : unlit, + requires : [ + color + ] +} + +fragment { + void material(inout MaterialInputs material) { + // You must always call the prepareMaterial() function + prepareMaterial(material); + + // We set the material's color to the color interpolated from + // the model's vertices + material.baseColor = getColor(); + } +} diff --git a/android/samples/sample-texture-target/src/main/materials/textured.mat b/android/samples/sample-texture-target/src/main/materials/textured.mat new file mode 100644 index 0000000000..5207b78852 --- /dev/null +++ b/android/samples/sample-texture-target/src/main/materials/textured.mat @@ -0,0 +1,20 @@ +material { + name : textured, + shadingModel : unlit, + parameters : [ + { + type : samplerExternal, + name : texture + } + ], + requires: [ + uv0 + ] +} + +fragment { + void material(inout MaterialInputs material) { + prepareMaterial(material); + material.baseColor = texture(materialParams_texture, getUV0()); + } +} diff --git a/android/samples/sample-texture-target/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/samples/sample-texture-target/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..b1517edf49 --- /dev/null +++ b/android/samples/sample-texture-target/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/android/samples/sample-texture-target/src/main/res/drawable/ic_launcher_background.xml b/android/samples/sample-texture-target/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..88e31872fe --- /dev/null +++ b/android/samples/sample-texture-target/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/samples/sample-texture-target/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..6d5e5d094c --- /dev/null +++ b/android/samples/sample-texture-target/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-texture-target/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/samples/sample-texture-target/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..6d5e5d094c --- /dev/null +++ b/android/samples/sample-texture-target/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-texture-target/src/main/res/mipmap-hdpi/ic_launcher.png b/android/samples/sample-texture-target/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..a2f5908281 Binary files /dev/null and b/android/samples/sample-texture-target/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/samples/sample-texture-target/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-texture-target/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-mdpi/ic_launcher.png b/android/samples/sample-texture-target/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..ff10afd6e1 Binary files /dev/null and b/android/samples/sample-texture-target/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/samples/sample-texture-target/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-texture-target/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/samples/sample-texture-target/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..dcd3cd8083 Binary files /dev/null and b/android/samples/sample-texture-target/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/samples/sample-texture-target/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-texture-target/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/samples/sample-texture-target/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..8ca12fe024 Binary files /dev/null and b/android/samples/sample-texture-target/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/samples/sample-texture-target/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-texture-target/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/samples/sample-texture-target/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b824ebdd48 Binary files /dev/null and b/android/samples/sample-texture-target/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/samples/sample-texture-target/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/samples/sample-texture-target/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-texture-target/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/samples/sample-texture-target/src/main/res/values/colors.xml b/android/samples/sample-texture-target/src/main/res/values/colors.xml new file mode 100644 index 0000000000..5a077b3a78 --- /dev/null +++ b/android/samples/sample-texture-target/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/android/samples/sample-texture-target/src/main/res/values/strings.xml b/android/samples/sample-texture-target/src/main/res/values/strings.xml new file mode 100644 index 0000000000..62c2e46fd7 --- /dev/null +++ b/android/samples/sample-texture-target/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Texture Target + diff --git a/android/samples/sample-texture-target/src/main/res/values/styles.xml b/android/samples/sample-texture-target/src/main/res/values/styles.xml new file mode 100644 index 0000000000..a7a06158ff --- /dev/null +++ b/android/samples/sample-texture-target/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/android/settings.gradle b/android/settings.gradle index 694e340df2..227c58af75 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -17,6 +17,7 @@ include ':samples:sample-multi-view' include ':samples:sample-page-curl' include ':samples:sample-stream-test' include ':samples:sample-texture-view' +include ':samples:sample-texture-target' include ':samples:sample-textured-object' include ':samples:sample-transparent-view' diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 18c1674752..7d9f36c4ff 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -1344,7 +1344,6 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, GLTexture* t = handle_cast(binfo.handle); assert_invariant(t); - assert_invariant(t->target != SamplerType::SAMPLER_EXTERNAL); assert_invariant(rt->width <= valueForLevel(binfo.level, t->width) && rt->height <= valueForLevel(binfo.level, t->height)); @@ -1418,6 +1417,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, case SamplerType::SAMPLER_3D: case SamplerType::SAMPLER_2D_ARRAY: case SamplerType::SAMPLER_CUBEMAP_ARRAY: + case SamplerType::SAMPLER_EXTERNAL: // this could be GL_TEXTURE_2D_MULTISAMPLE or GL_TEXTURE_2D_ARRAY target = t->gl.target; // note: multi-sampled textures can't have mipmaps @@ -1426,10 +1426,6 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, target = getCubemapTarget(binfo.layer); // note: cubemaps can't be multi-sampled break; - case SamplerType::SAMPLER_EXTERNAL: - // This is an error. We have asserted in debug build. - target = t->gl.target; - break; } } @@ -1458,6 +1454,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: case GL_TEXTURE_2D: + case GL_TEXTURE_EXTERNAL_OES: #if defined(BACKEND_OPENGL_LEVEL_GLES31) case GL_TEXTURE_2D_MULTISAMPLE: #endif @@ -1465,7 +1462,9 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo, glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, target, t->gl.id, binfo.level); } else { - assert_invariant(target == GL_TEXTURE_2D); + // in principle, it's possible to have a renderbuffer that's external + // (it filament this never happens, currently) + assert_invariant(target == GL_TEXTURE_2D || target == GL_TEXTURE_EXTERNAL_OES); glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, t->gl.id); } diff --git a/filament/include/filament/Skybox.h b/filament/include/filament/Skybox.h index ce203aae6a..bacd0acb56 100644 --- a/filament/include/filament/Skybox.h +++ b/filament/include/filament/Skybox.h @@ -131,6 +131,20 @@ public: */ Builder& color(math::float4 color) noexcept; + /** + * Set the rendering priority of the Skybox. By default, it is set to the lowest + * priority (7) such that the Skybox is always rendered after the opaque objects, + * to reduce overdraw when depth culling is enabled. + * + * @param priority clamped to the range [0..7], defaults to 4; 7 is lowest priority + * (rendered last). + * + * @return Builder reference for chaining calls. + * + * @see RenderableManager::Builder::priority() + */ + Builder& priority(uint8_t priority) noexcept; + /** * Creates the Skybox object and returns a pointer to it. * diff --git a/filament/src/details/MaterialInstance.cpp b/filament/src/details/MaterialInstance.cpp index 015af420dc..96678bf15d 100644 --- a/filament/src/details/MaterialInstance.cpp +++ b/filament/src/details/MaterialInstance.cpp @@ -298,6 +298,10 @@ void FMaterialInstance::setParameterImpl(std::string_view const name, TextureType const textureType = texture->getTextureType(); SamplerType const samplerType = texture->getTarget(); auto const& featureFlags = mMaterial->getEngine().features.engine.debug; + + FILAMENT_CHECK_PRECONDITION(texture->getUsage() & TextureUsage::SAMPLEABLE) + << "Texture for parameter \"" << name << "\"" << " is not SAMPLEABLE"; + FILAMENT_FLAG_GUARDED_CHECK_PRECONDITION( DescriptorSet::isTextureCompatibleWithDescriptor( textureType, samplerType, descriptorType), diff --git a/filament/src/details/RenderTarget.cpp b/filament/src/details/RenderTarget.cpp index c327da18ca..0c86bd59af 100644 --- a/filament/src/details/RenderTarget.cpp +++ b/filament/src/details/RenderTarget.cpp @@ -98,15 +98,11 @@ RenderTarget* RenderTarget::Builder::build(Engine& engine) { if (color.texture) { FILAMENT_CHECK_PRECONDITION(color.texture->getUsage() & TextureUsage::COLOR_ATTACHMENT) << "Texture usage must contain COLOR_ATTACHMENT"; - FILAMENT_CHECK_PRECONDITION(color.texture->getTarget() != Texture::Sampler::SAMPLER_EXTERNAL) - << "Color attachment can't be an external texture"; } if (depth.texture) { FILAMENT_CHECK_PRECONDITION(depth.texture->getUsage() & TextureUsage::DEPTH_ATTACHMENT) << "Texture usage must contain DEPTH_ATTACHMENT"; - FILAMENT_CHECK_PRECONDITION(depth.texture->getTarget() != Texture::Sampler::SAMPLER_EXTERNAL) - << "Depth attachment can't be an external texture"; } const size_t maxDrawBuffers = downcast(engine).getDriverApi().getMaxDrawBuffers(); diff --git a/filament/src/details/Skybox.cpp b/filament/src/details/Skybox.cpp index 2de015dfb8..2a07cb33e9 100644 --- a/filament/src/details/Skybox.cpp +++ b/filament/src/details/Skybox.cpp @@ -51,6 +51,7 @@ struct Skybox::BuilderDetails { float4 mColor{0, 0, 0, 1}; float mIntensity = FIndirectLight::DEFAULT_INTENSITY; bool mShowSun = false; + uint8_t mPriority = 7; }; using BuilderType = Skybox; @@ -77,13 +78,18 @@ Skybox::Builder& Skybox::Builder::color(float4 const color) noexcept { return *this; } +Skybox::Builder& Skybox::Builder::priority(uint8_t const priority) noexcept { + mImpl->mPriority = priority; + return *this; +} + Skybox::Builder& Skybox::Builder::showSun(bool const show) noexcept { mImpl->mShowSun = show; return *this; } Skybox* Skybox::Builder::build(Engine& engine) { - FTexture* cubemap = downcast(mImpl->mEnvironmentMap); + FTexture const* cubemap = downcast(mImpl->mEnvironmentMap); FILAMENT_CHECK_PRECONDITION(!cubemap || cubemap->isCubemap()) << "environment maps must be a cubemap"; @@ -118,7 +124,7 @@ FSkybox::FSkybox(FEngine& engine, const Builder& builder) noexcept .material(0, mSkyboxMaterialInstance) .castShadows(false) .receiveShadows(false) - .priority(0x7) + .priority(builder->mPriority) .culling(false) .build(engine, mSkybox); } diff --git a/filament/src/details/Texture.cpp b/filament/src/details/Texture.cpp index 12587a51cc..dc0fd7dd64 100644 --- a/filament/src/details/Texture.cpp +++ b/filament/src/details/Texture.cpp @@ -219,7 +219,7 @@ Texture* Texture::Builder::build(Engine& engine) { << "SamplerType " << uint8_t(mImpl->mTarget) << " not support at feature level " << uint8_t(engine.getActiveFeatureLevel()); - // SAMPLER_EXTERNAL implies imported. + // SAMPLER_EXTERNAL implies external textures. if (mImpl->mTarget == SamplerType::SAMPLER_EXTERNAL) { mImpl->mExternal = true; } @@ -229,7 +229,6 @@ Texture* Texture::Builder::build(Engine& engine) { case SamplerType::SAMPLER_2D: case SamplerType::SAMPLER_2D_ARRAY: case SamplerType::SAMPLER_CUBEMAP: - case SamplerType::SAMPLER_EXTERNAL: case SamplerType::SAMPLER_CUBEMAP_ARRAY: maxLevelCount = FTexture::maxLevelCount(mImpl->mWidth, mImpl->mHeight); break; @@ -237,11 +236,19 @@ Texture* Texture::Builder::build(Engine& engine) { maxLevelCount = FTexture::maxLevelCount(std::max( { mImpl->mWidth, mImpl->mHeight, mImpl->mDepth })); break; + case SamplerType::SAMPLER_EXTERNAL: + // external samplers can't mipmap + maxLevelCount = 1; + break; } mImpl->mLevels = std::min(mImpl->mLevels, maxLevelCount); if (mImpl->mUsage == TextureUsage::NONE) { mImpl->mUsage = TextureUsage::DEFAULT; + if (mImpl->mExternal) { + // external textures can't be uploadable + mImpl->mUsage = TextureUsage::SAMPLEABLE; + } } auto const& featureFlags = downcast(engine).features.engine.debug; @@ -662,7 +669,6 @@ bool FTexture::textureHandleCanMutate() const noexcept { } void FTexture::updateLodRange(uint8_t const baseLevel, uint8_t const levelCount) noexcept { - assert_invariant(!mExternal); if (any(mUsage & Usage::SAMPLEABLE) && mLevelCount > 1) { auto& range = mLodRange; uint8_t const last = int8_t(baseLevel + levelCount); diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 5d8a408566..009a9382e2 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1598,6 +1598,8 @@ class_("Skybox$Builder") .function("_build", EMBIND_LAMBDA(Skybox*, (SkyBuilder* builder, Engine* engine), { return builder->build(*engine); }), allow_raw_pointers()) + .BUILDER_FUNCTION("priority", SkyBuilder, (SkyBuilder* builder, uint8_t priority), { + return &builder->priority(priority); }) .BUILDER_FUNCTION("color", SkyBuilder, (SkyBuilder* builder, filament::math::float4 color), { return &builder->color(color); }) .BUILDER_FUNCTION("environment", SkyBuilder, (SkyBuilder* builder, Texture* cubemap), {