From bf72adbb57c2bc1e15002b8a674cbba3ceb5ef92 Mon Sep 17 00:00:00 2001 From: Mathias Agopian Date: Fri, 1 Nov 2019 14:46:23 -0700 Subject: [PATCH] Implement Headless SwapChain mode. This is useful for unit tests. The content of the swapchain can be read to main memory with Renderer::readPixels(). This PR doesn't implement this new feature in the following backends and platforms (TODO): VulkanDriver.cpp MetalDriver.mm PlatformWGL.cpp PlatformWebGL.cpp PlatformCocoaTouchGL.mm --- .../filament-android/src/main/cpp/Engine.cpp | 7 ++ .../com/google/android/filament/Engine.java | 27 +++++ .../google/android/filament/SwapChain.java | 6 +- .../include/private/backend/DriverAPI.inc | 8 ++ .../include/private/backend/OpenGLPlatform.h | 4 + filament/backend/src/metal/MetalDriver.mm | 15 ++- filament/backend/src/opengl/OpenGLContext.cpp | 9 +- filament/backend/src/opengl/OpenGLDriver.cpp | 22 +++- filament/backend/src/opengl/PlatformCocoaGL.h | 1 + .../backend/src/opengl/PlatformCocoaGL.mm | 19 ++- .../backend/src/opengl/PlatformCocoaTouchGL.h | 1 + .../src/opengl/PlatformCocoaTouchGL.mm | 5 + filament/backend/src/opengl/PlatformEGL.cpp | 24 +++- filament/backend/src/opengl/PlatformEGL.h | 1 + filament/backend/src/opengl/PlatformGLX.cpp | 27 ++++- filament/backend/src/opengl/PlatformGLX.h | 4 + filament/backend/src/opengl/PlatformWGL.cpp | 5 + filament/backend/src/opengl/PlatformWGL.h | 1 + filament/backend/src/opengl/PlatformWebGL.cpp | 6 + filament/backend/src/opengl/PlatformWebGL.h | 1 + filament/backend/src/vulkan/VulkanDriver.cpp | 14 +++ filament/include/filament/Engine.h | 14 +++ filament/src/Engine.cpp | 22 +++- filament/src/SwapChain.cpp | 10 +- filament/src/details/Engine.h | 1 + filament/src/details/SwapChain.h | 1 + filament/test/CMakeLists.txt | 2 +- filament/test/filament_rendering_test.cpp | 113 ++++++++++++++++++ 28 files changed, 347 insertions(+), 23 deletions(-) create mode 100644 filament/test/filament_rendering_test.cpp diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index a5638bbbe4..e208fb9415 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -59,6 +59,13 @@ Java_com_google_android_filament_Engine_nCreateSwapChain(JNIEnv* env, return (jlong) engine->createSwapChain(win, (uint64_t) flags); } +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_android_filament_Engine_nCreateSwapChainHeadless(JNIEnv* env, + jclass klass, jlong nativeEngine, jint width, jint height, jlong flags) { + Engine* engine = (Engine*) nativeEngine; + return (jlong) engine->createSwapChain(width, height, (uint64_t) flags); +} + extern "C" JNIEXPORT jlong JNICALL Java_com_google_android_filament_Engine_nCreateSwapChainFromRawPointer(JNIEnv*, jclass, jlong nativeEngine, jlong pointer, jlong flags) { diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index 4c4acd6510..70622cc7f8 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -290,6 +290,32 @@ public class Engine { throw new IllegalArgumentException("Invalid surface " + surface); } + /** + * Creates a headless {@link SwapChain} + * + * @param width width of the rendering buffer + * @param height height of the rendering buffer + * @param flags configuration flags, see {@link SwapChain} + * + * @return a newly created {@link SwapChain} object + * + * @exception IllegalStateException can be thrown if the SwapChain couldn't be created + * + * @see SwapChain#CONFIG_DEFAULT + * @see SwapChain#CONFIG_TRANSPARENT + * @see SwapChain#CONFIG_READABLE + * + */ + @NonNull + public SwapChain createSwapChain(int width, int height, long flags) { + if (width >= 0 && height >= 0) { + long nativeSwapChain = nCreateSwapChainHeadless(getNativeObject(), width, height, flags); + if (nativeSwapChain == 0) throw new IllegalStateException("Couldn't create SwapChain"); + return new SwapChain(nativeSwapChain, null); + } + throw new IllegalArgumentException("Invalid parameters"); + } + /** * Creates a {@link SwapChain} from a {@link NativeSurface}. * @@ -608,6 +634,7 @@ public class Engine { private static native void nDestroyEngine(long nativeEngine); private static native long nGetBackend(long nativeEngine); private static native long nCreateSwapChain(long nativeEngine, Object nativeWindow, long flags); + private static native long nCreateSwapChainHeadless(long nativeEngine, int width, int height, long flags); private static native long nCreateSwapChainFromRawPointer(long nativeEngine, long pointer, long flags); private static native void nDestroySwapChain(long nativeEngine, long nativeSwapChain); private static native long nCreateView(long nativeEngine); diff --git a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java index f8db30b3a2..7dc8d6f89c 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java +++ b/android/filament-android/src/main/java/com/google/android/filament/SwapChain.java @@ -85,15 +85,15 @@ public class SwapChain { */ public static final long CONFIG_READABLE = 0x2; - SwapChain(long nativeSwapChain, @NonNull Object surface) { + SwapChain(long nativeSwapChain, Object surface) { mNativeObject = nativeSwapChain; mSurface = surface; } /** - * @return the native Object this SwapChain was created from. + * @return the native Object this SwapChain was created from or null + * for a headless SwapChain. */ - @NonNull public Object getNativeWindow() { return mSurface; } diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index fefae305ae..869538b0c6 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -124,6 +124,9 @@ DECL_DRIVER_API_N(endFrame, // can start rendering. e.g. correspond to glFlush() for a GLES driver. DECL_DRIVER_API_0(flush) +// flush and wait for the effects to be done +DECL_DRIVER_API_0(finish) + /* * Creating driver objects * ----------------------- @@ -180,6 +183,11 @@ DECL_DRIVER_API_R_N(backend::SwapChainHandle, createSwapChain, void*, nativeWindow, uint64_t, flags) +DECL_DRIVER_API_R_N(backend::SwapChainHandle, createSwapChainHeadless, + uint32_t, width, + uint32_t, height, + uint64_t, flags) + DECL_DRIVER_API_R_N(backend::StreamHandle, createStreamFromTextureId, intptr_t, externalTextureId, uint32_t, width, diff --git a/filament/backend/include/private/backend/OpenGLPlatform.h b/filament/backend/include/private/backend/OpenGLPlatform.h index 3bf7b93d43..7df84cd238 100644 --- a/filament/backend/include/private/backend/OpenGLPlatform.h +++ b/filament/backend/include/private/backend/OpenGLPlatform.h @@ -41,6 +41,10 @@ public: virtual void terminate() noexcept = 0; virtual SwapChain* createSwapChain(void* nativeWindow, uint64_t& flags) noexcept = 0; + + // headless swapchain + virtual SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept = 0; + virtual void destroySwapChain(SwapChain* swapChain) noexcept = 0; virtual void createDefaultRenderTarget(uint32_t& framebuffer, uint32_t& colorbuffer, diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 93400c879a..b1d2f28cc8 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -128,8 +128,12 @@ void MetalDriver::endFrame(uint32_t frameId) { CVMetalTextureCacheFlush(mContext->textureCache, 0); } -void MetalDriver::flush(int dummy) { +void MetalDriver::flush(int) { + // TODO: implement flush, equivalent of glFlush() (needed for performance) +} +void MetalDriver::finish(int) { + // TODO: implement finish, equivalent of glFinish() (needed for unit tests) } void MetalDriver::createVertexBufferR(Handle vbh, uint8_t bufferCount, @@ -218,6 +222,11 @@ void MetalDriver::createSwapChainR(Handle sch, void* nativeWindow, construct_handle(mHandleMap, sch, mContext->device, metalLayer); } +void MetalDriver::createSwapChainHeadlessR(Handle sch, + uint32_t width, uint32_t height, uint64_t flags) { + // TODO: implement headless swapchain +} + void MetalDriver::createStreamFromTextureIdR(Handle, intptr_t externalTextureId, uint32_t width, uint32_t height) { @@ -267,6 +276,10 @@ Handle MetalDriver::createSwapChainS() noexcept { return alloc_handle(); } +Handle MetalDriver::createSwapChainHeadlessS() noexcept { + return alloc_handle(); +} + Handle MetalDriver::createStreamFromTextureIdS() noexcept { return {}; } diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 8aaaf14538..13af021c3a 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -36,11 +36,7 @@ OpenGLContext::OpenGLContext() noexcept { UTILS_UNUSED char const* const shader = (char const*) glGetString(GL_SHADING_LANGUAGE_VERSION); #ifndef NDEBUG - slog.i - << vendor << io::endl - << renderer << io::endl - << version << io::endl - << shader << io::endl; + slog.i << vendor << ", " << renderer << ", " << version << ", " << shader << io::endl; #endif // OpenGL (ES) version @@ -51,7 +47,8 @@ OpenGLContext::OpenGLContext() noexcept { glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gets.max_uniform_block_size); glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &gets.uniform_buffer_offset_alignment); -#ifndef NDEBUG +#if 0 + // this is useful for development, but too verbose even for debug builds slog.i << "GL_MAX_RENDERBUFFER_SIZE = " << gets.max_renderbuffer_size << io::endl << "GL_MAX_UNIFORM_BLOCK_SIZE = " << gets.max_uniform_block_size << io::endl diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index fc876afd4f..c86cb5f319 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -328,7 +328,7 @@ void OpenGLDriver::setRasterStateSlow(RasterState rs) noexcept { // For reference on a 64-bits machine: // GLFence : 8 // GLIndexBuffer : 12 moderate -// GLSamplerGroup : 16 moderate +// GLSamplerGroup : 16 moderate // -- less than 16 bytes // GLRenderPrimitive : 40 many @@ -351,7 +351,8 @@ OpenGLDriver::HandleAllocator::HandleAllocator(const utils::HeapArea& area) mPool2( pointermath::add(area.begin(), (6 * area.getSize()) / 16), area.end()) { -#ifndef NDEBUG +#if 0 + // this is useful for development, but too verbose even for debug builds slog.d << "HwFence: " << sizeof(HwFence) << io::endl; slog.d << "GLIndexBuffer: " << sizeof(GLIndexBuffer) << io::endl; slog.d << "GLSamplerGroup: " << sizeof(GLSamplerGroup) << io::endl; @@ -464,6 +465,10 @@ Handle OpenGLDriver::createSwapChainS() noexcept { return Handle( allocateHandle(sizeof(HwSwapChain)) ); } +Handle OpenGLDriver::createSwapChainHeadlessS() noexcept { + return Handle( allocateHandle(sizeof(HwSwapChain)) ); +} + Handle OpenGLDriver::createStreamFromTextureIdS() noexcept { return Handle( allocateHandle(sizeof(GLStream)) ); } @@ -983,6 +988,14 @@ void OpenGLDriver::createSwapChainR(Handle sch, void* nativeWindow, sc->swapChain = mPlatform.createSwapChain(nativeWindow, flags); } +void OpenGLDriver::createSwapChainHeadlessR(Handle sch, + uint32_t width, uint32_t height, uint64_t flags) { + DEBUG_MARKER() + + HwSwapChain* sc = construct(sch); + sc->swapChain = mPlatform.createSwapChain(width, height, flags); +} + void OpenGLDriver::createStreamFromTextureIdR(Handle sh, intptr_t externalTextureId, uint32_t width, uint32_t height) { DEBUG_MARKER() @@ -2467,6 +2480,11 @@ void OpenGLDriver::flush(int) { } } +void OpenGLDriver::finish(int) { + DEBUG_MARKER() + glFinish(); +} + UTILS_NOINLINE void OpenGLDriver::clearWithRasterPipe( bool clearColor, float4 const& linearColor, diff --git a/filament/backend/src/opengl/PlatformCocoaGL.h b/filament/backend/src/opengl/PlatformCocoaGL.h index 2c5823d552..8679414d5b 100644 --- a/filament/backend/src/opengl/PlatformCocoaGL.h +++ b/filament/backend/src/opengl/PlatformCocoaGL.h @@ -36,6 +36,7 @@ public: void terminate() noexcept final; SwapChain* createSwapChain(void* nativewindow, uint64_t& flags) noexcept final; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept final; void destroySwapChain(SwapChain* swapChain) noexcept final; void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept final; void commit(SwapChain* swapChain) noexcept final; diff --git a/filament/backend/src/opengl/PlatformCocoaGL.mm b/filament/backend/src/opengl/PlatformCocoaGL.mm index f27d2f239a..0369268b6c 100644 --- a/filament/backend/src/opengl/PlatformCocoaGL.mm +++ b/filament/backend/src/opengl/PlatformCocoaGL.mm @@ -27,6 +27,8 @@ #include #include +#include + namespace filament { using namespace backend; @@ -34,6 +36,7 @@ using namespace backend; struct PlatformCocoaGLImpl { NSOpenGLContext* mGLContext = nullptr; NSView* mCurrentView = nullptr; + std::vector mHeadlessSwapChains; }; PlatformCocoaGL::PlatformCocoaGL() @@ -75,12 +78,26 @@ void PlatformCocoaGL::terminate() noexcept { } Platform::SwapChain* PlatformCocoaGL::createSwapChain(void* nativewindow, uint64_t& flags) noexcept { - // Transparent swap chain is not supported + // Transparent SwapChain is not supported flags &= ~backend::SWAP_CHAIN_CONFIG_TRANSPARENT; return (SwapChain*) nativewindow; } +Platform::SwapChain* PlatformCocoaGL::createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept { + NSView* nsView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width, height)]; + // adding the pointer to the array retains the NSView + pImpl->mHeadlessSwapChains.push_back(nsView); + return (__bridge SwapChain*)nsView; +} + void PlatformCocoaGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { + auto& v = pImpl->mHeadlessSwapChains; + NSView* nsView = (__bridge NSView*)swapChain; + auto it = std::find(v.begin(), v.end(), nsView); + if (it != v.end()) { + // removing the pointer from the array releases the NSView + v.erase(it); + } } void PlatformCocoaGL::makeCurrent(Platform::SwapChain* drawSwapChain, diff --git a/filament/backend/src/opengl/PlatformCocoaTouchGL.h b/filament/backend/src/opengl/PlatformCocoaTouchGL.h index 7f3e61810c..d2288221b8 100644 --- a/filament/backend/src/opengl/PlatformCocoaTouchGL.h +++ b/filament/backend/src/opengl/PlatformCocoaTouchGL.h @@ -36,6 +36,7 @@ public: void terminate() noexcept final; SwapChain* createSwapChain(void* nativewindow, uint64_t& flags) noexcept final; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept final; void destroySwapChain(SwapChain* swapChain) noexcept final; void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept final; void commit(SwapChain* swapChain) noexcept final; diff --git a/filament/backend/src/opengl/PlatformCocoaTouchGL.mm b/filament/backend/src/opengl/PlatformCocoaTouchGL.mm index 1fab394aa3..70f627e50b 100644 --- a/filament/backend/src/opengl/PlatformCocoaTouchGL.mm +++ b/filament/backend/src/opengl/PlatformCocoaTouchGL.mm @@ -106,6 +106,11 @@ Platform::SwapChain* PlatformCocoaTouchGL::createSwapChain(void* nativewindow, u return (SwapChain*) nativewindow; } +Platform::SwapChain* PlatformCocoaTouchGL::createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept { + // TODO: implement headless SwapChain + return nullptr; +} + void PlatformCocoaTouchGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { } diff --git a/filament/backend/src/opengl/PlatformEGL.cpp b/filament/backend/src/opengl/PlatformEGL.cpp index 4a2bf9336d..377170ef26 100644 --- a/filament/backend/src/opengl/PlatformEGL.cpp +++ b/filament/backend/src/opengl/PlatformEGL.cpp @@ -343,8 +343,10 @@ void PlatformEGL::terminate() noexcept { Platform::SwapChain* PlatformEGL::createSwapChain( void* nativeWindow, uint64_t& flags) noexcept { EGLSurface sur = eglCreateWindowSurface(mEGLDisplay, - (flags & backend::SWAP_CHAIN_CONFIG_TRANSPARENT) ? mEGLTransparentConfig : mEGLConfig, + (flags & backend::SWAP_CHAIN_CONFIG_TRANSPARENT) ? + mEGLTransparentConfig : mEGLConfig, (EGLNativeWindowType)nativeWindow, nullptr); + if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) { logEglError("eglCreateWindowSurface"); return nullptr; @@ -361,6 +363,26 @@ Platform::SwapChain* PlatformEGL::createSwapChain( return (SwapChain*)sur; } +Platform::SwapChain* PlatformEGL::createSwapChain( + uint32_t width, uint32_t height, uint64_t& flags) noexcept { + + EGLint attribs[] = { + EGL_WIDTH, EGLint(width), + EGL_HEIGHT, EGLint(height), + EGL_NONE + }; + + EGLSurface sur = eglCreatePbufferSurface(mEGLDisplay, + (flags & backend::SWAP_CHAIN_CONFIG_TRANSPARENT) ? + mEGLTransparentConfig : mEGLConfig, attribs); + + if (UTILS_UNLIKELY(sur == EGL_NO_SURFACE)) { + logEglError("eglCreatePbufferSurface"); + return nullptr; + } + return (SwapChain*)sur; +} + void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { EGLSurface sur = (EGLSurface) swapChain; if (sur != EGL_NO_SURFACE) { diff --git a/filament/backend/src/opengl/PlatformEGL.h b/filament/backend/src/opengl/PlatformEGL.h index 4cbecba323..f68705006e 100644 --- a/filament/backend/src/opengl/PlatformEGL.h +++ b/filament/backend/src/opengl/PlatformEGL.h @@ -43,6 +43,7 @@ public: void terminate() noexcept override; SwapChain* createSwapChain(void* nativewindow, uint64_t& flags) noexcept final; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept final; void destroySwapChain(SwapChain* swapChain) noexcept final; void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept final; void commit(SwapChain* swapChain) noexcept final; diff --git a/filament/backend/src/opengl/PlatformGLX.cpp b/filament/backend/src/opengl/PlatformGLX.cpp index 92d086efd5..aea8661591 100644 --- a/filament/backend/src/opengl/PlatformGLX.cpp +++ b/filament/backend/src/opengl/PlatformGLX.cpp @@ -238,15 +238,34 @@ void PlatformGLX::terminate() noexcept { bluegl::unbind(); } -Platform::SwapChain* PlatformGLX::createSwapChain( - void* nativeWindow, uint64_t& flags) noexcept { - +Platform::SwapChain* PlatformGLX::createSwapChain(void* nativeWindow, uint64_t& flags) noexcept { // Transparent swap chain is not supported flags &= ~backend::SWAP_CHAIN_CONFIG_TRANSPARENT; return (SwapChain*) nativeWindow; } -void PlatformGLX::destroySwapChain(Platform::SwapChain* /*swapChain*/) noexcept { +Platform::SwapChain* PlatformGLX::createSwapChain( + uint32_t width, uint32_t height, uint64_t& flags) noexcept { + // Transparent swap chain is not supported + flags &= ~backend::SWAP_CHAIN_CONFIG_TRANSPARENT; + int pbufferAttribs[] = { + GLX_PBUFFER_WIDTH, int(width), + GLX_PBUFFER_HEIGHT, int(height), + GL_NONE + }; + GLXPbuffer sur = g_glx.createPbuffer(mGLXDisplay, mGLXConfig[0], pbufferAttribs); + if (sur) { + mPBuffers.push_back(sur); + } + return (SwapChain*) sur; +} + +void PlatformGLX::destroySwapChain(Platform::SwapChain* swapChain) noexcept { + auto it = std::find(mPBuffers.begin(), mPBuffers.end(), (GLXPbuffer)swapChain); + if (it != mPBuffers.end()) { + g_glx.destroyPbuffer(mGLXDisplay, (GLXPbuffer)swapChain); + mPBuffers.erase(it); + } } void PlatformGLX::makeCurrent( diff --git a/filament/backend/src/opengl/PlatformGLX.h b/filament/backend/src/opengl/PlatformGLX.h index 76cc51f7ea..45c3abf1fa 100644 --- a/filament/backend/src/opengl/PlatformGLX.h +++ b/filament/backend/src/opengl/PlatformGLX.h @@ -26,6 +26,8 @@ #include "private/backend/OpenGLPlatform.h" +#include + namespace filament { class PlatformGLX final : public backend::OpenGLPlatform { @@ -36,6 +38,7 @@ public: void terminate() noexcept override; SwapChain* createSwapChain(void* nativewindow, uint64_t& flags) noexcept override; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; void commit(SwapChain* swapChain) noexcept override; @@ -64,6 +67,7 @@ private: GLXContext mGLXContext; GLXFBConfig* mGLXConfig; GLXPbuffer mDummySurface; + std::vector mPBuffers; }; } // namespace filament diff --git a/filament/backend/src/opengl/PlatformWGL.cpp b/filament/backend/src/opengl/PlatformWGL.cpp index 413fa7bdd1..7d9060f1b7 100644 --- a/filament/backend/src/opengl/PlatformWGL.cpp +++ b/filament/backend/src/opengl/PlatformWGL.cpp @@ -182,6 +182,11 @@ Platform::SwapChain* PlatformWGL::createSwapChain(void* nativeWindow, uint64_t& return swapChain; } +Platform::SwapChain* PlatformWGL::createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept { + // TODO: implement headless SwapChain + return nullptr; +} + void PlatformWGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { HDC dc = (HDC) swapChain; HWND window = WindowFromDC(dc); diff --git a/filament/backend/src/opengl/PlatformWGL.h b/filament/backend/src/opengl/PlatformWGL.h index 955c9b5656..467352ea5a 100644 --- a/filament/backend/src/opengl/PlatformWGL.h +++ b/filament/backend/src/opengl/PlatformWGL.h @@ -34,6 +34,7 @@ public: void terminate() noexcept override; SwapChain* createSwapChain(void* nativewindow, uint64_t& flags) noexcept override; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept override; void destroySwapChain(SwapChain* swapChain) noexcept override; void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override; void commit(SwapChain* swapChain) noexcept override; diff --git a/filament/backend/src/opengl/PlatformWebGL.cpp b/filament/backend/src/opengl/PlatformWebGL.cpp index 00305c610d..bf2b73c38e 100644 --- a/filament/backend/src/opengl/PlatformWebGL.cpp +++ b/filament/backend/src/opengl/PlatformWebGL.cpp @@ -33,6 +33,12 @@ Platform::SwapChain* PlatformWebGL::createSwapChain( return (SwapChain*) nativeWindow; } +Platform::SwapChain* PlatformWebGL::createSwapChain( + uint32_t width, uint32_t height, uint64_t& flags) noexcept { + // TODO: implement headless SwapChain + return nullptr; +} + void PlatformWebGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept { } diff --git a/filament/backend/src/opengl/PlatformWebGL.h b/filament/backend/src/opengl/PlatformWebGL.h index 8208cd4d48..906cd80047 100644 --- a/filament/backend/src/opengl/PlatformWebGL.h +++ b/filament/backend/src/opengl/PlatformWebGL.h @@ -35,6 +35,7 @@ public: void terminate() noexcept override; SwapChain* createSwapChain(void* nativewindow, uint64_t& flags) noexcept final override; + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t& flags) noexcept final override; void destroySwapChain(SwapChain* swapChain) noexcept final override; void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept final override; void commit(SwapChain* swapChain) noexcept final override; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 7d51fa495d..16908c9a94 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -283,6 +283,10 @@ void VulkanDriver::flush(int) { // Todo: equivalent of glFlush() } +void VulkanDriver::finish(int) { + // Todo: equivalent of glFinish() +} + void VulkanDriver::createSamplerGroupR(Handle sbh, size_t count) { construct_handle(mHandleMap, sbh, mContext, count); } @@ -436,6 +440,12 @@ void VulkanDriver::createSwapChainR(Handle sch, void* nativeWindow, mContext.currentSurface = ≻ } +void VulkanDriver::createSwapChainHeadlessR(Handle sch, + uint32_t width, uint32_t height, uint64_t flags) { + //auto* swapChain = construct_handle(mHandleMap, sch); + // TODO: implement headless swapchain +} + void VulkanDriver::createStreamFromTextureIdR(Handle sh, intptr_t externalTextureId, uint32_t width, uint32_t height) { } @@ -484,6 +494,10 @@ Handle VulkanDriver::createSwapChainS() noexcept { return alloc_handle(); } +Handle VulkanDriver::createSwapChainHeadlessS() noexcept { + return alloc_handle(); +} + Handle VulkanDriver::createStreamFromTextureIdS() noexcept { return {}; } diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 9bccff2bd4..a6296a8387 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -259,6 +259,20 @@ public: */ SwapChain* createSwapChain(void* nativeWindow, uint64_t flags = 0) noexcept; + + /** + * Creates a headless SwapChain. + * + * @param width Width of the drawing buffer in pixels. + * @param height Height of the drawing buffer in pixels. + * @param flags One or more configuration flags as defined in `SwapChain`. + * + * @return A pointer to the newly created SwapChain or nullptr if it couldn't be created. + * + * @see Renderer.beginFrame() + */ + SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags = 0) noexcept; + /** * Creates a renderer associated to this engine. * diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 9de0f5806b..0fd55e1328 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -379,7 +379,15 @@ void FEngine::flush() { } void FEngine::flushAndWait() { - FFence::waitAndDestroy(FEngine::createFence(FFence::Type::SOFT), FFence::Mode::FLUSH); + // enqueue finish command -- this will stall in the driver until the GPU is done + getDriverApi().finish(); + + // then create a fence that will trigger when we're past the finish() above + FFence::waitAndDestroy( + FEngine::createFence(FFence::Type::SOFT), FFence::Mode::FLUSH); + + // finally, execute callbacks that might have been scheduled + getDriver().purge(); } // ----------------------------------------------------------------------------------------------- @@ -587,6 +595,14 @@ FSwapChain* FEngine::createSwapChain(void* nativeWindow, uint64_t flags) noexcep return p; } +FSwapChain* FEngine::createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept { + FSwapChain* p = mHeapAllocator.make(*this, width, height, flags); + if (p) { + mSwapChains.insert(p); + } + return p; +} + /* * Objects created with a component manager */ @@ -847,6 +863,10 @@ SwapChain* Engine::createSwapChain(void* nativeWindow, uint64_t flags) noexcept return upcast(this)->createSwapChain(nativeWindow, flags); } +SwapChain* Engine::createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept { + return upcast(this)->createSwapChain(width, height, flags); +} + void Engine::destroy(const VertexBuffer* p) { upcast(this)->destroy(upcast(p)); } diff --git a/filament/src/SwapChain.cpp b/filament/src/SwapChain.cpp index 4d303f5389..d0ebca2072 100644 --- a/filament/src/SwapChain.cpp +++ b/filament/src/SwapChain.cpp @@ -22,9 +22,13 @@ namespace filament { namespace details { FSwapChain::FSwapChain(FEngine& engine, void* nativeWindow, uint64_t flags) - : mNativeWindow(nativeWindow) { - mConfigFlags = flags; - mSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, mConfigFlags); + : mNativeWindow(nativeWindow), mConfigFlags(flags) { + mSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, flags); +} + +FSwapChain::FSwapChain(FEngine& engine, uint32_t width, uint32_t height, uint64_t flags) + : mConfigFlags(flags) { + mSwapChain = engine.getDriverApi().createSwapChainHeadless(width, height, flags); } void FSwapChain::terminate(FEngine& engine) noexcept { diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 6ccc8c0e68..9251ae8069 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -239,6 +239,7 @@ public: FView* createView() noexcept; FFence* createFence(FFence::Type type) noexcept; FSwapChain* createSwapChain(void* nativeWindow, uint64_t flags) noexcept; + FSwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept; FCamera* createCamera(utils::Entity entity) noexcept; FCamera* getCameraComponent(utils::Entity entity) noexcept; diff --git a/filament/src/details/SwapChain.h b/filament/src/details/SwapChain.h index b7d2781d8b..9d562a6ea6 100644 --- a/filament/src/details/SwapChain.h +++ b/filament/src/details/SwapChain.h @@ -33,6 +33,7 @@ class FEngine; class FSwapChain : public SwapChain { public: FSwapChain(FEngine& engine, void* nativeWindow, uint64_t flags); + FSwapChain(FEngine& engine, uint32_t width, uint32_t height, uint64_t flags); void terminate(FEngine& engine) noexcept; void makeCurrent(backend::DriverApi& driverApi) noexcept { diff --git a/filament/test/CMakeLists.txt b/filament/test/CMakeLists.txt index 68642665a9..4e8605742a 100644 --- a/filament/test/CMakeLists.txt +++ b/filament/test/CMakeLists.txt @@ -9,7 +9,7 @@ if(NOT IOS AND NOT WEBGL) # The following tests rely on private APIs that are stripped # away in Release builds if (TNT_DEV) - add_executable(test_${TARGET} filament_test_exposure.cpp filament_framegraph_test.cpp filament_test.cpp) + add_executable(test_${TARGET} filament_test_exposure.cpp filament_rendering_test.cpp filament_framegraph_test.cpp filament_test.cpp) target_link_libraries(test_${TARGET} PRIVATE filament gtest) target_compile_options(test_${TARGET} PRIVATE ${COMPILER_FLAGS}) diff --git a/filament/test/filament_rendering_test.cpp b/filament/test/filament_rendering_test.cpp new file mode 100644 index 0000000000..c03cb0eaf3 --- /dev/null +++ b/filament/test/filament_rendering_test.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2019 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 + +#include +#include +#include +#include + +using namespace filament; +using namespace backend; + +class RenderingTest : public testing::Test { +protected: + Engine* mEngine = nullptr; + SwapChain* mSurface = nullptr; + Renderer* mRenderer = nullptr; + View* mView = nullptr; + Scene* mScene = nullptr; + Camera* mCamera = nullptr; + + using closure_t = std::function; + + void SetUp() override { + mEngine = Engine::create(); + mSurface = mEngine->createSwapChain(16, 16); + mRenderer = mEngine->createRenderer(); + + mScene = mEngine->createScene(); + mCamera = mEngine->createCamera(); + + mView = mEngine->createView(); + mView->setViewport({0, 0, 16, 16}); + mView->setScene(mScene); + mView->setCamera(mCamera); + } + + void TearDown() override { + mEngine->destroy(mCamera); + mEngine->destroy(mScene); + mEngine->destroy(mView); + mEngine->destroy(mRenderer); + mEngine->destroy(mSurface); + Engine::destroy(&mEngine); + } + + void runTest(closure_t closure) { + auto* user = new closure_t(std::move(closure)); + + size_t size = 16 * 16 * 4; + void* buffer = malloc(size); + memset(buffer, 0, size); + PixelBufferDescriptor pd(buffer, size, + PixelDataFormat::RGBA, PixelDataType::UBYTE, + callback, user); + + Renderer* pRenderer = mRenderer; + pRenderer->beginFrame(mSurface); + pRenderer->render(mView); + pRenderer->readPixels(0, 0, 16, 16, std::move(pd)); + pRenderer->endFrame(); + + // Note: this is where the runTest() callback will be called. + mEngine->flushAndWait(); + } + +private: + static void callback(void* buffer, size_t size, void* user) { + closure_t* closure = (closure_t *)user; + uint8_t const* rgba = (uint8_t const*)buffer; + (*closure)(rgba, 16, 16); + delete closure; + ::free(buffer); + } +}; + +TEST_F(RenderingTest, ClearRed) { + mView->setClearColor(LinearColorA{1, 0, 0, 1}); + mView->setToneMapping(View::ToneMapping::LINEAR); + mView->setDithering(View::Dithering::NONE); + runTest([this](uint8_t const* rgba, uint32_t width, uint32_t height) { + EXPECT_EQ(rgba[0], 0xff); + EXPECT_EQ(rgba[1], 0); + EXPECT_EQ(rgba[2], 0); + EXPECT_EQ(rgba[3], 0xff); + }); +} + +TEST_F(RenderingTest, ClearGreen) { + mView->setClearColor(LinearColorA{0, 1, 0, 1}); + mView->setToneMapping(View::ToneMapping::LINEAR); + mView->setDithering(View::Dithering::NONE); + runTest([this](uint8_t const* rgba, uint32_t width, uint32_t height) { + EXPECT_EQ(rgba[0], 0); + EXPECT_EQ(rgba[1], 0xff); + EXPECT_EQ(rgba[2], 0); + EXPECT_EQ(rgba[3], 0xff); + }); +}