Files
filament/android/filament-android/src/main/cpp/Texture.cpp
T
Mathias Agopian f4a079f663 backend: Propagate backend thread exceptions to the main thread. (#9903)
* Filament Error Handling Remediation and noexcept Cleanup

- Replaced assert_invariant with FILAMENT_CHECK_PRECONDITION in
  Renderer::beginFrame for caller bugs.
- Removed noexcept from Camera::setCustomProjection to allow precondition
  checks to throw.
- Removed noexcept from Texture and InstanceBuffer methods using precondition
  checks.
- Documented silent clamping in View::setBloomOptions.
- Clarified comment in FrameGraphResources regarding preconditions.
- Refined MaterialInstance::commit to split texture loop into check and update
  passes, preventing partial state changes on precondition failure.

* backend: Propagate backend thread exceptions to the main thread.

Introduce a mechanism to catch exceptions thrown on the backend thread and
rethrow them on the main thread. This prevents deadlocks and allows the
application to handle fatal backend failures gracefully.

- Consolidate synchronization primitives in DriverBase.
- Add mHasUnrecoverableError flag to interrupt blocked fence waits.
- Optimize waitForFence to avoid extra atomic reads in common paths.
- Propagate FenceStatus::ERROR across all backends.
- CommandBufferQueue now stores a std::exception_ptr when the backend fails.
- The backend enters a "zombie" state on failure, skipping further command
  execution but allowing clean shutdown.
- Public APIs in Renderer and Engine now check for stored exceptions and
  rethrow them, documented with @throws.
- Guarded by __EXCEPTIONS to ensure no overhead when exceptions are disabled.
- Add unit test for fence interruption and document new APIs.

BUGS=[407545700]

* feat: harden backend exceptions and add hasUnrecoverableFailure API

- Update Renderer::beginFrame() and Renderer::shouldRenderFrame() to
  return false early if an unrecoverable backend exception has been
  delivered to the main thread.
- Document the new return behavior for beginFrame() and
  shouldRenderFrame() in Renderer.h.
- Add Engine::hasUnrecoverableFailure() to the public API to allow
  apps to check for fatal errors without relying on exceptions.
- Implement hasUnrecoverableFailure() in FEngine by delegating to
  CommandBufferQueue.
- Expose Engine::hasUnrecoverableFailure() to Java bindings
  (Engine.java and JNI).
- Expose Engine::hasUnrecoverableFailure() to JavaScript bindings
  (jsbindings.cpp).

* java: propagate C++ Panics & exceptions to JAVA
2026-04-22 10:56:09 -07:00

692 lines
26 KiB
C++

/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jni.h>
#include <algorithm>
#ifdef __ANDROID__
#include <android/bitmap.h>
#include <android/hardware_buffer_jni.h>
#include <backend/platforms/PlatformEGLAndroid.h>
# if FILAMENT_SUPPORTS_VULKAN
# include <backend/platforms/VulkanPlatformAndroid.h>
# endif
#endif
#include <filament/Engine.h>
#include <filament/Stream.h>
#include <filament/Texture.h>
#include <filament-generatePrefilterMipmap/generatePrefilterMipmap.h>
#include <backend/BufferDescriptor.h>
#include "common/CallbackUtils.h"
#include "common/NioUtils.h"
#include <common/JniUtils.h>
#include "private/backend/VirtualMachineEnv.h"
using namespace filament;
using namespace backend;
using namespace filament::android;
static size_t getTextureDataSize(const Texture *texture,
size_t level, Texture::Format format, Texture::Type type,
size_t stride, size_t height, size_t alignment) {
// Zero stride implies tight row-to-row packing.
stride = stride == 0 ? texture->getWidth(level) : std::max(size_t(1), stride >> level);
height = height == 0 ? texture->getHeight(level) : std::max(size_t(1), height >> level);
return Texture::computeTextureDataSize(format, type, stride, height, alignment);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Texture_nIsTextureFormatSupported(JNIEnv*, jclass,
jlong nativeEngine, jint internalFormat) {
Engine *engine = (Engine *) nativeEngine;
return (jboolean) Texture::isTextureFormatSupported(*engine,
(Texture::InternalFormat) internalFormat);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Texture_nIsTextureFormatMipmappable(JNIEnv*, jclass,
jlong nativeEngine, jint internalFormat) {
Engine *engine = (Engine *) nativeEngine;
return (jboolean) Texture::isTextureFormatMipmappable(*engine,
(Texture::InternalFormat) internalFormat);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Texture_nIsTextureSwizzleSupported(JNIEnv*, jclass,
jlong nativeEngine) {
Engine *engine = (Engine *) nativeEngine;
return (jboolean) Texture::isTextureSwizzleSupported(*engine);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetMaxTextureSize(JNIEnv *, jclass,
jlong nativeEngine, jint sampler) {
Engine *engine = (Engine *) nativeEngine;
return Texture::getMaxTextureSize(*engine, (Texture::Sampler)sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetMaxArrayTextureLayers(JNIEnv *, jclass,
jlong nativeEngine) {
Engine *engine = (Engine *) nativeEngine;
return Texture::getMaxArrayTextureLayers(*engine);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Texture_nValidatePixelFormatAndType(JNIEnv*, jclass,
jint internalFormat, jint pixelDataFormat, jint pixelDataType) {
return (jboolean) Texture::validatePixelFormatAndType(
(Texture::InternalFormat) internalFormat,
(Texture::Format) pixelDataFormat,
(Texture::Type) pixelDataType
);
}
// Texture::Builder...
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Texture_nCreateBuilder(JNIEnv*, jclass) {
return (jlong) new Texture::Builder();
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nDestroyBuilder(JNIEnv*, jclass,
jlong nativeBuilder) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
delete builder;
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderWidth(JNIEnv*, jclass,
jlong nativeBuilder, jint width) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->width((uint32_t) width);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderHeight(JNIEnv*, jclass,
jlong nativeBuilder, jint height) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->height((uint32_t) height);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderDepth(JNIEnv*, jclass,
jlong nativeBuilder, jint depth) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->depth((uint32_t) depth);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderLevels(JNIEnv*, jclass,
jlong nativeBuilder, jint levels) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->levels((uint8_t) levels);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderSampler(JNIEnv*, jclass,
jlong nativeBuilder, jint sampler) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->sampler((Texture::Sampler) sampler);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderFormat(JNIEnv*, jclass,
jlong nativeBuilder, jint format) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->format((Texture::InternalFormat) format);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderUsage(JNIEnv*, jclass,
jlong nativeBuilder, jint flags) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->usage((Texture::Usage) flags);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderSwizzle(JNIEnv *, jclass ,
jlong nativeBuilder, jint r, jint g, jint b, jint a) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->swizzle(
(Texture::Swizzle)r, (Texture::Swizzle)g, (Texture::Swizzle)b, (Texture::Swizzle)a);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderSamples(JNIEnv*, jclass,
jlong nativeBuilder, jint samples) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->samples((uint8_t) samples);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderImportTexture(JNIEnv*, jclass, jlong nativeBuilder, jlong id) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->import((intptr_t)id);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nBuilderExternal(JNIEnv*, jclass, jlong nativeBuilder) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
builder->external();
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Texture_nBuilderBuild(JNIEnv*, jclass,
jlong nativeBuilder, jlong nativeEngine) {
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
Engine *engine = (Engine *) nativeEngine;
return (jlong) builder->build(*engine);
}
// Texture...
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetWidth(JNIEnv*, jclass, jlong nativeTexture,
jint level) {
Texture *texture = (Texture *) nativeTexture;
return (jint) texture->getWidth((size_t) level);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetHeight(JNIEnv*, jclass, jlong nativeTexture,
jint level) {
Texture *texture = (Texture *) nativeTexture;
return (jint) texture->getHeight((size_t) level);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetDepth(JNIEnv*, jclass, jlong nativeTexture,
jint level) {
Texture *texture = (Texture *) nativeTexture;
return (jint) texture->getDepth((size_t) level);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetLevels(JNIEnv*, jclass, jlong nativeTexture) {
Texture *texture = (Texture *) nativeTexture;
return (jint) texture->getLevels();
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetTarget(JNIEnv*, jclass, jlong nativeTexture) {
Texture *texture = (Texture *) nativeTexture;
return (jint) texture->getTarget();
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGetInternalFormat(JNIEnv*, jclass,
jlong nativeTexture) {
Texture *texture = (Texture *) nativeTexture;
return (jint) texture->getFormat();
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nSetImage3D(JNIEnv* env, jclass, jlong nativeTexture,
jlong nativeEngine, jint level,
jint xoffset, jint yoffset, jint zoffset,
jint width, jint height, jint depth,
jobject storage, jint remaining,
jint left, jint top, jint type, jint alignment,
jint stride, jint format,
jobject handler, jobject runnable) {
Texture* texture = (Texture*) nativeTexture;
Engine* engine = (Engine*) nativeEngine;
size_t sizeInBytes = getTextureDataSize(texture, (size_t) level, (Texture::Format) format,
(Texture::Type) type, (size_t) stride, (size_t) height, (size_t) alignment) * depth;
AutoBuffer nioBuffer(env, storage, 0);
if (sizeInBytes > (size_t(remaining) << nioBuffer.getShift())) {
// BufferOverflowException
return -1;
}
void *buffer = nioBuffer.getData();
auto *callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer));
Texture::PixelBufferDescriptor desc(buffer, sizeInBytes, (backend::PixelDataFormat) format,
(backend::PixelDataType) type, (uint8_t) alignment, (uint32_t) left, (uint32_t) top,
(uint32_t) stride,
callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback);
return wrapJni<jint>(env, [&]() {
texture->setImage(*engine, (size_t) level,
(uint32_t) xoffset, (uint32_t) yoffset, (uint32_t) zoffset,
(uint32_t) width, (uint32_t) height, (uint32_t) depth,
std::move(desc));
return 0;
});
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nSetImage3DCompressed(JNIEnv *env, jclass,
jlong nativeTexture, jlong nativeEngine, jint level,
jint xoffset, jint yoffset, jint zoffset,
jint width, jint height, jint depth,
jobject storage, jint remaining,
jint, jint, jint, jint, jint compressedSizeInBytes, jint compressedFormat,
jobject handler, jobject runnable) {
Texture *texture = (Texture *) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
size_t sizeInBytes = (size_t) compressedSizeInBytes;
AutoBuffer nioBuffer(env, storage, 0);
if (sizeInBytes > (size_t(remaining) << nioBuffer.getShift())) {
// BufferOverflowException
return -1;
}
void *buffer = nioBuffer.getData();
auto *callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer));
Texture::PixelBufferDescriptor desc(buffer, sizeInBytes,
(backend::CompressedPixelDataType) compressedFormat, (uint32_t) compressedSizeInBytes,
callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback);
return wrapJni<jint>(env, [&]() {
texture->setImage(*engine, (size_t) level,
(uint32_t) xoffset, (uint32_t) yoffset, (uint32_t) zoffset,
(uint32_t) width, (uint32_t) height, (uint32_t) depth,
std::move(desc));
return 0;
});
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nSetImageCubemap(JNIEnv *env, jclass,
jlong nativeTexture, jlong nativeEngine, jint level, jobject storage, jint remaining,
jint left, jint top, jint type, jint alignment, jint stride, jint format,
jintArray faceOffsetsInBytes_,
jobject handler, jobject runnable) {
Texture *texture = (Texture *) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
jint *faceOffsetsInBytes = env->GetIntArrayElements(faceOffsetsInBytes_, nullptr);
Texture::FaceOffsets faceOffsets;
std::copy_n(faceOffsetsInBytes, 6, faceOffsets.offsets);
env->ReleaseIntArrayElements(faceOffsetsInBytes_, faceOffsetsInBytes, JNI_ABORT);
size_t sizeInBytes = 6 * getTextureDataSize(texture, (size_t) level, (Texture::Format) format,
(Texture::Type) type, (size_t) stride, 0, (size_t) alignment);
AutoBuffer nioBuffer(env, storage, 0);
if (sizeInBytes > (size_t(remaining) << nioBuffer.getShift())) {
// BufferOverflowException
return -1;
}
void *buffer = nioBuffer.getData();
auto *callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer));
Texture::PixelBufferDescriptor desc(buffer, sizeInBytes, (backend::PixelDataFormat) format,
(backend::PixelDataType) type, (uint8_t) alignment, (uint32_t) left, (uint32_t) top,
(uint32_t) stride,
callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback);
return wrapJni<jint>(env, [&]() {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
texture->setImage(*engine, (size_t) level, std::move(desc), faceOffsets);
#pragma clang diagnostic pop
return 0;
});
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nSetImageCubemapCompressed(JNIEnv *env, jclass,
jlong nativeTexture, jlong nativeEngine, jint level, jobject storage, jint remaining,
jint left, jint top, jint type, jint alignment,
jint compressedSizeInBytes, jint compressedFormat, jintArray faceOffsetsInBytes_,
jobject handler, jobject runnable) {
Texture *texture = (Texture *) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
jint *faceOffsetsInBytes = env->GetIntArrayElements(faceOffsetsInBytes_, nullptr);
Texture::FaceOffsets faceOffsets;
std::copy_n(faceOffsetsInBytes, 6, faceOffsets.offsets);
env->ReleaseIntArrayElements(faceOffsetsInBytes_, faceOffsetsInBytes, JNI_ABORT);
size_t sizeInBytes = 6 * (size_t) compressedSizeInBytes;
AutoBuffer nioBuffer(env, storage, 0);
if (sizeInBytes > (size_t(remaining) << nioBuffer.getShift())) {
// BufferOverflowException
return -1;
}
void *buffer = nioBuffer.getData();
auto *callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer));
Texture::PixelBufferDescriptor desc(buffer, sizeInBytes,
(backend::CompressedPixelDataType) compressedFormat, (uint32_t) compressedSizeInBytes,
callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback);
return wrapJni<jint>(env, [&]() {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
texture->setImage(*engine, (size_t) level, std::move(desc), faceOffsets);
#pragma clang diagnostic pop
return 0;
});
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nSetExternalImage(JNIEnv* env, jclass, jlong nativeTexture,
jlong nativeEngine, jlong eglImage) {
Texture *texture = (Texture *) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
wrapJni(env, [=]() {
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;
}
return wrapJni<jboolean>(env, [=]() {
if (engine->getBackend() == Backend::OPENGL) {
// CAVEAT: we assume that Backend::OPENGL on Android implies PlatformEGLAndroid.
#if UTILS_HAS_RTTI
if (!dynamic_cast<PlatformEGLAndroid*>(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<VulkanPlatformAndroid*>(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* env, jclass,
jlong nativeTexture, jlong nativeEngine, jlong nativeStream) {
Texture *texture = (Texture *) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
Stream *stream = (Stream *) nativeStream;
wrapJni(env, [=]() {
texture->setExternalStream(*engine, stream);
});
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Texture_nGenerateMipmaps(JNIEnv* env, jclass,
jlong nativeTexture, jlong nativeEngine) {
Texture *texture = (Texture *) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
wrapJni(env, [=]() {
texture->generateMipmaps(*engine);
});
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Texture_nIsStreamValidForTexture(JNIEnv*, jclass,
jlong nativeTexture, jlong) {
Texture* texture = (Texture*) nativeTexture;
return (jboolean) (texture->getTarget() == SamplerType::SAMPLER_EXTERNAL);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Texture_nGeneratePrefilterMipmap(JNIEnv *env, jclass,
jlong nativeTexture, jlong nativeEngine, jint width, jint height,
jobject storage, jint remaining, jint left,
jint top, jint type, jint alignment, jint stride, jint format,
jintArray faceOffsetsInBytes_, jobject handler, jobject runnable, jint sampleCount,
jboolean mirror) {
Texture *texture = (Texture *) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
jint *faceOffsetsInBytes = env->GetIntArrayElements(faceOffsetsInBytes_, nullptr);
filament::FaceOffsets faceOffsets;
std::copy_n(faceOffsetsInBytes, 6, faceOffsets.offsets);
env->ReleaseIntArrayElements(faceOffsetsInBytes_, faceOffsetsInBytes, JNI_ABORT);
stride = stride ? stride : width;
size_t sizeInBytes = 6 *
Texture::computeTextureDataSize((Texture::Format) format, (Texture::Type) type,
(size_t) stride, (size_t) height, (size_t) alignment);
AutoBuffer nioBuffer(env, storage, 0);
if (sizeInBytes > (size_t(remaining) << nioBuffer.getShift())) {
// BufferOverflowException
return -1;
}
void* buffer = nioBuffer.getData();
auto* callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer));
Texture::PixelBufferDescriptor desc(buffer, sizeInBytes, (backend::PixelDataFormat) format,
(backend::PixelDataType) type, (uint8_t) alignment,
(uint32_t) left, (uint32_t) top, (uint32_t) stride,
callback->getHandler(), &JniBufferCallback::postToJavaAndDestroy, callback);
filament::PrefilterOptions options;
options.sampleCount = sampleCount;
options.mirror = mirror;
return wrapJni<jint>(env, [&]() {
filament::generatePrefilterMipmap(texture, *engine, std::move(desc), faceOffsets, &options);
return 0;
});
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// ANDROID SPECIFIC BITS
////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef __ANDROID__
#define BITMAP_CONFIG_ALPHA_8 0
#define BITMAP_CONFIG_RGB_565 1
#define BITMAP_CONFIG_RGBA_4444 2
#define BITMAP_CONFIG_RGBA_8888 3
#define BITMAP_CONFIG_RGBA_F16 4
#define BITMAP_CONFIG_HARDWARE 5
class AutoBitmap : public JniCallback {
private:
AutoBitmap(JNIEnv* env, jobject bitmap) noexcept
: JniCallback(),
mBitmap(env->NewGlobalRef(bitmap)) {
if (mBitmap) {
AndroidBitmap_getInfo(env, mBitmap, &mInfo);
AndroidBitmap_lockPixels(env, mBitmap, &mData);
}
}
AutoBitmap(JNIEnv* env, jobject bitmap, jobject handler, jobject runnable) noexcept
: JniCallback(env, handler, runnable),
mBitmap(env->NewGlobalRef(bitmap)) {
if (mBitmap) {
AndroidBitmap_getInfo(env, mBitmap, &mInfo);
AndroidBitmap_lockPixels(env, mBitmap, &mData);
}
}
void release(JNIEnv* env) {
if (mBitmap) {
AndroidBitmap_unlockPixels(env, mBitmap);
env->DeleteGlobalRef(mBitmap);
}
}
~AutoBitmap() override = default;
public:
void* getData() const noexcept {
return mData;
}
size_t getSizeInBytes() const noexcept {
return mInfo.height * mInfo.stride;
}
PixelDataFormat getFormat(int format) const noexcept {
// AndroidBitmapInfo does not capture the HARDWARE and RGBA_F16 formats
// so we switch on the Bitmap.Config values directly
switch (format) {
case BITMAP_CONFIG_ALPHA_8: return PixelDataFormat::ALPHA;
case BITMAP_CONFIG_RGB_565: return PixelDataFormat::RGB;
default: return PixelDataFormat::RGBA;
}
}
PixelDataType getType(int format) const noexcept {
switch (format) {
case BITMAP_CONFIG_RGB_565: return PixelDataType::USHORT_565;
case BITMAP_CONFIG_RGBA_F16: return PixelDataType::HALF;
default: return PixelDataType::UBYTE;
}
}
// create a AutoBitmap
static AutoBitmap* make(JNIEnv* env, jobject bitmap, jobject handler, jobject runnable) {
return new AutoBitmap(env, bitmap, handler, runnable);
}
// execute the callback on the java thread and destroy ourselves
static void invoke(void*, size_t, void* user) {
auto* autoBitmap = reinterpret_cast<AutoBitmap*>(user);
JNIEnv* env = filament::VirtualMachineEnv::get().getEnvironment();
releaseCallbackJni(env, autoBitmap->mCallbackUtils, autoBitmap->mHandler, autoBitmap->mCallback);
autoBitmap->release(env);
delete autoBitmap;
}
// create a AutoBitmap without a handler
static AutoBitmap* make(JNIEnv* env, jobject bitmap) {
return new AutoBitmap(env, bitmap);
}
// just destroy ourselves
static void invokeNoCallback(void*, size_t, void* user) {
auto* autoBitmap = reinterpret_cast<AutoBitmap*>(user);
JNIEnv* env = filament::VirtualMachineEnv::get().getEnvironment();
autoBitmap->release(env);
delete autoBitmap;
}
private:
void* mData = nullptr;
jobject mBitmap{};
AndroidBitmapInfo mInfo{};
};
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_android_TextureHelper_nSetBitmap(JNIEnv* env, jclass,
jlong nativeTexture, jlong nativeEngine, jint level, jint xoffset, jint yoffset,
jint width, jint height, jobject bitmap, jint format) {
Texture* texture = (Texture*) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
auto* autoBitmap = AutoBitmap::make(env, bitmap);
Texture::PixelBufferDescriptor desc(
autoBitmap->getData(),
autoBitmap->getSizeInBytes(),
autoBitmap->getFormat(format),
autoBitmap->getType(format),
&AutoBitmap::invokeNoCallback, autoBitmap);
filament::android::wrapJni(env, [&]() {
texture->setImage(*engine, (size_t) level,
(uint32_t) xoffset, (uint32_t) yoffset,
(uint32_t) width, (uint32_t) height,
std::move(desc));
});
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_android_TextureHelper_nSetBitmapWithCallback(JNIEnv* env, jclass,
jlong nativeTexture, jlong nativeEngine, jint level, jint xoffset, jint yoffset,
jint width, jint height, jobject bitmap, jint format, jobject handler, jobject runnable) {
Texture* texture = (Texture*) nativeTexture;
Engine *engine = (Engine *) nativeEngine;
auto* autoBitmap = AutoBitmap::make(env, bitmap, handler, runnable);
Texture::PixelBufferDescriptor desc(
autoBitmap->getData(),
autoBitmap->getSizeInBytes(),
autoBitmap->getFormat(format),
autoBitmap->getType(format),
autoBitmap->getHandler(), &AutoBitmap::invoke, autoBitmap);
filament::android::wrapJni(env, [&]() {
texture->setImage(*engine, (size_t) level,
(uint32_t) xoffset, (uint32_t) yoffset,
(uint32_t) width, (uint32_t) height,
std::move(desc));
});
}
#endif