diff --git a/android/filament-android/src/main/cpp/Texture.cpp b/android/filament-android/src/main/cpp/Texture.cpp index d2a1cdd20b..68ef60a1d7 100644 --- a/android/filament-android/src/main/cpp/Texture.cpp +++ b/android/filament-android/src/main/cpp/Texture.cpp @@ -233,6 +233,76 @@ Java_com_google_android_filament_Texture_nSetImageCompressed(JNIEnv *env, jclass return 0; } +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 bottom, 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) 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) bottom, + (uint32_t) stride, &JniBufferCallback::invoke, callback); + + 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, + &JniBufferCallback::invoke, callback); + + 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, 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 66daba87a7..450f0a2029 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 @@ -92,7 +92,9 @@ public class Texture { /** Cubemap sampler */ SAMPLER_CUBEMAP, /** External texture sampler */ - SAMPLER_EXTERNAL + SAMPLER_EXTERNAL, + /** 3D sampler */ + SAMPLER_3D, } /** @@ -817,7 +819,7 @@ public class Texture { /** - * setImage is used to modify a sub-region of the texure from a CPU-buffer. + * setImage is used to modify a sub-region of the texture from a CPU-buffer. * *

This Texture instance must use {@link Sampler#SAMPLER_2D SAMPLER_2D} or * {@link Sampler#SAMPLER_EXTERNAL SAMPLER_EXTERNAL}. If the later is specified @@ -868,6 +870,58 @@ public class Texture { } } + /** + * setImage is used to modify a sub-region of the 3D texture or 2D texture array + * from a CPU-buffer. + * + *

This Texture instance must use {@link Sampler#SAMPLER_2D_ARRAY SAMPLER_2D_ARRAY} or + * {@link Sampler#SAMPLER_3D SAMPLER_3D}.

+ * + * @param engine {@link Engine} this texture is associated to. Must be the + * instance passed to {@link Builder#build Builder.build()}. + * @param level Level to set the image for. Must be less than {@link #getLevels()}. + * @param xoffset x-offset in texel of the region to modify + * @param yoffset y-offset in texel of the region to modify + * @param yoffset z-offset in texel of the region to modify + * @param width width in texel of the region to modify + * @param height height in texel of the region to modify + * @param depth depth in texel or index of the region to modify + * @param buffer Client-side buffer containing the image to set. + * buffer's {@link Format format} must match that + * of {@link #getFormat()} + * + * @exception BufferOverflowException if the specified parameters would result in reading + * outside of buffer. + * + * @see Builder#sampler + * @see PixelBufferDescriptor + */ + public void setImage(@NonNull Engine engine, + @IntRange(from = 0) int level, + @IntRange(from = 0) int xoffset, @IntRange(from = 0) int yoffset, @IntRange(from = 0) int zoffset, + @IntRange(from = 0) int width, @IntRange(from = 0) int height, @IntRange(from = 0) int depth, + @NonNull PixelBufferDescriptor buffer) { + int result; + if (buffer.type == COMPRESSED) { + result = nSetImage3DCompressed(getNativeObject(), engine.getNativeObject(), level, + xoffset, yoffset, zoffset, width, height, depth, + buffer.storage, buffer.storage.remaining(), + buffer.left, buffer.top, buffer.type.ordinal(), buffer.alignment, + buffer.compressedSizeInBytes, buffer.compressedFormat.ordinal(), + buffer.handler, buffer.callback); + } else { + result = nSetImage3D(getNativeObject(), engine.getNativeObject(), level, + xoffset, yoffset, zoffset, width, height, depth, + buffer.storage, buffer.storage.remaining(), + buffer.left, buffer.top, buffer.type.ordinal(), buffer.alignment, + buffer.stride, buffer.format.ordinal(), + buffer.handler, buffer.callback); + } + if (result < 0) { + throw new BufferOverflowException(); + } + } + /** * setImage is used to specify all six images of a cubemap level and * follows exactly the OpenGL conventions @@ -1116,6 +1170,18 @@ public class Texture { int compressedSizeInBytes, int compressedFormat, Object handler, Runnable callback); + private static native int nSetImage3D(long nativeTexture, long nativeEngine, + int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, + Buffer storage, int remaining, int left, int bottom, int type, int alignment, + int stride, int format, + Object handler, Runnable callback); + + private static native int nSetImage3DCompressed(long nativeTexture, long nativeEngine, + int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, + Buffer storage, int remaining, int left, int bottom, int type, int alignment, + int compressedSizeInBytes, int compressedFormat, + Object handler, Runnable callback); + private static native int nSetImageCubemap(long nativeTexture, long nativeEngine, int level, Buffer storage, int remaining, int left, int bottom, int type, int alignment, int stride, int format, diff --git a/filament/include/filament/Texture.h b/filament/include/filament/Texture.h index b51a4187e0..afca19800c 100644 --- a/filament/include/filament/Texture.h +++ b/filament/include/filament/Texture.h @@ -129,7 +129,7 @@ public: * effectively create a 3D texture. * @param depth Depth of the texture in texels (default: 1). * @return This Builder, for chaining calls. - * @attention This Texture instance must use Sampler::SAMPLER_2D_ARRAY or it has no effect. + * @attention This Texture instance must use Sampler::SAMPLER_3D or Sampler::SAMPLER_2D_ARRAY or it has no effect. */ Builder& depth(uint32_t depth) noexcept; @@ -305,7 +305,7 @@ public: * * @see Builder::sampler() */ - void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer) const noexcept; + void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer) const; /** * Updates a sub-image of a 2D texture for a level. @@ -331,7 +331,32 @@ public: */ void setImage(Engine& engine, size_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, - PixelBufferDescriptor&& buffer) const noexcept; + PixelBufferDescriptor&& buffer) const; + + /** + * Updates a sub-image of a 3D texture or 2D texture array for a level. + * + * @param engine Engine this texture is associated to. + * @param level Level to set the image for. + * @param xoffset Left offset of the sub-region to update. + * @param yoffset Bottom offset of the sub-region to update. + * @param zoffset Depth offset of the sub-region to update. + * @param width Width of the sub-region to update. + * @param height Height of the sub-region to update. + * @param depth Depth of the sub-region to update. + * @param buffer Client-side buffer containing the image to set. + * + * @attention \p engine must be the instance passed to Builder::build() + * @attention \p level must be less than getLevels(). + * @attention \p buffer's Texture::Format must match that of getFormat(). + * @attention This Texture instance must use Sampler::SAMPLER_3D or Sampler::SAMPLER_2D_array. + * + * @see Builder::sampler() + */ + void setImage(Engine& engine, size_t level, + uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + uint32_t width, uint32_t height, uint32_t depth, + PixelBufferDescriptor&& buffer) const; /** * Specify all six images of a cube map level. @@ -352,7 +377,7 @@ public: * @see Texture::CubemapFace, Builder::sampler() */ void setImage(Engine& engine, size_t level, - PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const noexcept; + PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const; /** diff --git a/filament/src/Texture.cpp b/filament/src/Texture.cpp index 023c35d1a4..f535db4fef 100644 --- a/filament/src/Texture.cpp +++ b/filament/src/Texture.cpp @@ -179,7 +179,19 @@ size_t FTexture::getDepth(size_t level) const noexcept { void FTexture::setImage(FEngine& engine, size_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, - Texture::PixelBufferDescriptor&& buffer) const noexcept { + Texture::PixelBufferDescriptor&& buffer) const { + + auto validateTarget = [](SamplerType sampler) -> bool { + switch (sampler) { + case SamplerType::SAMPLER_2D: + case SamplerType::SAMPLER_EXTERNAL: + return true; + case SamplerType::SAMPLER_CUBEMAP: + case SamplerType::SAMPLER_3D: + case SamplerType::SAMPLER_2D_ARRAY: + return false; + } + }; if (!ASSERT_POSTCONDITION_NON_FATAL(buffer.type == PixelDataType::COMPRESSED || validatePixelFormatAndType(mFormat, buffer.format, buffer.type), @@ -188,16 +200,130 @@ void FTexture::setImage(FEngine& engine, return; } - if (!mStream && mTarget != Sampler::SAMPLER_CUBEMAP && level < mLevelCount) { - if (buffer.buffer) { - engine.getDriverApi().update2DImage(mHandle, - uint8_t(level), xoffset, yoffset, width, height, std::move(buffer)); - } + if (!ASSERT_POSTCONDITION_NON_FATAL(!mStream, "setImage() called on a Stream texture.")) { + return; } + + if (!ASSERT_POSTCONDITION_NON_FATAL(level < mLevelCount, + "level=%u is >= to levelCount=%u.", unsigned(level), unsigned(mLevelCount))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(validateTarget(mTarget), + "Texture Sampler type (%u) not supported for this operation.", unsigned(mTarget))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(buffer.buffer, "Data buffer is nullptr.")) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(mSampleCount <= 1, + "Operation not supported with multisample (%u) texture.", unsigned(mSampleCount))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(xoffset + width <= valueForLevel(level, mWidth), + "xoffset (%u) + width (%u) > texture width (%u) at level (%u)", + unsigned(xoffset), unsigned(width), unsigned(valueForLevel(level, mWidth)), unsigned(level))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(yoffset + height <= valueForLevel(level, mHeight), + "xoffset (%u) + width (%u) > texture width (%u) at level (%u)", + unsigned(yoffset), unsigned(height), unsigned(valueForLevel(level, mHeight)), unsigned(level))) { + return; + } + + engine.getDriverApi().update2DImage(mHandle, + uint8_t(level), xoffset, yoffset, width, height, std::move(buffer)); +} + +void FTexture::setImage(FEngine& engine, + size_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + uint32_t width, uint32_t height, uint32_t depth, + Texture::PixelBufferDescriptor&& buffer) const { + + auto validateTarget = [](SamplerType sampler) -> bool { + switch (sampler) { + case SamplerType::SAMPLER_3D: + case SamplerType::SAMPLER_2D_ARRAY: + return true; + case SamplerType::SAMPLER_2D: + case SamplerType::SAMPLER_EXTERNAL: + case SamplerType::SAMPLER_CUBEMAP: + return false; + } + }; + + if (!ASSERT_POSTCONDITION_NON_FATAL(buffer.type == PixelDataType::COMPRESSED || + validatePixelFormatAndType(mFormat, buffer.format, buffer.type), + "The combination of internal format=%u and {format=%u, type=%u} is not supported.", + unsigned(mFormat), unsigned(buffer.format), unsigned(buffer.type))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(!mStream, "setImage() called on a Stream texture.")) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(level < mLevelCount, + "level=%u is >= to levelCount=%u.", unsigned(level), unsigned(mLevelCount))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(validateTarget(mTarget), + "Texture Sampler type (%u) not supported for this operation.", unsigned(mTarget))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(mSampleCount <= 1, + "Operation not supported with multisample (%u) texture.", unsigned(mSampleCount))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(xoffset + width <= valueForLevel(level, mWidth), + "xoffset (%u) + width (%u) > texture width (%u) at level (%u)", + unsigned(xoffset), unsigned(width), unsigned(valueForLevel(level, mWidth)), unsigned(level))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(yoffset + height <= valueForLevel(level, mHeight), + "yoffset (%u) + height (%u) > texture height (%u) at level (%u)", + unsigned(yoffset), unsigned(height), unsigned(valueForLevel(level, mHeight)), unsigned(level))) { + return; + } + + // effective level is just how we compute the index/depth based on whether we're an array or a 3D texture + const uint8_t effectiveLevel = mTarget == SamplerType::SAMPLER_3D ? level : 0; + if (!ASSERT_POSTCONDITION_NON_FATAL(zoffset + depth <= valueForLevel(effectiveLevel, mDepth), + "zoffset (%u) + depth (%u) > texture depth (%u) at level (%u)", + unsigned(zoffset), unsigned(depth), unsigned(valueForLevel(effectiveLevel, mDepth)), unsigned(level))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(buffer.buffer, "Data buffer is nullptr.")) { + return; + } + + engine.getDriverApi().update3DImage(mHandle, + uint8_t(level), xoffset, yoffset, zoffset, width, height, depth, std::move(buffer)); } void FTexture::setImage(FEngine& engine, size_t level, - Texture::PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const noexcept { + Texture::PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const { + + auto validateTarget = [](SamplerType sampler) -> bool { + switch (sampler) { + case SamplerType::SAMPLER_CUBEMAP: + return true; + case SamplerType::SAMPLER_3D: + case SamplerType::SAMPLER_2D_ARRAY: + case SamplerType::SAMPLER_2D: + case SamplerType::SAMPLER_EXTERNAL: + return false; + } + }; if (!ASSERT_POSTCONDITION_NON_FATAL(buffer.type == PixelDataType::COMPRESSED || validatePixelFormatAndType(mFormat, buffer.format, buffer.type), @@ -206,12 +332,26 @@ void FTexture::setImage(FEngine& engine, size_t level, return; } - if (!mStream && mTarget == Sampler::SAMPLER_CUBEMAP && level < mLevelCount) { - if (buffer.buffer) { - engine.getDriverApi().updateCubeImage(mHandle, uint8_t(level), - std::move(buffer), faceOffsets); - } + if (!ASSERT_POSTCONDITION_NON_FATAL(!mStream, "setImage() called on a Stream texture.")) { + return; } + + if (!ASSERT_POSTCONDITION_NON_FATAL(level < mLevelCount, + "level=%u is >= to levelCount=%u.", unsigned(level), unsigned(mLevelCount))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(validateTarget(mTarget), + "Texture Sampler type (%u) not supported for this operation.", unsigned(mTarget))) { + return; + } + + if (!ASSERT_POSTCONDITION_NON_FATAL(buffer.buffer, "Data buffer is nullptr.")) { + return; + } + + engine.getDriverApi().updateCubeImage(mHandle, uint8_t(level), + std::move(buffer), faceOffsets); } void FTexture::setExternalImage(FEngine& engine, void* image) noexcept { @@ -904,20 +1044,29 @@ Texture::InternalFormat Texture::getFormat() const noexcept { } void Texture::setImage(Engine& engine, size_t level, - Texture::PixelBufferDescriptor&& buffer) const noexcept { + Texture::PixelBufferDescriptor&& buffer) const { upcast(this)->setImage(upcast(engine), - level, 0, 0, uint32_t(getWidth(level)), uint32_t(getHeight(level)), std::move(buffer)); + level, 0, 0, + uint32_t(getWidth(level)), uint32_t(getHeight(level)), std::move(buffer)); } void Texture::setImage(Engine& engine, size_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, - PixelBufferDescriptor&& buffer) const noexcept { + PixelBufferDescriptor&& buffer) const { upcast(this)->setImage(upcast(engine), level, xoffset, yoffset, width, height, std::move(buffer)); } void Texture::setImage(Engine& engine, size_t level, - Texture::PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const noexcept { + uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + uint32_t width, uint32_t height, uint32_t depth, + PixelBufferDescriptor&& buffer) const { + upcast(this)->setImage(upcast(engine), + level, xoffset, yoffset, zoffset, width, height, depth, std::move(buffer)); +} + +void Texture::setImage(Engine& engine, size_t level, + Texture::PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const { upcast(this)->setImage(upcast(engine), level, std::move(buffer), faceOffsets); } diff --git a/filament/src/details/Texture.h b/filament/src/details/Texture.h index a5a01a9abd..1e9f6e9005 100644 --- a/filament/src/details/Texture.h +++ b/filament/src/details/Texture.h @@ -50,10 +50,15 @@ public: void setImage(FEngine& engine, size_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, - PixelBufferDescriptor&& buffer) const noexcept; + PixelBufferDescriptor&& buffer) const; void setImage(FEngine& engine, size_t level, - PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const noexcept; + uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, + uint32_t width, uint32_t height, uint32_t depth, + PixelBufferDescriptor&& buffer) const; + + void setImage(FEngine& engine, size_t level, + PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const; void generatePrefilterMipmap(FEngine& engine, PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets,