From f7eb55f2f557da63b0ed65540a48741def127561 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Tue, 13 Apr 2021 11:00:19 -0700 Subject: [PATCH] NEW API: Renderer can process standalone View New Api: - Renderer::renderStandaloneView() is a new method that can be used outside of beginFrame/endFrame on Views that have a RenderTarget associated. This can be used as a poor man's compute API. Improvements to Renderer: - more aggressively skip lighting/shadowing code that won't be needed by the current view - Allow up to 64 KiB to be allocated from the render stream, up from 1 KiB --- RELEASE_NOTES.md | 24 +- .../src/main/cpp/Renderer.cpp | 8 + .../com/google/android/filament/Renderer.java | 52 +++- filament/include/filament/Renderer.h | 247 ++++++++---------- filament/src/Engine.cpp | 2 +- filament/src/Renderer.cpp | 87 ++++-- filament/src/Scene.cpp | 13 +- filament/src/View.cpp | 75 +++--- filament/src/details/Renderer.h | 18 +- web/filament-js/jsbindings.cpp | 1 + 10 files changed, 303 insertions(+), 224 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b49f14b1c3..028c61092f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,21 +5,23 @@ A new header is inserted each time a *tag* is created. ## Next release (main branch) +- engine: Add new `Renderer::renderStandaloneView()` API. + ## v1.9.21 ## v1.9.20 -Android: Fix VSM. -engine: Introduce BufferObject API. -engine: Add new isTextureSwizzleSupported API on Texture. -engine: Add support to Metal and Vulkan backends for texture swizzling. -engine: Add new DoF settings (native/half res, gather kernel ring counts, CoC radius clamp). -engine: DoF quality and performance improvements. -engine: Fix high-quality upsampling issue with SSAO. -Java: Expose `TransformManager.getParent(int)`. -samples: Add Metal and Vulkan backend support to Suzanne sample. -WebGL: expose fitIntoUnitCube to JS. -WebGL: support for multiple elements. +- Android: Fix VSM. +- engine: Introduce BufferObject API. +- engine: Add new isTextureSwizzleSupported API on Texture. +- engine: Add support to Metal and Vulkan backends for texture swizzling. +- engine: Add new DoF settings (native/half res, gather kernel ring counts, CoC radius clamp). +- engine: DoF quality and performance improvements. +- engine: Fix high-quality upsampling issue with SSAO. +- Java: Expose `TransformManager.getParent(int)`. +- samples: Add Metal and Vulkan backend support to Suzanne sample. +- WebGL: expose fitIntoUnitCube to JS. +- WebGL: support for multiple `` elements. ## v1.9.19 diff --git a/android/filament-android/src/main/cpp/Renderer.cpp b/android/filament-android/src/main/cpp/Renderer.cpp index b140b50a5d..f20b7a0bcf 100644 --- a/android/filament-android/src/main/cpp/Renderer.cpp +++ b/android/filament-android/src/main/cpp/Renderer.cpp @@ -50,6 +50,14 @@ Java_com_google_android_filament_Renderer_nRender(JNIEnv *, jclass, jlong native renderer->render(view); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_Renderer_nRenderStandaloneView(JNIEnv *, jclass, jlong nativeRenderer, + jlong nativeView) { + Renderer *renderer = (Renderer *) nativeRenderer; + View *view = (View *) nativeView; + renderer->renderStandaloneView(view); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Renderer_nCopyFrame(JNIEnv *, jclass, jlong nativeRenderer, jlong nativeDstSwapChain, 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 5a4aae7b4f..b4c456c8d3 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 @@ -315,7 +315,7 @@ public class Renderer { *
  • Color pass
  • *
  • Post-processing pass
  • * - * + *

    * A typical render loop looks like this: * *

    @@ -331,31 +331,58 @@ public class Renderer {
          * 
    * *
      - *
    • render() must be called after {@link #beginFrame} and before - *{@link #endFrame}.
    • + *
    • render() must be called after {@link #beginFrame} and before + * {@link #endFrame}.
    • * - *
    • render() must be called from the {@link Engine}'s main thread - *(or external synchronization must be provided). In particular, calls to render() - *on different Renderer instances must be synchronized.
    • + *
    • render() must be called from the {@link Engine}'s main thread + * (or external synchronization must be provided). In particular, calls to render() + * on different Renderer instances must be synchronized.
    • * - *
    • render() performs potentially heavy computations and cannot be multi-threaded. - *However, internally, it is highly multi-threaded to both improve performance and mitigate - *the call's latency.
    • + *
    • render() performs potentially heavy computations and cannot be multi-threaded. + * However, internally, it is highly multi-threaded to both improve performance and mitigate + * the call's latency.
    • * - *
    • render() is typically called once per frame (but not necessarily).
    • + *
    • render() is typically called once per frame (but not necessarily).
    • *
    * * @param view the {@link View} to render - * * @see #beginFrame * @see #endFrame * @see View - * */ public void render(@NonNull View view) { nRender(getNativeObject(), view.getNativeObject()); } + /** + * Renders a standalone {@link View} into its associated RenderTarget. + * + *

    + * This call is mostly equivalent to calling {@link #render(View)} inside a + * {@link #beginFrame} / {@link #endFrame} block, but incurs less overhead. It can be used + * as a poor man's compute API. + *

    + * + *
      + *
    • renderStandaloneView() must be called outside of + * {@link #beginFrame} / {@link #endFrame}.
    • + * + *
    • renderStandaloneView() must be called from the {@link Engine}'s main thread + * (or external synchronization must be provided). In particular, calls to + * renderStandaloneView() on different Renderer instances must + * be synchronized.
    • + * + *
    • renderStandaloneView() performs potentially heavy computations and cannot be + * multi-threaded. However, internally, it is highly multi-threaded to both improve performance + * and mitigate the call's latency.
    • + * + * @param view the {@link View} to render. This View must have an associated {@link RenderTarget} + * @see View + */ + public void renderStandaloneView(@NonNull View view) { + nRenderStandaloneView(getNativeObject(), view.getNativeObject()); + } + /** * Copies the currently rendered {@link View} to the indicated {@link SwapChain}, using the * indicated source and destination rectangle. @@ -635,6 +662,7 @@ public class Renderer { private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos); private static native void nEndFrame(long nativeRenderer); private static native void nRender(long nativeRenderer, long nativeView); + private static native void nRenderStandaloneView(long nativeRenderer, long nativeView); private static native void nCopyFrame(long nativeRenderer, long nativeDstSwapChain, int dstLeft, int dstBottom, int dstWidth, int dstHeight, int srcLeft, int srcBottom, int srcWidth, int srcHeight, diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h index 12fa0d9ee0..ee2470e0c8 100644 --- a/filament/include/filament/Renderer.h +++ b/filament/include/filament/Renderer.h @@ -168,6 +168,90 @@ public: return const_cast(this)->getEngine(); } + /** + * Flags used to configure the behavior of copyFrame(). + * + * @see + * copyFrame() + */ + using CopyFrameFlag = uint32_t; + + /** + * Indicates that the dstSwapChain passed into copyFrame() should be + * committed after the frame has been copied. + * + * @see + * copyFrame() + */ + static constexpr CopyFrameFlag COMMIT = 0x1; + /** + * Indicates that the presentation time should be set on the dstSwapChain + * passed into copyFrame to the monotonic clock time when the frame is + * copied. + * + * @see + * copyFrame() + */ + static constexpr CopyFrameFlag SET_PRESENTATION_TIME = 0x2; + /** + * Indicates that the dstSwapChain passed into copyFrame() should be + * cleared to black before the frame is copied into the specified viewport. + * + * @see + * copyFrame() + */ + static constexpr CopyFrameFlag CLEAR = 0x4; + + + /** + * Set-up a frame for this Renderer. + * + * beginFrame() manages frame pacing, and returns whether or not a frame should be drawn. The + * goal of this is to skip frames when the GPU falls behind in order to keep the frame + * latency low. + * + * If a given frame takes too much time in the GPU, the CPU will get ahead of the GPU. The + * display will draw the same frame twice producing a stutter. At this point, the CPU is + * ahead of the GPU and depending on how many frames are buffered, latency increases. + * + * beginFrame() attempts to detect this situation and returns false in that case, indicating + * to the caller to skip the current frame. + * + * When beginFrame() returns true, it is mandatory to render the frame and call endFrame(). + * However, when beginFrame() returns false, the caller has the choice to either skip the + * frame and not call endFrame(), or proceed as though true was returned. + * + * @param vsyncSteadyClockTimeNano The time in nanosecond of when the current frame started, + * or 0 if unknown. This value should be the timestamp of + * the last h/w vsync. It is expressed in the + * std::chrono::steady_clock time base. + * @param swapChain A pointer to the SwapChain instance to use. + * + * @return + * *false* the current frame should be skipped, + * *true* the current frame must be drawn and endFrame() must be called. + * + * @remark + * When skipping a frame, the whole frame is canceled, and endFrame() must not be called. + * + * @note + * All calls to render() must happen *after* beginFrame(). + * + * @see + * endFrame() + */ + bool beginFrame(SwapChain* swapChain, + uint64_t vsyncSteadyClockTimeNano = 0u); + + /** + * @deprecated, use SwapChain::setFrameScheduledCallback to set the callback instead. + * @see beginFrame(SwapChain*, uint64_t) + */ + UTILS_DEPRECATED + bool beginFrame(SwapChain* swapChain, + uint64_t vsyncSteadyClockTimeNano, + backend::FrameScheduledCallback callback, void* user = nullptr); + /** * Render a View into this renderer's window. * @@ -224,40 +308,6 @@ public: */ void render(View const* view); - /** - * Flags used to configure the behavior of copyFrame(). - * - * @see - * copyFrame() - */ - using CopyFrameFlag = uint32_t; - - /** - * Indicates that the dstSwapChain passed into copyFrame() should be - * committed after the frame has been copied. - * - * @see - * copyFrame() - */ - static constexpr CopyFrameFlag COMMIT = 0x1; - /** - * Indicates that the presentation time should be set on the dstSwapChain - * passed into copyFrame to the monotonic clock time when the frame is - * copied. - * - * @see - * copyFrame() - */ - static constexpr CopyFrameFlag SET_PRESENTATION_TIME = 0x2; - /** - * Indicates that the dstSwapChain passed into copyFrame() should be - * cleared to black before the frame is copied into the specified viewport. - * - * @see - * copyFrame() - */ - static constexpr CopyFrameFlag CLEAR = 0x4; - /** * Copy the currently rendered view to the indicated swap chain, using the * indicated source and destination rectangle. @@ -332,6 +382,21 @@ public: void readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& buffer); + /** + * Finishes the current frame and schedules it for display. + * + * endFrame() schedules the current frame to be displayed on the Renderer's window. + * + * @note + * All calls to render() must happen *before* endFrame(). endFrame() must be called if + * beginFrame() returned true, otherwise, endFrame() must not be called unless the caller + * ignored beginFrame()'s return value. + * + * @see + * beginFrame() + */ + void endFrame(); + /** * Reads back the content of the provided RenderTarget. @@ -394,112 +459,30 @@ public: backend::PixelBufferDescriptor&& buffer); /** - * Set-up a frame for this Renderer. + * Render a standalone View into its associated RenderTarget * - * beginFrame() manages frame pacing, and returns whether or not a frame should be drawn. The - * goal of this is to skip frames when the GPU falls behind in order to keep the frame - * latency low. + * This call is mostly equivalent to calling render(View*) inside a + * beginFrame / endFrame block, but incurs less overhead. It can be used + * as a poor man's compute API. * - * If a given frame takes too much time in the GPU, the CPU will get ahead of the GPU. The - * display will draw the same frame twice producing a stutter. At this point, the CPU is - * ahead of the GPU and depending on how many frames are buffered, latency increases. + * @param view A pointer to the view to render. This View must have a RenderTarget associated + * to it. * - * beginFrame() attempts to detect this situation and returns false in that case, indicating - * to the caller to skip the current frame. + * @attention + * renderStandaloneView() must be called outside of beginFrame() / endFrame(). * - * When beginFrame() returns true, it is mandatory to render the frame and call endFrame(). - * However, when beginFrame() returns false, the caller has the choice to either skip the - * frame and not call endFrame(), or proceed as though true was returned. - * - * @param vsyncSteadyClockTimeNano The time in nanosecond of when the current frame started, - * or 0 if unknown. This value should be the timestamp of - * the last h/w vsync. It is expressed in the - * std::chrono::steady_clock time base. - * @param swapChain A pointer to the SwapChain instance to use. - * - * @return - * *false* the current frame should be skipped, - * *true* the current frame must be drawn and endFrame() must be called. + * @note + * renderStandaloneView() must be called from the Engine's main thread + * (or external synchronization must be provided). In particular, calls to + * renderStandaloneView() on different Renderer instances **must** be synchronized. * * @remark - * When skipping a frame, the whole frame is canceled, and endFrame() must not be called. - * - * @note - * All calls to render() must happen *after* beginFrame(). - * - * @see - * endFrame() + * renderStandaloneView() perform potentially heavy computations and cannot be multi-threaded. + * However, internally, renderStandaloneView() is highly multi-threaded to both improve + * performance in mitigate the call's latency. */ - bool beginFrame(SwapChain* swapChain, - uint64_t vsyncSteadyClockTimeNano = 0u); + void renderStandaloneView(View const* view); - /** - * Set-up a frame for this Renderer. - * - * beginFrame() manages frame pacing, and returns whether or not a frame should be drawn. The - * goal of this is to skip frames when the GPU falls behind in order to keep the frame - * latency low. - * - * If a given frame takes too much time in the GPU, the CPU will get ahead of the GPU. The - * display will draw the same frame twice producing a stutter. At this point, the CPU is - * ahead of the GPU and depending on how many frames are buffered, latency increases. - * - * beginFrame() attempts to detect this situation and returns false in that case, indicating - * to the caller to skip the current frame. - * - * When beginFrame() returns true, it is mandatory to render the frame and call endFrame(). - * However, when beginFrame() returns false, the caller has the choice to either skip the - * frame and not call endFrame(), or proceed as though true was returned. - * - * Typically, Filament is responsible for scheduling the frame's presentation to the SwapChain. - * If a backend::FrameScheduledCallback is provided, however, the application bares the - * responsibility of scheduling a frame for presentation by calling the backend::PresentCallable - * passed to the callback function. Currently this functionality is only supported by the Metal - * backend. - * - * @param vsyncSteadyClockTimeNano The time in nanosecond of when the current frame started, - * or 0 if unknown. This value should be the timestamp of - * the last h/w vsync. It is expressed in the - * std::chrono::steady_clock time base. - * @param swapChain A pointer to the SwapChain instance to use. - * @param callback A callback function that will be called when the backend has finished - * processing the frame. - * @param user User data to be passed to the callback function. - * - * @return - * *false* the current frame should be skipped, - * *true* the current frame must be drawn and endFrame() must be called. - * - * @remark - * When skipping a frame, the whole frame is canceled, and endFrame() must not be called. - * - * @note - * All calls to render() must happen *after* beginFrame(). - * - * @deprecated, use SwapChain::setFrameScheduledCallback to set the callback instead. - * - * @see - * endFrame(), backend::PresentCallable, backend::FrameFinishedCallback - */ - UTILS_DEPRECATED - bool beginFrame(SwapChain* swapChain, - uint64_t vsyncSteadyClockTimeNano, - backend::FrameScheduledCallback callback, void* user = nullptr); - - /** - * Finishes the current frame and schedules it for display. - * - * endFrame() schedules the current frame to be displayed on the Renderer's window. - * - * @note - * All calls to render() must happen *before* endFrame(). endFrame() must be called if - * beginFrame() returned true, otherwise, endFrame() must not be called unless the caller - * ignored beginFrame()'s return value. - * - * @see - * beginFrame() - */ - void endFrame(); /** * Returns the time in second of the last call to beginFrame(). This value is constant for all diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 0bd5381693..8ffdfc5f1b 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -824,7 +824,7 @@ void FEngine::destroy(Entity e) { void* FEngine::streamAlloc(size_t size, size_t alignment) noexcept { // we allow this only for small allocations - if (size > 1024) { + if (size > 65536) { return nullptr; } return getDriverApi().allocate(size, alignment); diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index 2e46ec0260..45140a6f03 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -155,6 +155,31 @@ TextureFormat FRenderer::getLdrFormat(bool translucent) const noexcept { return (translucent || !mIsRGB8Supported) ? TextureFormat::RGBA8 : TextureFormat::RGB8; } +void FRenderer::renderStandaloneView(FView const* view) { + SYSTRACE_CALL(); + + using namespace std::chrono; + + ASSERT_PRECONDITION(view->getRenderTarget(), + "View \"%s\" must have a RenderTarget associated", view->getName()); + + if (UTILS_LIKELY(view->getScene())) { + mPreviousRenderTargets.clear(); + mFrameId++; + + // ask the engine to do what it needs to (e.g. updates light buffer, materials...) + FEngine& engine = getEngine(); + engine.prepare(); + + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.beginFrame(steady_clock::now().time_since_epoch().count(), mFrameId); + + renderInternal(view); + + driver.endFrame(mFrameId); + } +} + void FRenderer::render(FView const* view) { SYSTRACE_CALL(); @@ -166,26 +191,30 @@ void FRenderer::render(FView const* view) { } if (UTILS_LIKELY(view && view->getScene())) { - // per-renderpass data - ArenaScope rootArena(mPerRenderPassArena); - - FEngine& engine = mEngine; - JobSystem& js = engine.getJobSystem(); - - // create a root job so no other job can escape - auto *rootJob = js.setRootJob(js.createJob()); - - // execute the render pass - renderJob(rootArena, const_cast(*view)); - - // make sure to flush the command buffer - engine.flush(); - - // and wait for all jobs to finish as a safety (this should be a no-op) - js.runAndWait(rootJob); + renderInternal(view); } } +void FRenderer::renderInternal(FView const* view) { + // per-renderpass data + ArenaScope rootArena(mPerRenderPassArena); + + FEngine& engine = mEngine; + JobSystem& js = engine.getJobSystem(); + + // create a root job so no other job can escape + auto *rootJob = js.setRootJob(js.createJob()); + + // execute the render pass + renderJob(rootArena, const_cast(*view)); + + // make sure to flush the command buffer + engine.flush(); + + // and wait for all jobs to finish as a safety (this should be a no-op) + js.runAndWait(rootJob); +} + void FRenderer::renderJob(ArenaScope& arena, FView& view) { FEngine& engine = getEngine(); JobSystem& js = engine.getJobSystem(); @@ -236,8 +265,11 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { view.prepare(engine, driver, arena, svp, getShaderUserTime()); // start froxelization immediately, it has no dependencies - JobSystem::Job* jobFroxelize = js.runAndRetain(js.createJob(nullptr, - [&engine, &view](JobSystem&, JobSystem::Job*) { view.froxelize(engine); })); + JobSystem::Job* jobFroxelize = nullptr; + if (view.hasDynamicLighting()) { + jobFroxelize = js.runAndRetain(js.createJob(nullptr, + [&engine, &view](JobSystem&, JobSystem::Job*) { view.froxelize(engine); })); + } /* * Allocate command buffer @@ -305,7 +337,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { const bool blending = !hasCustomRenderTarget && blendModeTranslucent; // If the swapchain is transparent or if we blend into it, we need to allocate our intermediate // buffers with an alpha channel. - const bool needsAlphaChannel = mSwapChain->isTransparent() || blendModeTranslucent; + const bool needsAlphaChannel = (mSwapChain ? mSwapChain->isTransparent() : false) || blendModeTranslucent; const TextureFormat hdrFormat = getHdrFormat(view, needsAlphaChannel); const ColorPassConfig config{ @@ -866,7 +898,7 @@ void FRenderer::copyFrame(FSwapChain* dstSwapChain, filament::Viewport const& ds } bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano, - backend::FrameScheduledCallback callback, void* user) { + FrameScheduledCallback callback, void* user) { assert_invariant(swapChain); SYSTRACE_CALL(); @@ -922,7 +954,8 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN FEngine& engine = getEngine(); FEngine::DriverApi& driver = engine.getDriverApi(); - if (callback) { + if (UTILS_UNLIKELY(callback)) { + // this is here just to handle the deprecated version of beginFrame() driver.setFrameScheduledCallback(swapChain->getHwHandle(), callback, user); } driver.beginFrame(appVsync.time_since_epoch().count(), mFrameId); @@ -1125,12 +1158,14 @@ void Renderer::render(View const* view) { } bool Renderer::beginFrame(SwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) { - return upcast(this)->beginFrame(upcast(swapChain), vsyncSteadyClockTimeNano, nullptr, nullptr); + return upcast(this)->beginFrame(upcast(swapChain), vsyncSteadyClockTimeNano, + nullptr, nullptr); } bool Renderer::beginFrame(SwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano, backend::FrameScheduledCallback callback, void* user) { - return upcast(this)->beginFrame(upcast(swapChain), vsyncSteadyClockTimeNano, callback, user); + return upcast(this)->beginFrame(upcast(swapChain), vsyncSteadyClockTimeNano, + callback, user); } void Renderer::copyFrame(SwapChain* dstSwapChain, filament::Viewport const& dstViewport, @@ -1174,4 +1209,8 @@ void Renderer::setClearOptions(const ClearOptions& options) { upcast(this)->setClearOptions(options); } +void Renderer::renderStandaloneView(View const* view) { + upcast(this)->renderStandaloneView(upcast(view)); +} + } // namespace filament diff --git a/filament/src/Scene.cpp b/filament/src/Scene.cpp index 5d7ef7d5a8..541af9a74e 100644 --- a/filament/src/Scene.cpp +++ b/filament/src/Scene.cpp @@ -164,14 +164,14 @@ void FScene::prepare(const mat4f& worldOriginTransform) { // some elements past the end of the array will be accessed by SIMD code, we need to make // sure the data is valid enough as not to produce errors such as divide-by-zero // (e.g. in computeLightRanges()) - for (size_t i = lightData.size(), e = (lightData.size() + 3u) & ~3u; i < e; i++) { + for (size_t i = lightData.size(), e = lightDataCapacity; i < e; i++) { new(lightData.data() + i) float4{ 0, 0, 0, 1 }; } // Purely for the benefit of MSAN, we can avoid uninitialized reads by zeroing out the // unused scene elements between the end of the array and the rounded-up count. if (UTILS_HAS_SANITIZE_MEMORY) { - for (size_t i = sceneData.size(), e = (sceneData.size() + 0xFu) & ~0xFu; i < e; i++) { + for (size_t i = sceneData.size(), e = renderableDataCapacity; i < e; i++) { sceneData.data()[i] = 0; sceneData.data()[i] = 0; sceneData.data()[i] = {}; @@ -256,7 +256,8 @@ void FScene::terminate(FEngine& engine) { mRenderableViewUbh.clear(); } -void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootArena, backend::Handle lightUbh) noexcept { +void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootArena, + backend::Handle lightUbh) noexcept { FEngine::DriverApi& driver = mEngine.getDriverApi(); FLightManager& lcm = mEngine.getLightManager(); FScene::LightSoa& lightData = getLightData(); @@ -273,6 +274,9 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren ArenaScope arena(rootArena.getAllocator()); size_t const size = lightData.size(); + // number of point/spot lights + size_t positionalLightCount = size - DIRECTIONAL_LIGHTS_COUNT; + assert_invariant(positionalLightCount); // always allocate at least 4 entries, because the vectorized loops below rely on that float* const UTILS_RESTRICT distances = arena.allocate((size + 3u) & 3u, CACHELINE_SIZE); @@ -290,9 +294,6 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren // drop excess lights lightData.resize(std::min(size, CONFIG_MAX_LIGHT_COUNT + DIRECTIONAL_LIGHTS_COUNT)); - // number of point/spot lights - size_t positionalLightCount = size - DIRECTIONAL_LIGHTS_COUNT; - // compute the light ranges (needed when building light trees) float2* const zrange = lightData.data(); computeLightRanges(zrange, camera, spheres + DIRECTIONAL_LIGHTS_COUNT, positionalLightCount); diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 1ba7d876b0..76956ee018 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -214,7 +214,6 @@ void FView::prepareShadowing(FEngine& engine, backend::DriverApi& driver, mHasShadowing = false; mNeedsShadowMap = false; - if (!mShadowingEnabled) { return; } @@ -270,25 +269,40 @@ void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaSc UniformBuffer& u = mPerViewUb; const CameraInfo& camera = mViewingCameraInfo; FScene* const scene = mScene; - - scene->prepareDynamicLights(camera, arena, mLightUbh); - - // here the array of visible lights has been shrunk to CONFIG_MAX_LIGHT_COUNT auto const& lightData = scene->getLightData(); - // trace the number of visible lights + /* + * Dynamic lights + */ + + mHasDynamicLighting = scene->getLightData().size() > FScene::DIRECTIONAL_LIGHTS_COUNT; + if (mHasDynamicLighting) { + scene->prepareDynamicLights(camera, arena, mLightUbh); + Froxelizer& froxelizer = mFroxelizer; + if (froxelizer.prepare(driver, arena, viewport, camera.projection, camera.zn, camera.zf)) { + froxelizer.updateUniforms(u); // update our uniform buffer if needed + } + } + + // here the array of visible lights has been shrunk to CONFIG_MAX_LIGHT_COUNT SYSTRACE_VALUE32("visibleLights", lightData.size() - FScene::DIRECTIONAL_LIGHTS_COUNT); - // Exposure - const float ev100 = camera.ev100; - const float exposure = Exposure::exposure(ev100); + /* + * Exposure + */ + + const float exposure = Exposure::exposure(camera.ev100); u.setUniform(offsetof(PerViewUib, exposure), exposure); - u.setUniform(offsetof(PerViewUib, ev100), ev100); + u.setUniform(offsetof(PerViewUib, ev100), camera.ev100); + + /* + * Indirect light (IBL) + */ // If the scene does not have an IBL, use the black 1x1 IBL and honor the fallback intensity // associated with the skybox. FIndirectLight const* ibl = scene->getIndirectLight(); - float intensity{}; + float intensity; if (UTILS_LIKELY(ibl)) { intensity = ibl->getIntensity(); } else { @@ -313,7 +327,10 @@ void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaSc .filterMin = SamplerMinFilter::LINEAR_MIPMAP_LINEAR }}); - // Directional light (always at index 0) + /* + * Directional light (always at index 0) + */ + auto& lcm = engine.getLightManager(); FLightManager::Instance directionalLight = lightData.elementAt(0); mHasDirectionalLight = directionalLight.isValid(); @@ -345,15 +362,6 @@ void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaSc float4 sun{ 0.0f, 0.0f, 0.0f, -1.0f }; u.setUniform(offsetof(PerViewUib, sun), sun); } - - // Dynamic lighting - mHasDynamicLighting = scene->getLightData().size() > FScene::DIRECTIONAL_LIGHTS_COUNT; - if (mHasDynamicLighting) { - Froxelizer& froxelizer = mFroxelizer; - if (froxelizer.prepare(driver, arena, viewport, camera.projection, camera.zn, camera.zf)) { - froxelizer.updateUniforms(u); // update our uniform buffer if needed - } - } } void FView::prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& arena, @@ -413,11 +421,14 @@ void FView::prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& are * Light culling: runs in parallel with Renderable culling (below) */ - auto *prepareVisibleLightsJob = js.runAndRetain(js.createJob(nullptr, - [&frustum = mCullingFrustum, &engine, scene](JobSystem& js, JobSystem::Job*) { - FView::prepareVisibleLights( - engine.getLightManager(), js, frustum, scene->getLightData()); - })); + JobSystem::Job* prepareVisibleLightsJob = nullptr; + if (scene->getLightData().size() > FScene::DIRECTIONAL_LIGHTS_COUNT) { + prepareVisibleLightsJob = js.runAndRetain(js.createJob(nullptr, + [&frustum = mCullingFrustum, &engine, scene](JobSystem& js, JobSystem::Job*) { + FView::prepareVisibleLights( + engine.getLightManager(), js, frustum, scene->getLightData()); + })); + } Range merged; FScene::RenderableSoa& renderableData = scene->getRenderableData(); @@ -441,7 +452,9 @@ void FView::prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& are */ // prepareShadowing relies on prepareVisibleLights(). - js.waitAndRelease(prepareVisibleLightsJob); + if (prepareVisibleLightsJob) { + js.waitAndRelease(prepareVisibleLightsJob); + } prepareShadowing(engine, driver, renderableData, scene->getLightData()); /* @@ -693,11 +706,8 @@ void FView::cleanupRenderPasses() const noexcept { void FView::froxelize(FEngine& engine) const noexcept { SYSTRACE_CALL(); - - if (mHasDynamicLighting) { - // froxelize lights - mFroxelizer.froxelizeLights(engine, mViewingCameraInfo, mScene->getLightData()); - } + assert_invariant(mHasDynamicLighting); + mFroxelizer.froxelizeLights(engine, mViewingCameraInfo, mScene->getLightData()); } void FView::commitUniforms(backend::DriverApi& driver) const noexcept { @@ -758,6 +768,7 @@ void FView::cullRenderables(JobSystem& js, void FView::prepareVisibleLights(FLightManager const& lcm, utils::JobSystem&, Frustum const& frustum, FScene::LightSoa& lightData) noexcept { SYSTRACE_CALL(); + assert_invariant(lightData.size() > FScene::DIRECTIONAL_LIGHTS_COUNT); auto const* UTILS_RESTRICT sphereArray = lightData.data(); auto const* UTILS_RESTRICT directions = lightData.data(); diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index 40cd2f2e01..e5159dfe9e 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -74,25 +74,29 @@ public: math::float4 getShaderUserTime() const { return mShaderUserTime; } // do all the work here! - void render(FView const* view); void renderJob(ArenaScope& arena, FView& view); - void copyFrame(FSwapChain* dstSwapChain, Viewport const& dstViewport, - Viewport const& srcViewport, CopyFrameFlag flags); - bool beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano, backend::FrameScheduledCallback callback, void* user); - void endFrame(); - void resetUserTime(); + void render(FView const* view); void readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& buffer); + void copyFrame(FSwapChain* dstSwapChain, Viewport const& dstViewport, + Viewport const& srcViewport, CopyFrameFlag flags); + + void endFrame(); + + void renderStandaloneView(FView const* view); + void readPixels(FRenderTarget* renderTarget, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& buffer); + void resetUserTime(); + // Clean-up everything, this is typically called when the client calls Engine::destroyRenderer() void terminate(FEngine& engine); @@ -134,6 +138,8 @@ private: uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, backend::PixelBufferDescriptor&& buffer); + void renderInternal(FView const* view); + struct ColorPassConfig { Viewport vp; Viewport svp; diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index db290fc8b2..bbed7f0866 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -627,6 +627,7 @@ class_("SwapChain"); /// See also the [Engine] methods `createRenderer` and `destroyRenderer`. class_("Renderer") .function("renderView", &Renderer::render, allow_raw_pointers()) + .function("renderStandaloneView", &Renderer::renderStandaloneView, allow_raw_pointers()) /// render ::method:: requests rendering for a single frame on the given [View] /// swapChain ::argument:: the [SwapChain] corresponding to the canvas /// view ::argument:: the [View] corresponding to the canvas