From 5462cc0fcf8c6e8fdda8eada0c5cfaac455ff21e Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Thu, 31 Oct 2019 16:29:32 -0700 Subject: [PATCH] Renderer::readPixels() can now read back from a RenderTarget Before it was limited to reading back from the SwapChain. --- .../src/main/cpp/Renderer.cpp | 36 +++++++ .../com/google/android/filament/Renderer.java | 94 +++++++++++++++++++ filament/include/filament/Renderer.h | 64 ++++++++++++- filament/src/Renderer.cpp | 20 +++- filament/src/details/Renderer.h | 8 ++ 5 files changed, 220 insertions(+), 2 deletions(-) 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.

+ * + * @param renderTarget {@link 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: + *

  • {@link Texture.Format#RGBA}
  • + *
  • {@link Texture.Format#RGBA_INTEGER}
  • + *

    + * + *

    + * The following types are always supported: + *

  • {@link Texture.Type#UBYTE}
  • + *
  • {@link Texture.Type#UINT}
  • + *
  • {@link Texture.Type#INT}
  • + *
  • {@link Texture.Type#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.

    + * + * @exception BufferOverflowException if the specified parameters would result in reading + * outside of buffer. + */ + 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 renderTargetHandle, + uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, + backend::PixelBufferDescriptor&& buffer) { if (!ASSERT_POSTCONDITION_NON_FATAL( buffer.type != PixelDataType::COMPRESSED, "buffer.format cannot be COMPRESSED")) { @@ -613,7 +624,7 @@ void FRenderer::readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, u FEngine& engine = getEngine(); FEngine::DriverApi& driver = engine.getDriverApi(); - driver.readPixels(mRenderTarget, xoffset, yoffset, width, height, std::move(buffer)); + driver.readPixels(renderTargetHandle, xoffset, yoffset, width, height, std::move(buffer)); } Handle FRenderer::getRenderTarget(FView& view) const noexcept { @@ -672,6 +683,13 @@ void Renderer::readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, ui upcast(this)->readPixels(xoffset, yoffset, width, height, std::move(buffer)); } +void Renderer::readPixels(RenderTarget* renderTarget, + uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, + PixelBufferDescriptor&& buffer) { + upcast(this)->readPixels(upcast(renderTarget), + xoffset, yoffset, width, height, std::move(buffer)); +} + void Renderer::endFrame() { upcast(this)->endFrame(); } diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index 9190236788..61ced5f510 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -83,6 +83,10 @@ public: void readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& buffer); + void readPixels(FRenderTarget* renderTarget, + uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, + backend::PixelBufferDescriptor&& buffer); + // Clean-up everything, this is typically called when the client calls Engine::destroyRenderer() void terminate(FEngine& engine); @@ -92,6 +96,10 @@ private: backend::Handle getRenderTarget(FView& view) const noexcept; + void readPixels(backend::Handle renderTargetHandle, + uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, + backend::PixelBufferDescriptor&& buffer); + RenderPass::CommandTypeFlags getCommandType(View::DepthPrepass prepass) const noexcept; void recordHighWatermark(size_t watermark) noexcept {