feat!: big refactor to support multiple swapchains

This commit is contained in:
Nick Fisher
2024-09-27 18:39:20 +08:00
parent 399d447eec
commit a6d2f2ecf9
24 changed files with 752 additions and 626 deletions

View File

@@ -739,7 +739,12 @@ namespace thermion_filament
FilamentViewer::~FilamentViewer()
{
clearLights();
destroySwapChain();
for(auto swapChain : _swapChains) {
_engine->destroy(swapChain);
}
_swapChains.clear();
if (!_imageEntity.isNull())
{
_engine->destroy(_imageEntity);
@@ -760,25 +765,29 @@ namespace thermion_filament
Renderer *FilamentViewer::getRenderer() { return _renderer; }
void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height)
SwapChain* FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height)
{
std::lock_guard lock(_renderMutex);
SwapChain* swapChain;
#if TARGET_OS_IPHONE
_swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
#else
if (window)
{
_swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
Log("Created window swapchain.");
}
else
{
Log("Created headless swapchain.");
_swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
}
#endif
_swapChains.push_back(swapChain);
return swapChain;
}
void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height)
RenderTarget* FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height)
{
// Create filament textures and render targets (note the color buffer has the import call)
auto rtColor = filament::Texture::Builder()
@@ -796,40 +805,44 @@ namespace thermion_filament
.usage(filament::Texture::Usage::DEPTH_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE)
.format(filament::Texture::InternalFormat::DEPTH32F)
.build(*_engine);
_rt = filament::RenderTarget::Builder()
auto rt = filament::RenderTarget::Builder()
.texture(RenderTarget::AttachmentPoint::COLOR, rtColor)
.texture(RenderTarget::AttachmentPoint::DEPTH, rtDepth)
.build(*_engine);
_view->setRenderTarget(_rt);
Log("Created render target for texture id %ld (%u x %u)", (long)texture, width, height);
return rt;
}
void FilamentViewer::destroySwapChain()
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::setRenderTarget(RenderTarget *renderTarget) {
std::lock_guard lock(_renderMutex);
_view->setRenderTarget(renderTarget);
}
void FilamentViewer::destroySwapChain(SwapChain *swapChain)
{
if (_rt)
{
_view->setRenderTarget(nullptr);
auto rtDepth = _rt->getTexture(RenderTarget::AttachmentPoint::DEPTH);
if(rtDepth) {
_engine->destroy(rtDepth);
Log("destroyed depth");
}
auto rtColor = _rt->getTexture(RenderTarget::AttachmentPoint::COLOR);
if(rtColor) {
_engine->destroy(rtColor);
Log("destroyed color");
}
_engine->destroy(_rt);
_rt = nullptr;
}
if (_swapChain)
{
_engine->destroy(_swapChain);
_swapChain = nullptr;
Log("Swapchain destroyed.");
std::lock_guard lock(_renderMutex);
auto it = std::find(_swapChains.begin(), _swapChains.end(), swapChain);
if(it != _swapChains.end()) {
_swapChains.erase(it);
}
_engine->destroy(swapChain);
Log("Swapchain destroyed.");
#ifdef __EMSCRIPTEN__
_engine->execute();
#else
@@ -845,10 +858,10 @@ namespace thermion_filament
void FilamentViewer::removeEntity(EntityId asset)
{
mtx.lock();
_renderMutex.lock();
// todo - what if we are using a camera from this asset?
_sceneManager->remove(asset);
mtx.unlock();
_renderMutex.unlock();
}
///
@@ -987,7 +1000,6 @@ namespace thermion_filament
void FilamentViewer::removeSkybox()
{
Log("Removing skybox");
_scene->setSkybox(nullptr);
if (_skybox)
{
@@ -1114,12 +1126,13 @@ namespace thermion_filament
bool FilamentViewer::render(
uint64_t frameTimeInNanos,
SwapChain* swapChain,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data)
{
if (!_view || !_swapChain)
if (!_view || !swapChain)
{
return false;
}
@@ -1152,7 +1165,7 @@ namespace thermion_filament
}
// Render the scene, unless the renderer wants to skip the frame.
bool beginFrame = _renderer->beginFrame(_swapChain, frameTimeInNanos);
bool beginFrame = _renderer->beginFrame(swapChain, frameTimeInNanos);
if (!beginFrame)
{
_skippedFrames++;
@@ -1165,33 +1178,6 @@ namespace thermion_filament
_frameCount++;
if (_recording)
{
Viewport const &vp = _view->getViewport();
size_t pixelBufferSize = vp.width * vp.height * 4;
auto *pixelBuffer = new uint8_t[pixelBufferSize];
auto callback = [](void *buf, size_t size, void *data)
{
auto frameCallbackData = (FrameCallbackData *)data;
auto viewer = (FilamentViewer *)frameCallbackData->viewer;
viewer->savePng(buf, size, frameCallbackData->frameNumber);
delete frameCallbackData;
};
auto now = std::chrono::high_resolution_clock::now();
auto elapsed = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - _recordingStartTime).count());
auto frameNumber = uint32_t(floor(elapsed / _frameInterval));
auto userData = new FrameCallbackData{this, frameNumber};
auto pbd = Texture::PixelBufferDescriptor(
pixelBuffer, pixelBufferSize,
Texture::Format::RGBA,
Texture::Type::UBYTE, nullptr, callback, userData);
_renderer->readPixels(_rt, 0, 0, vp.width, vp.height, std::move(pbd));
}
_renderer->endFrame();
}
#ifdef __EMSCRIPTEN__
@@ -1206,9 +1192,14 @@ namespace thermion_filament
}
};
void FilamentViewer::capture(uint8_t *out, bool useFence, void (*onComplete)())
void FilamentViewer::capture(uint8_t *out, bool useFence, SwapChain* swapChain, void (*onComplete)())
{
if(!swapChain) {
Log("NO SWAPCHAIN");
return;
}
Viewport const &vp = _view->getViewport();
size_t pixelBufferSize = vp.width * vp.height * 4;
auto *pixelBuffer = new uint8_t[pixelBufferSize];
@@ -1240,18 +1231,9 @@ namespace thermion_filament
pixelBuffer, pixelBufferSize,
Texture::Format::RGBA,
Texture::Type::UBYTE, dispatcher, callback, userData);
_renderer->beginFrame(_swapChain, 0);
_renderer->render(_view);
if (_rt)
{
_renderer->readPixels(_rt, 0, 0, vp.width, vp.height, std::move(pbd));
}
else
{
_renderer->readPixels(0, 0, vp.width, vp.height, std::move(pbd));
}
_renderer->beginFrame(swapChain, 0);
_renderer->render(_view);
_renderer->readPixels(0, 0, vp.width, vp.height, std::move(pbd));
_renderer->endFrame();
#ifdef __EMSCRIPTEN__
@@ -1263,74 +1245,57 @@ namespace thermion_filament
}
}
void FilamentViewer::savePng(void *buf, size_t size, int frameNumber)
void FilamentViewer::capture(uint8_t *out, bool useFence, SwapChain* swapChain, RenderTarget* renderTarget, void (*onComplete)())
{
// std::lock_guard lock(_recordingMutex);
// if (!_recording)
// {
// delete[] static_cast<uint8_t *>(buf);
// return;
// }
if(!renderTarget) {
Log("NO SWAPCHAIN");
return;
}
Viewport const &vp = _view->getViewport();
size_t pixelBufferSize = vp.width * vp.height * 4;
auto *pixelBuffer = new uint8_t[pixelBufferSize];
auto callback = [](void *buf, size_t size, void *data)
{
auto frameCallbackData = (std::vector<void *> *)data;
uint8_t *out = (uint8_t *)(frameCallbackData->at(0));
void *callbackPtr = frameCallbackData->at(1);
std::packaged_task<void()> lambda([=]() mutable
{
int digits = 6;
std::ostringstream stringStream;
stringStream << _recordingOutputDirectory << "/output_";
stringStream << std::setfill('0') << std::setw(digits);
stringStream << std::to_string(frameNumber);
stringStream << ".png";
memcpy(out, buf, size);
delete frameCallbackData;
if(callbackPtr) {
void (*callback)(void) = (void (*)(void))callbackPtr;
callback();
}
};
std::string filename = stringStream.str();
// Create a fence
Fence* fence = nullptr;
if(useFence) {
fence = _engine->createFence();
}
std::ofstream wf(filename, std::ios::out | std::ios::binary);
auto userData = new std::vector<void *>{out, (void *)onComplete};
LinearImage image(toLinearWithAlpha<uint8_t>(vp.width, vp.height, vp.width * 4,
static_cast<uint8_t *>(buf)));
auto dispatcher = new CaptureCallbackHandler();
auto result = image::ImageEncoder::encode(
wf, image::ImageEncoder::Format::PNG, image, std::string(""), std::string(""));
auto pbd = Texture::PixelBufferDescriptor(
pixelBuffer, 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();
delete[] static_cast<uint8_t *>(buf);
if (!result)
{
Log("Failed to encode");
}
wf.close();
if (!wf.good())
{
Log("Write failed!");
} });
_tp->add_task(lambda);
#ifdef __EMSCRIPTEN__
_engine->execute();
emscripten_webgl_commit_frame();
#endif
if(fence) {
Fence::waitAndDestroy(fence);
}
void FilamentViewer::setRecordingOutputDirectory(const char *path)
{
_recordingOutputDirectory = std::string(path);
auto outputDirAsPath = std::filesystem::path(path);
if (!std::filesystem::is_directory(outputDirAsPath))
{
std::filesystem::create_directories(outputDirAsPath);
}
}
void FilamentViewer::setRecording(bool recording)
{
// std::lock_guard lock(_recordingMutex);
if (recording)
{
_tp = new thermion_filament::ThreadPool(16);
_recordingStartTime = std::chrono::high_resolution_clock::now();
}
else
{
delete _tp;
}
this->_recording = recording;
}
Camera* FilamentViewer::getCamera(EntityId entity) {
@@ -1381,7 +1346,7 @@ namespace thermion_filament
void FilamentViewer::grabBegin(float x, float y, bool pan)
{
if (!_view || !_mainCamera || !_swapChain)
if (!_view || !_mainCamera)
{
Log("View not ready, ignoring grab");
return;
@@ -1395,7 +1360,7 @@ namespace thermion_filament
void FilamentViewer::grabUpdate(float x, float y)
{
if (!_view || !_swapChain)
if (!_view )
{
Log("View not ready, ignoring grab");
return;
@@ -1413,7 +1378,7 @@ namespace thermion_filament
void FilamentViewer::grabEnd()
{
if (!_view || !_mainCamera || !_swapChain)
if (!_view || !_mainCamera )
{
Log("View not ready, ignoring grab");
return;

View File

@@ -56,9 +56,23 @@ extern "C"
return reinterpret_cast<TEngine*>(engine);
}
EMSCRIPTEN_KEEPALIVE void create_render_target(TViewer *viewer, intptr_t texture, uint32_t width, uint32_t height)
EMSCRIPTEN_KEEPALIVE TRenderTarget* Viewer_createRenderTarget(TViewer *tViewer, intptr_t texture, uint32_t width, uint32_t height)
{
((FilamentViewer *)viewer)->createRenderTarget(texture, width, height);
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto renderTarget = viewer->createRenderTarget(texture, width, height);
return reinterpret_cast<TRenderTarget*>(renderTarget);
}
EMSCRIPTEN_KEEPALIVE void Viewer_destroyRenderTarget(TViewer *tViewer, TRenderTarget* tRenderTarget) {
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto renderTarget = reinterpret_cast<RenderTarget*>(tRenderTarget);
viewer->destroyRenderTarget(renderTarget);
}
EMSCRIPTEN_KEEPALIVE void Viewer_setRenderTarget(TViewer *tViewer, TRenderTarget* tRenderTarget) {
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto renderTarget = reinterpret_cast<RenderTarget*>(tRenderTarget);
viewer->setRenderTarget(renderTarget);
}
EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer(TViewer *viewer)
@@ -349,18 +363,22 @@ extern "C"
cam->setModelMatrix(mat);
}
EMSCRIPTEN_KEEPALIVE bool render(
TViewer *viewer,
EMSCRIPTEN_KEEPALIVE bool Viewer_render(
TViewer *tViewer,
TSwapChain *tSwapChain,
uint64_t frameTimeInNanos,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data)
{
return ((FilamentViewer *)viewer)->render(frameTimeInNanos, pixelBuffer, callback, data);
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
return viewer->render(frameTimeInNanos, swapChain, pixelBuffer, callback, data);
}
EMSCRIPTEN_KEEPALIVE void capture(
TViewer *viewer,
EMSCRIPTEN_KEEPALIVE void Viewer_capture(
TViewer *tViewer,
TSwapChain* tSwapChain,
uint8_t *pixelBuffer,
void (*callback)(void))
{
@@ -369,7 +387,27 @@ extern "C"
#else
bool useFence = false;
#endif
((FilamentViewer *)viewer)->capture(pixelBuffer, useFence, callback);
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
viewer->capture(pixelBuffer, useFence, swapChain, callback);
};
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTarget(
TViewer *tViewer,
TSwapChain* tSwapChain,
TRenderTarget *tRenderTarget,
uint8_t *pixelBuffer,
void (*callback)(void))
{
#ifdef __EMSCRIPTEN__
bool useFence = true;
#else
bool useFence = false;
#endif
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
auto renderTarget = reinterpret_cast<RenderTarget*>(tRenderTarget);
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
viewer->capture(pixelBuffer, useFence, swapChain, renderTarget, callback);
};
EMSCRIPTEN_KEEPALIVE void set_frame_interval(
@@ -379,14 +417,17 @@ extern "C"
((FilamentViewer *)viewer)->setFrameInterval(frameInterval);
}
EMSCRIPTEN_KEEPALIVE void destroy_swap_chain(TViewer *viewer)
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChain(TViewer *tViewer, TSwapChain* tSwapChain)
{
((FilamentViewer *)viewer)->destroySwapChain();
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
viewer->destroySwapChain(swapChain);
}
EMSCRIPTEN_KEEPALIVE void create_swap_chain(TViewer *viewer, const void *const window, uint32_t width, uint32_t height)
{
((FilamentViewer *)viewer)->createSwapChain(window, width, height);
EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createSwapChain(TViewer *tViewer, const void *const window, uint32_t width, uint32_t height) {
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
auto swapChain = viewer->createSwapChain(window, width, height);
return reinterpret_cast<TSwapChain*>(swapChain);
}
EMSCRIPTEN_KEEPALIVE void update_viewport(TViewer *viewer, uint32_t width, uint32_t height)
@@ -837,16 +878,6 @@ extern "C"
return ((SceneManager *)sceneManager)->getEntityNameAt(target, index, renderableOnly);
}
EMSCRIPTEN_KEEPALIVE void set_recording(TViewer *viewer, bool recording)
{
((FilamentViewer *)viewer)->setRecording(recording);
}
EMSCRIPTEN_KEEPALIVE void set_recording_output_directory(TViewer *viewer, const char *outputDirectory)
{
((FilamentViewer *)viewer)->setRecordingOutputDirectory(outputDirectory);
}
EMSCRIPTEN_KEEPALIVE void ios_dummy()
{
Log("Dummy called");

View File

@@ -49,8 +49,8 @@ public:
~RenderLoop()
{
_render = false;
_stop = true;
target = nullptr;
_cv.notify_one();
#ifdef __EMSCRIPTEN__
pthread_join(t, NULL);
@@ -82,20 +82,20 @@ public:
}
}
void requestFrame(void (*callback)())
void requestFrame(SwapChain* swapChain, void (*callback)())
{
this->_render = true;
this->target = swapChain;
this->_requestFrameRenderCallback = callback;
}
void iter()
{
std::unique_lock<std::mutex> lock(_mutex);
if (_render)
if (target)
{
doRender();
doRender(target);
this->_requestFrameRenderCallback();
_render = false;
target = nullptr;
// Calculate and print FPS
auto currentTime = std::chrono::high_resolution_clock::now();
@@ -123,7 +123,7 @@ public:
}
_cv.wait_for(lock, std::chrono::microseconds(1000), [this]
{ return !_tasks.empty() || _stop || _render; });
{ return !_tasks.empty() || _stop; });
if (_stop)
return;
@@ -169,14 +169,14 @@ public:
{
std::packaged_task<void()> lambda([=]() mutable
{
_render = false;
target = nullptr;
_viewer = nullptr;
destroy_filament_viewer(reinterpret_cast<TViewer*>(viewer)); });
auto fut = add_task(lambda);
fut.wait();
}
bool doRender()
bool doRender(SwapChain *swapChain)
{
#ifdef __EMSCRIPTEN__
if (emscripten_is_webgl_context_lost(_context) == EM_TRUE)
@@ -187,7 +187,8 @@ public:
return;
}
#endif
auto rendered = render(_viewer, 0, nullptr, nullptr, nullptr);
TSwapChain *tSwapChain = reinterpret_cast<TSwapChain*>(swapChain);
auto rendered = Viewer_render(_viewer, tSwapChain, 0, nullptr, nullptr, nullptr);
if (_renderCallback)
{
_renderCallback(_renderCallbackOwner);
@@ -217,7 +218,7 @@ public:
}
public:
std::atomic_bool _render = false;
SwapChain* target;
private:
void(*_requestFrameRenderCallback)() = nullptr;
@@ -270,16 +271,31 @@ extern "C"
_rl = nullptr;
}
EMSCRIPTEN_KEEPALIVE void create_swap_chain_render_thread(TViewer *viewer,
EMSCRIPTEN_KEEPALIVE void Viewer_createSwapChainRenderThread(TViewer *viewer,
void *const surface,
uint32_t width,
uint32_t height,
void (*onComplete)())
void (*onComplete)(TSwapChain*))
{
std::packaged_task<void()> lambda(
[=]() mutable
{
create_swap_chain(viewer, surface, width, height);
auto *swapChain = Viewer_createSwapChain(viewer, surface, width, height);
#ifdef __EMSCRIPTEN__
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete);
#else
onComplete(swapChain);
#endif
});
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChainRenderThread(TViewer *viewer, TSwapChain *swapChain, void (*onComplete)())
{
std::packaged_task<void()> lambda(
[=]() mutable
{
Viewer_destroySwapChain(viewer, swapChain);
#ifdef __EMSCRIPTEN__
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete);
#else
@@ -289,40 +305,8 @@ extern "C"
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void destroy_swap_chain_render_thread(TViewer *viewer, void (*onComplete)())
{
std::packaged_task<void()> lambda(
[=]() mutable
{
destroy_swap_chain(viewer);
#ifdef __EMSCRIPTEN__
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete);
#else
onComplete();
#endif
});
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void create_render_target_render_thread(TViewer *viewer,
intptr_t nativeTextureId,
uint32_t width,
uint32_t height,
void (*onComplete)())
{
std::packaged_task<void()> lambda([=]() mutable
{
create_render_target(viewer, nativeTextureId, width, height);
#ifdef __EMSCRIPTEN__
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete);
#else
onComplete();
#endif
});
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void request_frame_render_thread(TViewer *viewer, void(*onComplete)())
EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, TSwapChain* tSwapChain, void(*onComplete)())
{
if (!_rl)
{
@@ -330,7 +314,7 @@ extern "C"
}
else
{
_rl->requestFrame(onComplete);
_rl->requestFrame(reinterpret_cast<SwapChain*>(tSwapChain), onComplete);
}
}
@@ -343,17 +327,24 @@ extern "C"
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void render_render_thread(TViewer *viewer)
EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TSwapChain *tSwapChain)
{
std::packaged_task<void()> lambda([=]() mutable
{ _rl->doRender(); });
{ _rl->doRender(reinterpret_cast<SwapChain*>(tSwapChain)); });
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void capture_render_thread(TViewer *viewer, uint8_t *pixelBuffer, void (*onComplete)())
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderThread(TViewer *viewer, TSwapChain *tSwapChain, uint8_t *pixelBuffer, void (*onComplete)())
{
std::packaged_task<void()> lambda([=]() mutable
{ capture(viewer, pixelBuffer, onComplete); });
{ Viewer_capture(viewer, tSwapChain, pixelBuffer, onComplete); });
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTargetRenderThread(TViewer *viewer, TSwapChain *tSwapChain, TRenderTarget* tRenderTarget, uint8_t *pixelBuffer, void (*onComplete)())
{
std::packaged_task<void()> lambda([=]() mutable
{ Viewer_captureRenderTarget(viewer, tSwapChain, tRenderTarget, pixelBuffer, onComplete); });
auto fut = _rl->add_task(lambda);
}