#if __APPLE__ #include "TargetConditionals.h" #endif #ifdef _WIN32 #pragma comment(lib, "Ws2_32.lib") #endif /* * 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 #ifdef __EMSCRIPTEN__ #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "math.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Log.hpp" #include "FilamentViewer.hpp" #include "StreamBufferAdapter.hpp" #include "material/image.h" #include "TimeIt.hpp" #include "UnprojectTexture.hpp" namespace thermion { using namespace filament; using namespace filament::math; using namespace gltfio; using namespace utils; using namespace image; using namespace std::chrono; using std::string; static constexpr filament::math::float4 sFullScreenTriangleVertices[3] = { {-1.0f, -1.0f, 1.0f, 1.0f}, {3.0f, -1.0f, 1.0f, 1.0f}, {-1.0f, 3.0f, 1.0f, 1.0f} }; static const uint16_t sFullScreenTriangleIndices[3] = {0, 1, 2}; FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapperImpl *const resourceLoader, void *const platform, const char *uberArchivePath) : _resourceLoaderWrapper(resourceLoader) { ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); #if TARGET_OS_IPHONE ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on iOS"); _engine = Engine::create(Engine::Backend::METAL); #elif TARGET_OS_OSX ASSERT_POSTCONDITION(platform == nullptr, "Custom Platform not supported on macOS"); _engine = Engine::create(Engine::Backend::METAL); #elif defined(__EMSCRIPTEN__) _engine = Engine::create(Engine::Backend::OPENGL, (backend::Platform *)new filament::backend::PlatformWebGL(), (void *)sharedContext, nullptr); #elif defined(_WIN32) Engine::Config config; config.stereoscopicEyeCount = 1; config.disableHandleUseAfterFreeCheck = true; _engine = Engine::create(Engine::Backend::VULKAN, (backend::Platform *)platform, (void *)sharedContext, &config); #else _engine = Engine::create(Engine::Backend::OPENGL, (backend::Platform *)platform, (void *)sharedContext, nullptr); #endif _renderer = _engine->createRenderer(); Renderer::ClearOptions clearOptions; clearOptions.clear = true; _renderer->setClearOptions(clearOptions); setFrameInterval(60.0f); _scene = _engine->createScene(); utils::Entity camera = EntityManager::get().create(); _mainCamera = _engine->createCamera(camera); createView(); const float aperture = _mainCamera->getAperture(); const float shutterSpeed = _mainCamera->getShutterSpeed(); const float sens = _mainCamera->getSensitivity(); EntityManager &em = EntityManager::get(); _sceneManager = new SceneManager( _resourceLoaderWrapper, _engine, _scene, uberArchivePath, _mainCamera); } void FilamentViewer::setFrameInterval(float frameInterval) { Renderer::FrameRateOptions fro; fro.interval = frameInterval / 60.0; // TODO don't hardcode display refresh rate _renderer->setFrameRateOptions(fro); } static bool endsWith(std::string path, std::string ending) { return path.compare(path.length() - ending.length(), ending.length(), ending) == 0; } void FilamentViewer::loadKtx2Texture(string path, ResourceBuffer rb) { // TODO - check all this // ktxreader::Ktx2Reader reader(*_engine); // reader.requestFormat(Texture::InternalFormat::DXT3_SRGBA); // reader.requestFormat(Texture::InternalFormat::DXT3_RGBA); // // Uncompressed formats are lower priority, so they get added last. // reader.requestFormat(Texture::InternalFormat::SRGB8_A8); // reader.requestFormat(Texture::InternalFormat::RGBA8); // // std::ifstream inputStream("/data/data/app.polyvox.filament_example/foo.ktx", ios::binary); // // auto contents = vector((istreambuf_iterator(inputStream)), {}); // _imageTexture = reader.load(contents.data(), contents.size(), // ktxreader::Ktx2Reader::TransferFunction::LINEAR); } void FilamentViewer::loadKtxTexture(string path, ResourceBuffer rb) { ktxreader::Ktx1Bundle *bundle = new ktxreader::Ktx1Bundle(static_cast(rb.data), static_cast(rb.size)); // the ResourceBuffer will go out of scope before the texture callback is invoked // make a copy to the heap ResourceBuffer *rbCopy = new ResourceBuffer(rb); std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, rbCopy}; _imageTexture = ktxreader::Ktx1Reader::createTexture( _engine, *bundle, false, [](void *userdata) { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapperImpl* loader = (ResourceLoaderWrapperImpl*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; delete vec; }, callbackData); auto info = bundle->getInfo(); _imageWidth = info.pixelWidth; _imageHeight = info.pixelHeight; } void FilamentViewer::loadPngTexture(string path, ResourceBuffer rb) { thermion::StreamBufferAdapter sb((char *)rb.data, (char *)rb.data + rb.size); std::istream inputStream(&sb); LinearImage *image = new LinearImage(ImageDecoder::decode( inputStream, path.c_str(), ImageDecoder::ColorSpace::SRGB)); if (!image->isValid()) { Log("Invalid image : %s", path.c_str()); return; } uint32_t channels = image->getChannels(); _imageWidth = image->getWidth(); _imageHeight = image->getHeight(); _imageTexture = Texture::Builder() .width(_imageWidth) .height(_imageHeight) .levels(0x01) .format(channels == 3 ? Texture::InternalFormat::RGB16F : Texture::InternalFormat::RGBA16F) .sampler(Texture::Sampler::SAMPLER_2D) .build(*_engine); Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, void *data) { delete reinterpret_cast(data); }; auto pbd = Texture::PixelBufferDescriptor( image->getPixelRef(), size_t(_imageWidth * _imageHeight * channels * sizeof(float)), channels == 3 ? Texture::Format::RGB : Texture::Format::RGBA, Texture::Type::FLOAT, nullptr, freeCallback, image); _imageTexture->setImage(*_engine, 0, std::move(pbd)); // don't need to free the ResourceBuffer in the texture callback // LinearImage takes a copy _resourceLoaderWrapper->free(rb); } void FilamentViewer::loadTextureFromPath(string path) { std::string ktxExt(".ktx"); string ktx2Ext(".ktx2"); string pngExt(".png"); if (path.length() < 5) { Log("Invalid resource path : %s", path.c_str()); return; } ResourceBuffer rb = _resourceLoaderWrapper->load(path.c_str()); if (endsWith(path, ktxExt)) { loadKtxTexture(path, rb); } else if (endsWith(path, ktx2Ext)) { loadKtx2Texture(path, rb); } else if (endsWith(path, pngExt)) { loadPngTexture(path, rb); } } void FilamentViewer::setBackgroundColor(const float r, const float g, const float b, const float a) { std::lock_guard lock(_imageMutex); if (_imageEntity.isNull()) { createBackgroundImage(); } _imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, filament::math::float4(r, g, b, a)); _imageMaterial->setDefaultParameter("transform", _imageScale); } void FilamentViewer::createBackgroundImage() { _dummyImageTexture = Texture::Builder() .width(1) .height(1) .levels(0x01) .format(Texture::InternalFormat::RGB16F) .sampler(Texture::Sampler::SAMPLER_2D) .build(*_engine); try { _imageMaterial = Material::Builder() .package(IMAGE_IMAGE_DATA, IMAGE_IMAGE_SIZE) .build(*_engine); _imageMaterial->setDefaultParameter("showImage", 0); _imageMaterial->setDefaultParameter("backgroundColor", RgbaType::sRGB, filament::math::float4(1.0f, 1.0f, 1.0f, 0.0f)); _imageMaterial->setDefaultParameter("image", _dummyImageTexture, _imageSampler); } catch (...) { Log("Failed to load background image material provider"); std::rethrow_exception(std::current_exception()); } _imageScale = mat4f{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; _imageMaterial->setDefaultParameter("transform", _imageScale); _imageVb = VertexBuffer::Builder() .vertexCount(3) .bufferCount(1) .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT4, 0) .build(*_engine); _imageVb->setBufferAt( *_engine, 0, {sFullScreenTriangleVertices, sizeof(sFullScreenTriangleVertices)}); _imageIb = IndexBuffer::Builder() .indexCount(3) .bufferType(IndexBuffer::IndexType::USHORT) .build(*_engine); _imageIb->setBuffer(*_engine, {sFullScreenTriangleIndices, sizeof(sFullScreenTriangleIndices)}); auto &em = EntityManager::get(); _imageEntity = em.create(); RenderableManager::Builder(1) .boundingBox({{}, {1.0f, 1.0f, 1.0f}}) .material(0, _imageMaterial->getDefaultInstance()) .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, _imageVb, _imageIb, 0, 3) .layerMask(0xFF, 1u << SceneManager::LAYERS::BACKGROUND) .culling(false) .build(*_engine, _imageEntity); _scene->addEntity(_imageEntity); } void FilamentViewer::clearBackgroundImage() { std::lock_guard lock(_imageMutex); if (_imageEntity.isNull()) { createBackgroundImage(); } _imageMaterial->setDefaultParameter("image", _dummyImageTexture, _imageSampler); _imageMaterial->setDefaultParameter("showImage", 0); if (_imageTexture) { _engine->destroy(_imageTexture); _imageTexture = nullptr; } } void FilamentViewer::setBackgroundImage(const char *resourcePath, bool fillHeight, uint32_t width, uint32_t height) { std::lock_guard lock(_imageMutex); if (_imageEntity.isNull()) { createBackgroundImage(); } string resourcePathString(resourcePath); loadTextureFromPath(resourcePathString); // This currently just anchors the image at the bottom left of the viewport at its original size // TODO - implement stretch/etc float xScale = float(width) / float(_imageWidth); float yScale; if (fillHeight) { yScale = 1.0f; } else { yScale = float(height) / float(_imageHeight); } _imageScale = mat4f{xScale, 0.0f, 0.0f, 0.0f, 0.0f, yScale, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; _imageMaterial->setDefaultParameter("transform", _imageScale); _imageMaterial->setDefaultParameter("image", _imageTexture, _imageSampler); _imageMaterial->setDefaultParameter("showImage", 1); } /// /// Translates the background image by (x,y) pixels. /// If clamp is true, x/y are both clamped so that the left/top and right/bottom sides of the background image /// are positioned at a max/min of -1/1 respectively /// (i.e. you cannot set a position where the left/top or right/bottom sides would be "inside" the screen coordinate space). /// void FilamentViewer::setBackgroundImagePosition(float x, float y, bool clamp = false, uint32_t width = 0, uint32_t height = 0) { std::lock_guard lock(_imageMutex); if (_imageEntity.isNull()) { createBackgroundImage(); } // to translate the background image, we apply a transform to the UV coordinates of the quad texture, not the quad itself (see image.mat). // this allows us to set a background colour for the quad when the texture has been translated outside the quad's bounds. // so we need to munge the coordinates appropriately (and take into consideration the scale transform applied when the image was loaded). // first, convert x/y to a percentage of the original image size x /= _imageWidth; y /= _imageHeight; // now scale these by the viewport dimensions so they can be incorporated directly into the UV transform matrix. // x *= _imageScale[0][0]; // y *= _imageScale[1][1]; // TODO - I haven't updated the clamp calculations to work with scaled image width/height percentages so the below code is probably wrong, don't use it until it's fixed. if (clamp) { Log("Clamping background image translation"); // first, clamp x/y auto xScale = float(_imageWidth) / width; auto yScale = float(_imageHeight) / height; float xMin = 0; float xMax = 0; float yMin = 0; float yMax = 0; // we need to clamp x so that it can only be translated between (left side touching viewport left) and (right side touching viewport right) // if width is less than viewport, these values are 0/1-xScale respectively if (xScale < 1) { xMin = 0; xMax = 1 - xScale; // otherwise, these value are (xScale-1 and 1-xScale) } else { xMin = 1 - xScale; xMax = 0; } // do the same for y if (yScale < 1) { yMin = 0; yMax = 1 - yScale; } else { yMin = 1 - yScale; yMax = 0; } x = std::max(xMin, std::min(x, xMax)); y = std::max(yMin, std::min(y, yMax)); } // these values are then negated to account for the fact that the transform is applied to the UV coordinates, not the vertices (see image.mat). // i.e. translating the image right by 0.5 units means translating the UV coordinates left by 0.5 units. x = -x; y = -y; auto transform = math::mat4f::translation(filament::math::float3(x, y, 0.0f)) * _imageScale; _imageMaterial->setDefaultParameter("transform", transform); } FilamentViewer::~FilamentViewer() { _sceneManager->destroyAll(); for (auto view : _views) { view->setRenderTarget(nullptr); _engine->destroy(view); } _views.clear(); for(auto rt : _renderTargets) { destroyRenderTarget(rt); } _renderTargets.clear(); for (auto swapChain : _swapChains) { _engine->destroy(swapChain); } _swapChains.clear(); if (!_imageEntity.isNull()) { _engine->destroy(_imageEntity); _engine->destroy(_imageTexture); _engine->destroy(_imageVb); _engine->destroy(_imageIb); _engine->destroy(_imageMaterial); } delete _sceneManager; _engine->destroyCameraComponent(_mainCamera->getEntity()); _mainCamera = nullptr; _engine->destroy(_scene); _engine->destroy(_renderer); Engine::destroy(&_engine); delete _resourceLoaderWrapper; } Renderer *FilamentViewer::getRenderer() { return _renderer; } SwapChain *FilamentViewer::createSwapChain(const void *window) { std::lock_guard lock(_renderMutex); SwapChain *swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER); _swapChains.push_back(swapChain); return swapChain; } SwapChain *FilamentViewer::createSwapChain(uint32_t width, uint32_t height) { std::lock_guard lock(_renderMutex); SwapChain *swapChain; swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER); _swapChains.push_back(swapChain); _engine->flushAndWait(); return swapChain; } RenderTarget *FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height) { Log("Creating render target with size %d x %d", width, height); // Create filament textures and render targets (note the color buffer has the import call) auto rtColor = filament::Texture::Builder() .width(width) .height(height) .levels(1) .usage(filament::Texture::Usage::COLOR_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE) .format(filament::Texture::InternalFormat::RGBA8) .import(texture) .build(*_engine); auto rtDepth = filament::Texture::Builder() .width(width) .height(height) .levels(1) .usage(filament::Texture::Usage::DEPTH_ATTACHMENT | filament::Texture::Usage::STENCIL_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE) .format(filament::Texture::InternalFormat::DEPTH24_STENCIL8) .build(*_engine); auto rt = filament::RenderTarget::Builder() .texture(RenderTarget::AttachmentPoint::COLOR, rtColor) .texture(RenderTarget::AttachmentPoint::DEPTH, rtDepth) .build(*_engine); _renderTargets.push_back(rt); return rt; } void FilamentViewer::destroyRenderTarget(RenderTarget *renderTarget) { std::lock_guard lock(_renderMutex); auto rtDepth = renderTarget->getTexture(RenderTarget::AttachmentPoint::DEPTH); if (rtDepth) { _engine->destroy(rtDepth); } auto rtColor = renderTarget->getTexture(RenderTarget::AttachmentPoint::COLOR); if (rtColor) { _engine->destroy(rtColor); } _engine->destroy(renderTarget); auto it = std::find(_renderTargets.begin(), _renderTargets.end(), renderTarget); if (it != _renderTargets.end()) { _renderTargets.erase(it); } } void FilamentViewer::destroySwapChain(SwapChain *swapChain) { std::lock_guard lock(_renderMutex); _renderable[swapChain].clear(); auto it = std::find(_swapChains.begin(), _swapChains.end(), swapChain); if (it != _swapChains.end()) { _swapChains.erase(it); } _engine->destroy(swapChain); #ifdef __EMSCRIPTEN__ _engine->execute(); #else _engine->flushAndWait(); #endif } /// /// /// View *FilamentViewer::createView() { auto *view = _engine->createView(); view->setLayerEnabled(SceneManager::LAYERS::DEFAULT_ASSETS, true); view->setLayerEnabled(SceneManager::LAYERS::BACKGROUND, true); // skybox + image view->setLayerEnabled(SceneManager::LAYERS::OVERLAY, false); // world grid + gizmo view->setBlendMode(filament::View::BlendMode::TRANSLUCENT); view->setStencilBufferEnabled(true); view->setAmbientOcclusionOptions({.enabled = false}); view->setDynamicResolutionOptions({.enabled = true}); ACESToneMapper tm; auto colorGrading = ColorGrading::Builder().toneMapper(&tm).build(*_engine); view->setColorGrading(colorGrading); #if defined(_WIN32) view->setStereoscopicOptions({.enabled = false}); #endif view->setBloomOptions({.strength = 0, .enabled=false }); view->setShadowingEnabled(false); view->setScreenSpaceRefractionEnabled(false); view->setPostProcessingEnabled(false); view->setScene(_scene); view->setCamera(_mainCamera); _views.push_back(view); return view; } /// /// /// View *FilamentViewer::getViewAt(int32_t index) { if (index < _views.size()) { return _views[index]; } return nullptr; } /// /// /// void FilamentViewer::setMainCamera(View *view) { view->setCamera(_mainCamera); } /// /// /// EntityId FilamentViewer::getMainCamera() { return Entity::smuggle(_mainCamera->getEntity()); } void FilamentViewer::loadSkybox(const char *const skyboxPath) { removeSkybox(); if (!skyboxPath) { Log("No skybox path provided, removed skybox."); } Log("Loading skybox from path %s", skyboxPath); ResourceBuffer skyboxBuffer = _resourceLoaderWrapper->load(skyboxPath); // because this will go out of scope before the texture callback is invoked, we need to make a copy of the variable itself (not its contents) ResourceBuffer *skyboxBufferCopy = new ResourceBuffer(skyboxBuffer); if (skyboxBuffer.size <= 0) { Log("Could not load skybox resource."); return; } Log("Loaded skybox data of length %d", skyboxBuffer.size); std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, skyboxBufferCopy}; image::Ktx1Bundle *skyboxBundle = new image::Ktx1Bundle(static_cast(skyboxBuffer.data), static_cast(skyboxBuffer.size)); _skyboxTexture = ktxreader::Ktx1Reader::createTexture( _engine, *skyboxBundle, false, [](void *userdata) { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapperImpl* loader = (ResourceLoaderWrapperImpl*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; delete vec; }, callbackData); _skybox = filament::Skybox::Builder() .environment(_skyboxTexture) .build(*_engine); _skybox->setLayerMask(0xFF, 1u << SceneManager::LAYERS::BACKGROUND); _scene->setSkybox(_skybox); } void FilamentViewer::removeSkybox() { _scene->setSkybox(nullptr); if (_skybox) { _engine->destroy(_skybox); _skybox = nullptr; } if (_skyboxTexture) { _engine->destroy(_skyboxTexture); _skyboxTexture = nullptr; } } void FilamentViewer::removeIbl() { if (_indirectLight) { _engine->destroy(_indirectLight); _engine->destroy(_iblTexture); _indirectLight = nullptr; _iblTexture = nullptr; } _scene->setIndirectLight(nullptr); } void FilamentViewer::rotateIbl(const math::mat3f &matrix) { _indirectLight->setRotation(matrix); } void FilamentViewer::createIbl(float r, float g, float b, float intensity) { if (_indirectLight) { removeIbl(); } _iblTexture = Texture::Builder() .width(1) .height(1) .levels(0x01) .format(Texture::InternalFormat::RGB16F) .sampler(Texture::Sampler::SAMPLER_CUBEMAP) .build(*_engine); // Create a copy of the cubemap data float *pixelData = new float[18]{ r, g, b, r, g, b, r, g, b, r, g, b, r, g, b, r, g, b, }; Texture::PixelBufferDescriptor::Callback freeCallback = [](void *buf, size_t, void *data) { delete[] reinterpret_cast(data); }; auto pbd = Texture::PixelBufferDescriptor( pixelData, 18 * sizeof(float), Texture::Format::RGB, Texture::Type::FLOAT, freeCallback, pixelData); _iblTexture->setImage(*_engine, 0, std::move(pbd)); _indirectLight = filament::IndirectLight::Builder() .reflections(_iblTexture) .intensity(intensity) .build(*_engine); _scene->setIndirectLight(_indirectLight); } void FilamentViewer::loadIbl(const char *const iblPath, float intensity) { removeIbl(); if (iblPath) { // Load IBL. ResourceBuffer iblBuffer = _resourceLoaderWrapper->load(iblPath); // because this will go out of scope before the texture callback is invoked, we need to make a copy to the heap ResourceBuffer *iblBufferCopy = new ResourceBuffer(iblBuffer); if (iblBuffer.size == 0) { Log("Error loading IBL, resource could not be loaded."); return; } image::Ktx1Bundle *iblBundle = new image::Ktx1Bundle(static_cast(iblBuffer.data), static_cast(iblBuffer.size)); filament::math::float3 harmonics[9]; iblBundle->getSphericalHarmonics(harmonics); std::vector *callbackData = new std::vector{(void *)_resourceLoaderWrapper, iblBufferCopy}; _iblTexture = ktxreader::Ktx1Reader::createTexture( _engine, *iblBundle, false, [](void *userdata) { std::vector* vec = (std::vector*)userdata; ResourceLoaderWrapperImpl* loader = (ResourceLoaderWrapperImpl*)vec->at(0); ResourceBuffer* rb = (ResourceBuffer*) vec->at(1); loader->free(*rb); delete rb; delete vec; }, callbackData); _indirectLight = filament::IndirectLight::Builder() .reflections(_iblTexture) .irradiance(3, harmonics) .intensity(intensity) .build(*_engine); _scene->setIndirectLight(_indirectLight); Log("IBL loaded."); } } void FilamentViewer::setRenderable(View* view, SwapChain* swapChain, bool renderable) { std::lock_guard lock(_renderMutex); auto views = _renderable[swapChain]; auto it = std::find(views.begin(), views.end(), view); if(renderable) { if(it == views.end()) { views.push_back(view); } } else { if(it != views.end()) { views.erase(it); } } _renderable[swapChain] = views; } void FilamentViewer::render( uint64_t frameTimeInNanos) { _sceneManager->update(); for(auto swapChain : _swapChains) { auto views = _renderable[swapChain]; if(views.size() > 0) { bool beginFrame = _renderer->beginFrame(swapChain, frameTimeInNanos); if (beginFrame) { for(auto view : views) { _renderer->render(view); } } _renderer->endFrame(); } } #ifdef __EMSCRIPTEN__ _engine->execute(); #endif } class CaptureCallbackHandler : public filament::backend::CallbackHandler { void post(void *user, Callback callback) { callback(user); } }; void FilamentViewer::capture(View *view, uint8_t *out, bool useFence, SwapChain *swapChain, void (*onComplete)()) { Viewport const &vp = view->getViewport(); size_t pixelBufferSize = vp.width * vp.height * 4; auto callback = [](void *buf, size_t size, void *data) { auto frameCallbackData = (std::vector *)data; void *callbackPtr = frameCallbackData->at(1); delete frameCallbackData; if (callbackPtr) { void (*callback)(void) = (void (*)(void))callbackPtr; callback(); } }; Fence *fence = nullptr; if (useFence) { fence = _engine->createFence(); } auto userData = new std::vector{out, (void *)onComplete}; auto dispatcher = new CaptureCallbackHandler(); auto pbd = Texture::PixelBufferDescriptor( out, pixelBufferSize, Texture::Format::RGBA, Texture::Type::UBYTE, dispatcher, callback, userData); _renderer->beginFrame(swapChain, 0); _renderer->render(view); _renderer->readPixels(0, 0, vp.width, vp.height, std::move(pbd)); _renderer->endFrame(); #ifdef __EMSCRIPTEN__ _engine->execute(); emscripten_webgl_commit_frame(); #else _engine->flushAndWait(); #endif if (fence) { Fence::waitAndDestroy(fence); } } void FilamentViewer::capture(View *view, uint8_t *out, bool useFence, SwapChain *swapChain, RenderTarget *renderTarget, void (*onComplete)()) { if(swapChain && !_engine->isValid(swapChain)) { Log("SwapChain exists, but is not valid"); return; } int i =0 ; for(auto sc : _swapChains) { if(sc == swapChain) { Log("Using swapchain at index %d", i); } i++; } Viewport const &vp = view->getViewport(); size_t pixelBufferSize = vp.width * vp.height * 4; auto callback = [](void *buf, size_t size, void *data) { auto frameCallbackData = (std::vector *)data; void *callbackPtr = frameCallbackData->at(1); delete frameCallbackData; if (callbackPtr) { void (*callback)(void) = (void (*)(void))callbackPtr; callback(); } }; // Create a fence Fence *fence = nullptr; if (useFence) { fence = _engine->createFence(); } auto userData = new std::vector{out, (void *)onComplete}; auto dispatcher = new CaptureCallbackHandler(); auto pbd = Texture::PixelBufferDescriptor( out, pixelBufferSize, Texture::Format::RGBA, Texture::Type::UBYTE, dispatcher, callback, userData); _renderer->beginFrame(swapChain, 0); _renderer->render(view); _renderer->readPixels(renderTarget, 0, 0, vp.width, vp.height, std::move(pbd)); _renderer->endFrame(); #ifdef __EMSCRIPTEN__ _engine->execute(); emscripten_webgl_commit_frame(); #else _engine->flushAndWait(); #endif if (fence) { Fence::waitAndDestroy(fence); } } Camera *FilamentViewer::getCamera(EntityId entity) { return _engine->getCameraComponent(Entity::import(entity)); } void FilamentViewer::unprojectTexture(EntityId entityId, uint8_t *input, uint32_t inputWidth, uint32_t inputHeight, uint8_t *out, uint32_t outWidth, uint32_t outHeight) { // const auto *geometry = _sceneManager->getGeometry(entityId); // if (!geometry->uvs) // { // Log("No UVS"); // return; // } // UnprojectTexture unproject(geometry, _view->getCamera(), _engine); // TODO - check that input dimensions match viewport? // unproject.unproject(utils::Entity::import(entityId), input, out, inputWidth, inputHeight, outWidth, outHeight); } } // namespace thermion