diff --git a/android/filament-android/src/main/cpp/Renderer.cpp b/android/filament-android/src/main/cpp/Renderer.cpp index c0a51c7f8d..1cb292c6d0 100644 --- a/android/filament-android/src/main/cpp/Renderer.cpp +++ b/android/filament-android/src/main/cpp/Renderer.cpp @@ -97,6 +97,42 @@ Java_com_google_android_filament_Renderer_nReadPixels(JNIEnv *env, jclass, return 0; } +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_Renderer_nReadPixelsEx(JNIEnv *env, jclass, + jlong nativeRenderer, jlong nativeEngine, jlong nativeRenderTarget, + jint xoffset, jint yoffset, jint width, jint height, + jobject storage, jint remaining, + jint left, jint top, jint type, jint alignment, jint stride, jint format, + jobject handler, jobject runnable) { + Renderer *renderer = (Renderer *) nativeRenderer; + Engine *engine = (Engine *) nativeEngine; + RenderTarget *renderTarget = (RenderTarget *) nativeRenderTarget; + + stride = stride ? stride : width; + size_t sizeInBytes = PixelBufferDescriptor::computeDataSize( + (PixelDataFormat) format, (PixelDataType) type, + (size_t) stride, (size_t) (height + top), (size_t) alignment); + + AutoBuffer nioBuffer(env, storage, 0); + if (sizeInBytes > (remaining << nioBuffer.getShift())) { + // BufferOverflowException + return -1; + } + + void *buffer = nioBuffer.getData(); + auto *callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer)); + + PixelBufferDescriptor desc(buffer, sizeInBytes, (backend::PixelDataFormat) format, + (backend::PixelDataType) type, (uint8_t) alignment, (uint32_t) left, (uint32_t) top, + (uint32_t) stride, &JniBufferCallback::invoke, callback); + + renderer->readPixels(renderTarget, + uint32_t(xoffset), uint32_t(yoffset), uint32_t(width), uint32_t(height), + std::move(desc)); + + return 0; +} + extern "C" JNIEXPORT jdouble JNICALL Java_com_google_android_filament_Renderer_nGetUserTime(JNIEnv*, jclass, jlong nativeRenderer) { Renderer *renderer = (Renderer *) nativeRenderer; diff --git a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java index c110b8afda..06d344273e 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java @@ -299,6 +299,94 @@ public class Renderer { } } + /** + * Reads back the content of a specified {@link RenderTarget}. + * + *
+ * + * Framebuffer as seen on User buffer (PixelBufferDescriptor) + * screen + * +--------------------+ + * | | .stride .alignment + * | | ----------------------->--> + * | | O----------------------+--+ low addresses + * | | | | | | + * | w | | | .top | | + * | <---------> | | V | | + * | +---------+ | | +---------+ | | + * | | ^ | | ======> | | | | | + * | x | h| | | |.left| | | | + * +------>| v | | +---->| | | | + * | +.........+ | | +.........+ | | + * | ^ | | | | + * | y | | +----------------------+--+ high addresses + * O------------+-------+ + * + *+ * + * + *
Typically readPixels will be called after {@link #render} and before
+ * {@link #endFrame}.
After calling this method, the callback associated with buffer
+ * will be invoked on the main thread, indicating that the read-back has completed.
+ * Typically, this will happen after multiple calls to {@link #beginFrame},
+ * {@link #render}, {@link #endFrame}.
readPixels is intended for debugging and testing.
+ * It will impact performance significantly.
+ * The following format are always supported: + *
+ * The following types are always supported: + *
Other combination of format/type may be supported. If a combination is + * not supported, this operation may fail silently. Use a DEBUG build + * to get some logs about the failure.
+ * + * @exception BufferOverflowException if the specified parameters would result in reading + * outside ofbuffer.
+ */
+ public void readPixels(
+ @NonNull RenderTarget renderTarget,
+ @IntRange(from = 0) int xoffset, @IntRange(from = 0) int yoffset,
+ @IntRange(from = 0) int width, @IntRange(from = 0) int height,
+ @NonNull Texture.PixelBufferDescriptor buffer) {
+
+ if (buffer.storage.isReadOnly()) {
+ throw new ReadOnlyBufferException();
+ }
+
+ int result = nReadPixelsEx(getNativeObject(), mEngine.getNativeObject(),
+ renderTarget.getNativeObject(),
+ xoffset, yoffset, width, height,
+ 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();
+ }
+ }
+
/**
* Returns a timestamp (in seconds) for the last call to {@link #beginFrame}. This value is
* constant for all {@link View views} rendered during a frame. The epoch is set with
@@ -386,6 +474,12 @@ public class Renderer {
Buffer storage, int remaining,
int left, int top, int type, int alignment, int stride, int format,
Object handler, Runnable callback);
+ private static native int nReadPixelsEx(long nativeRenderer, long nativeEngine,
+ long nativeRenderTarget,
+ int xoffset, int yoffset, int width, int height,
+ Buffer storage, int remaining,
+ int left, int top, int type, int alignment, int stride, int format,
+ Object handler, Runnable callback);
private static native double nGetUserTime(long nativeRenderer);
private static native void nResetUserTime(long nativeRenderer);
}
diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h
index cc3ea38dad..f9c9811fcc 100644
--- a/filament/include/filament/Renderer.h
+++ b/filament/include/filament/Renderer.h
@@ -31,6 +31,7 @@
namespace filament {
class Engine;
+class RenderTarget;
class SwapChain;
class View;
@@ -190,7 +191,7 @@ public:
Viewport const& srcViewport, uint32_t flags = 0);
/**
- * Read-back the content of the SwapChain associated with this Renderer.
+ * Reads back the content of the SwapChain associated with this Renderer.
*
* @param xoffset Left offset of the sub-region to read back.
* @param yoffset Bottom offset of the sub-region to read back.
@@ -247,6 +248,67 @@ public:
void readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer);
+
+ /**
+ * Reads back the content of the provided RenderTarget.
+ *
+ * @param renderTarget RenderTarget to read back from.
+ * @param xoffset Left offset of the sub-region to read back.
+ * @param yoffset Bottom offset of the sub-region to read back.
+ * @param width Width of the sub-region to read back.
+ * @param height Height of the sub-region to read back.
+ * @param buffer Client-side buffer where the read-back will be written.
+ *
+ * The following format are always supported:
+ * - PixelBufferDescriptor::PixelDataFormat::RGBA
+ * - PixelBufferDescriptor::PixelDataFormat::RGBA_INTEGER
+ *
+ * The following types are always supported:
+ * - PixelBufferDescriptor::PixelDataType::UBYTE
+ * - PixelBufferDescriptor::PixelDataType::UINT
+ * - PixelBufferDescriptor::PixelDataType::INT
+ * - PixelBufferDescriptor::PixelDataType::FLOAT
+ *
+ * Other combination of format/type may be supported. If a combination is
+ * not supported, this operation may fail silently. Use a DEBUG build
+ * to get some logs about the failure.
+ *
+ *
+ * Framebuffer as seen on User buffer (PixelBufferDescriptor&)
+ * screen
+ * +--------------------+
+ * | | .stride .alignment
+ * | | ----------------------->-->
+ * | | O----------------------+--+ low addresses
+ * | | | | | |
+ * | w | | | .top | |
+ * | <---------> | | V | |
+ * | +---------+ | | +---------+ | |
+ * | | ^ | | ======> | | | | |
+ * | x | h| | | |.left| | | |
+ * +------>| v | | +---->| | | |
+ * | +.........+ | | +.........+ | |
+ * | ^ | | | |
+ * | y | | +----------------------+--+ high addresses
+ * O------------+-------+
+ *
+ *
+ * Typically readPixels() will be called after render() and before endFrame().
+ *
+ * After issuing this method, the callback associated with `buffer` will be invoked on the
+ * main thread, indicating that the read-back has completed. Typically, this will happen
+ * after multiple calls to beginFrame(), render(), endFrame().
+ *
+ * It is also possible to use a Fence to wait for the read-back.
+ *
+ * @remark
+ * readPixels() is intended for debugging and testing. It will impact performance significantly.
+ *
+ */
+ void readPixels(RenderTarget* renderTarget,
+ uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
+ backend::PixelBufferDescriptor&& buffer);
+
/**
* Set-up a frame for this Renderer.
*
diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp
index 9f0216fd27..98b49b95e3 100644
--- a/filament/src/Renderer.cpp
+++ b/filament/src/Renderer.cpp
@@ -581,7 +581,18 @@ void FRenderer::endFrame() {
void FRenderer::readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
PixelBufferDescriptor&& buffer) {
+ readPixels(mRenderTarget, xoffset, yoffset, width, height, std::move(buffer));
+}
+void FRenderer::readPixels(FRenderTarget* renderTarget,
+ uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
+ backend::PixelBufferDescriptor&& buffer) {
+ readPixels(renderTarget->getHwHandle(), xoffset, yoffset, width, height, std::move(buffer));
+}
+
+void FRenderer::readPixels(Handle