LCOV - code coverage report
Current view: top level - xenolith/backend/vkgui - XLVkView.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 434 749 57.9 %
Date: 2024-05-12 00:16:13 Functions: 55 92 59.8 %

          Line data    Source code
       1             : /**
       2             : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
       3             : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
       4             : 
       5             : Permission is hereby granted, free of charge, to any person obtaining a copy
       6             : of this software and associated documentation files (the "Software"), to deal
       7             : in the Software without restriction, including without limitation the rights
       8             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       9             : copies of the Software, and to permit persons to whom the Software is
      10             : furnished to do so, subject to the following conditions:
      11             : 
      12             : The above copyright notice and this permission notice shall be included in
      13             : all copies or substantial portions of the Software.
      14             : 
      15             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      16             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      17             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      18             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      19             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      20             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      21             : THE SOFTWARE.
      22             : **/
      23             : 
      24             : #include "XLVkView.h"
      25             : #include "XLVkLoop.h"
      26             : #include "XLVkDevice.h"
      27             : #include "XLVkTextureSet.h"
      28             : #include "XLVkSwapchain.h"
      29             : #include "XLVkGuiConfig.h"
      30             : #include "XLDirector.h"
      31             : #include "XLCoreImageStorage.h"
      32             : #include "XLCoreFrameQueue.h"
      33             : #include "XLCoreFrameRequest.h"
      34             : #include "XLCoreFrameCache.h"
      35             : #include "SPBitmap.h"
      36             : 
      37             : #define XL_VKVIEW_DEBUG 0
      38             : 
      39             : #ifndef XL_VKAPI_LOG
      40             : #define XL_VKAPI_LOG(...)
      41             : #endif
      42             : 
      43             : #if XL_VKVIEW_DEBUG
      44             : #define XL_VKVIEW_LOG(...) log::debug("vk::View", __VA_ARGS__)
      45             : #else
      46             : #define XL_VKVIEW_LOG(...)
      47             : #endif
      48             : 
      49             : namespace STAPPLER_VERSIONIZED stappler::xenolith::vk {
      50             : 
      51          10 : View::~View() {
      52             : 
      53          10 : }
      54             : 
      55          10 : bool View::init(Application &loop, const Device &dev, ViewInfo &&info) {
      56          10 :         if (!xenolith::View::init(loop, move(info))) {
      57           0 :                 return false;
      58             :         }
      59             : 
      60          10 :         _threadName = toString("View:", info.name);
      61          10 :         _instance = static_cast<Instance *>(loop.getGlLoop()->getGlInstance().get());
      62          10 :         _device = const_cast<Device *>(static_cast<const Device *>(&dev));
      63          10 :         _director = Rc<Director>::create(_mainLoop, _constraints, this);
      64          10 :         if (_info.onCreated) {
      65          10 :                 _mainLoop->performOnMainThread([this, c = _constraints] {
      66          10 :                         _info.onCreated(*this, c);
      67          10 :                 }, this);
      68             :         } else {
      69           0 :                 run();
      70             :         }
      71          10 :         return true;
      72             : }
      73             : 
      74          10 : void View::threadInit() {
      75          10 :         _init = true;
      76          10 :         _running = true;
      77          10 :         _avgFrameInterval.reset(0);
      78             : 
      79          10 :         _refId = retain();
      80          10 :         thread::ThreadInfo::setThreadInfo(_threadName);
      81          10 :         _threadId = std::this_thread::get_id();
      82          10 :         _shouldQuit.test_and_set();
      83             : 
      84          10 :         auto info = getSurfaceOptions();
      85          10 :         auto cfg = _info.selectConfig(*this, info);
      86             : 
      87          10 :         if (info.surfaceDensity != 1.0f) {
      88           0 :                 _constraints.density = _info.density * info.surfaceDensity;
      89             :         }
      90             : 
      91          10 :         createSwapchain(info, move(cfg), cfg.presentMode);
      92             : 
      93          10 :         if (_initImage && !_options.followDisplayLink) {
      94          10 :                 presentImmediate(move(_initImage), nullptr);
      95          10 :                 _initImage = nullptr;
      96             :         }
      97             : 
      98          10 :         mapWindow();
      99          10 : }
     100             : 
     101          10 : void View::threadDispose() {
     102          10 :         clearImages();
     103          10 :         _running = false;
     104             : 
     105          10 :         if (_options.renderImageOffscreen) {
     106             :                 // offscreen does not need swapchain outside of view thread
     107           0 :                 _swapchain->invalidate();
     108             :         }
     109          10 :         _swapchain = nullptr;
     110          10 :         _surface = nullptr;
     111             : 
     112          10 :         finalize();
     113             : 
     114          10 :         if (_threadStarted) {
     115          10 :                 _thread.detach();
     116          10 :                 _threadStarted = false;
     117             :         }
     118             : 
     119             : #if SP_REF_DEBUG
     120             :         auto refcount = getReferenceCount();
     121             :         release(_refId);
     122             :         if (refcount > 1) {
     123             :                 this->foreachBacktrace([] (uint64_t id, Time time, const std::vector<std::string> &vec) {
     124             :                         StringStream stream;
     125             :                         stream << "[" << id << ":" << time.toHttp<Interface>() << "]:\n";
     126             :                         for (auto &it : vec) {
     127             :                                 stream << "\t" << it << "\n";
     128             :                         }
     129             :                         log::debug("vk::View", stream.str());
     130             :                 });
     131             :         } else {
     132             :                 _glLoop = nullptr;
     133             :         }
     134             : #else
     135          10 :         release(_refId);
     136             : #endif
     137          10 : }
     138             : 
     139      217166 : void View::update(bool displayLink) {
     140      217166 :         xenolith::View::update(displayLink);
     141             : 
     142      217166 :         updateFences();
     143             : 
     144      217166 :         if (displayLink && _options.followDisplayLink) {
     145             :                 // ignore present windows
     146           0 :                 for (auto &it : _scheduledPresent) {
     147           0 :                         runScheduledPresent(move(it));
     148             :                 }
     149           0 :                 _scheduledPresent.clear();
     150             :         }
     151             : 
     152             :         do {
     153      217166 :                 auto it = _fenceImages.begin();
     154      217166 :                 while (it != _fenceImages.end()) {
     155           0 :                         if (_fenceOrder < (*it)->getOrder()) {
     156           0 :                                 _scheduledImages.emplace_back(move(*it));
     157           0 :                                 it = _fenceImages.erase(it);
     158             :                         } else {
     159           0 :                                 ++ it;
     160             :                         }
     161             :                 }
     162             :         } while (0);
     163             : 
     164      217166 :         acquireScheduledImage();
     165             : 
     166      217166 :         auto clock = xenolith::platform::clock(core::ClockType::Monotonic);
     167             : 
     168      217166 :         if (!_options.followDisplayLink) {
     169      217166 :                 auto it = _scheduledPresent.begin();
     170      388290 :                 while (it != _scheduledPresent.end()) {
     171      171124 :                         if (!(*it)->getPresentWindow() || (*it)->getPresentWindow() < clock) {
     172        4019 :                                 runScheduledPresent(move(*it));
     173        4019 :                                 it = _scheduledPresent.erase(it);
     174             :                         } else {
     175      167105 :                                 ++ it;
     176             :                         }
     177             :                 }
     178             :         }
     179             : 
     180      217166 :         if (_swapchain && !_swapchainInvalidated && _scheduledTime < clock && _options.renderOnDemand) {
     181      206835 :                 auto acquiredImages = _swapchain->getAcquiredImagesCount();
     182      206835 :                 if (_swapchain && _framesInProgress == 0 && acquiredImages == 0) {
     183             :                         XL_VKVIEW_LOG("update - scheduleNextImage");
     184           0 :                         scheduleNextImage(0, true);
     185             :                 } else {
     186             :                         //log::verbose("vk::View", "Frame dropped: ", acquiredImages, " ", _framesInProgress);
     187             :                 }
     188             :         }
     189      217166 : }
     190             : 
     191          10 : void View::close() {
     192          10 :         xenolith::View::close();
     193          10 : }
     194             : 
     195          10 : void View::run() {
     196          10 :         auto refId = retain();
     197          10 :         _threadStarted = true;
     198          10 :         _thread = std::thread(View::workerThread, this, nullptr);
     199          10 :         performOnThread([this, refId] {
     200          10 :                 release(refId);
     201          10 :         }, this);
     202          10 : }
     203             : 
     204          10 : void View::runWithQueue(const Rc<RenderQueue> &queue) {
     205             :         XL_VKVIEW_LOG("runWithQueue");
     206          10 :         auto a = queue->getPresentImageOutput();
     207          10 :         if (!a) {
     208           0 :                 a = queue->getTransferImageOutput();
     209             :         }
     210          10 :         if (!a) {
     211           0 :                 log::error("vk::View", "Fail to run view with queue '", queue->getName(),  "': no usable output attachments found");
     212           0 :                 return;
     213             :         }
     214             : 
     215          10 :         log::verbose("View", "View::runWithQueue");
     216             : 
     217          10 :         auto req = Rc<FrameRequest>::create(queue, _frameEmitter, _constraints);
     218          10 :         req->setOutput(a, [this] (core::FrameAttachmentData &attachment, bool success, Ref *data) {
     219          10 :                 log::verbose("View", "View::runWithQueue - output");
     220          10 :                 if (success) {
     221          10 :                         _initImage = move(attachment.image);
     222             :                 }
     223          10 :                 run();
     224          10 :                 return true;
     225             :         }, this);
     226             : 
     227          10 :         _mainLoop->performOnMainThread([this, req] {
     228          10 :                 if (_director->acquireFrame(req)) {
     229          10 :                         _glLoop->performOnGlThread([this, req = move(req)] () mutable {
     230          10 :                                 _frameEmitter->submitNextFrame(move(req));
     231          10 :                         });
     232             :                 }
     233          10 :         }, this);
     234          10 : }
     235             : 
     236           0 : void View::onAdded(Device &dev) {
     237           0 :         std::unique_lock<Mutex> lock(_mutex);
     238           0 :         _device = &dev;
     239           0 :         _running = true;
     240           0 : }
     241             : 
     242           0 : void View::onRemoved() {
     243           0 :         std::unique_lock<Mutex> lock(_mutex);
     244           0 :         _running = false;
     245           0 :         _callbacks.clear();
     246           0 :         lock.unlock();
     247           0 :         if (_threadStarted) {
     248           0 :                 _thread.join();
     249             :         }
     250           0 : }
     251             : 
     252          10 : void View::deprecateSwapchain(bool fast) {
     253             :         XL_VKVIEW_LOG("deprecateSwapchain");
     254          10 :         if (!_running) {
     255           0 :                 return;
     256             :         }
     257          10 :         performOnThread([this, fast] {
     258          10 :                 if (!_swapchain) {
     259           0 :                         return;
     260             :                 }
     261             : 
     262          10 :                 _swapchain->deprecate(fast);
     263          10 :                 auto it = _scheduledPresent.begin();
     264          10 :                 while (it != _scheduledPresent.end()) {
     265           0 :                         runScheduledPresent(move(*it));
     266           0 :                         it = _scheduledPresent.erase(it);
     267             :                 }
     268             : 
     269          10 :                 if (!_blockSwapchainRecreation && _swapchain->getAcquiredImagesCount() == 0) {
     270           0 :                         recreateSwapchain(_swapchain->getRebuildMode());
     271             :                 }
     272             :         }, this, true);
     273             : }
     274             : 
     275        4630 : bool View::present(Rc<ImageStorage> &&object) {
     276             :         XL_VKVIEW_LOG("present");
     277        4630 :         if (object->isSwapchainImage()) {
     278        4630 :                 if (_options.followDisplayLink) {
     279           0 :                         performOnThread([this, object = move(object)] () mutable {
     280           0 :                                 schedulePresent((SwapchainImage *)object.get(), 0);
     281           0 :                         }, this);
     282           0 :                         return false;
     283             :                 }
     284        4630 :                 auto clock = xenolith::platform::clock(core::ClockType::Monotonic);
     285        4630 :                 auto img = (SwapchainImage *)object.get();
     286        4630 :                 if (!img->getPresentWindow() || img->getPresentWindow() < clock) {
     287         611 :                         if (_options.presentImmediate) {
     288           0 :                                 performOnThread([this, object = move(object)] () mutable {
     289           0 :                                         auto queue = _device->tryAcquireQueueSync(QueueOperations::Present, true);
     290           0 :                                         auto img = (SwapchainImage *)object.get();
     291           0 :                                         if (img->getSwapchain() == _swapchain && img->isSubmitted()) {
     292           0 :                                                 presentWithQueue(*queue, move(object));
     293             :                                         }
     294           0 :                                         _glLoop->performOnGlThread([this,  queue = move(queue)] () mutable {
     295           0 :                                                 _device->releaseQueue(move(queue));
     296           0 :                                         }, this);
     297           0 :                                 }, this);
     298           0 :                                 return false;
     299             :                         }
     300         611 :                         auto queue = _device->tryAcquireQueueSync(QueueOperations::Present, false);
     301         611 :                         if (queue) {
     302         611 :                                 performOnThread([this, queue = move(queue), object = move(object)] () mutable {
     303         611 :                                         auto img = (SwapchainImage *)object.get();
     304         611 :                                         if (img->getSwapchain() == _swapchain && img->isSubmitted()) {
     305         611 :                                                 presentWithQueue(*queue, move(object));
     306             :                                         }
     307         611 :                                         _glLoop->performOnGlThread([this,  queue = move(queue)] () mutable {
     308         611 :                                                 _device->releaseQueue(move(queue));
     309         611 :                                         }, this);
     310         611 :                                 }, this);
     311             :                         } else {
     312           0 :                                 _device->acquireQueue(QueueOperations::Present, *(Loop *)_glLoop.get(),
     313           0 :                                                 [this, object = move(object)] (Loop &, const Rc<DeviceQueue> &queue) mutable {
     314           0 :                                         performOnThread([this, queue, object = move(object)] () mutable {
     315           0 :                                                 auto img = (SwapchainImage *)object.get();
     316           0 :                                                 if (img->getSwapchain() == _swapchain && img->isSubmitted()) {
     317           0 :                                                         presentWithQueue(*queue, move(object));
     318             :                                                 }
     319           0 :                                                 _glLoop->performOnGlThread([this,  queue = move(queue)] () mutable {
     320           0 :                                                         _device->releaseQueue(move(queue));
     321           0 :                                                 }, this);
     322           0 :                                         }, this);
     323           0 :                                 }, [this] (Loop &) {
     324           0 :                                         invalidate();
     325           0 :                                 }, this);
     326             :                         }
     327         611 :                 } else {
     328        4019 :                         performOnThread([this, object = move(object), t = img->getPresentWindow() - clock] () mutable {
     329        4019 :                                 schedulePresent((SwapchainImage *)object.get(), t);
     330        4019 :                         }, this, true);
     331             :                 }
     332             :         } else {
     333           0 :                 if (!_options.renderImageOffscreen) {
     334           0 :                         return true;
     335             :                 }
     336           0 :                 auto gen = _gen;
     337           0 :                 performOnThread([this, object = move(object), gen] () mutable {
     338           0 :                         presentImmediate(move(object), [this, gen] (bool success) {
     339           0 :                                 if (gen == _gen) {
     340             :                                         XL_VKVIEW_LOG("present - scheduleNextImage");
     341           0 :                                         scheduleNextImage(0, false);
     342             :                                 }
     343           0 :                         });
     344           0 :                         if (_swapchain->isDeprecated()) {
     345           0 :                                 recreateSwapchain(_swapchain->getRebuildMode());
     346             :                         }
     347           0 :                 }, this);
     348           0 :                 return true;
     349             :         }
     350        4630 :         return false;
     351             : }
     352             : 
     353          10 : bool View::presentImmediate(Rc<ImageStorage> &&object, Function<void(bool)> &&scheduleCb) {
     354             :         XL_VKVIEW_LOG("presentImmediate: ", _framesInProgress);
     355          10 :         if (!_swapchain) {
     356           0 :                 return false;
     357             :         }
     358             : 
     359          10 :         auto ops = QueueOperations::Present;
     360          10 :         auto dev = (Device *)_device.get();
     361             : 
     362             :         VkFilter filter;
     363          10 :         if (!isImagePresentable(*object->getImage(), filter)) {
     364           0 :                 return false;
     365             :         }
     366             : 
     367          10 :         Rc<DeviceQueue> queue;
     368          10 :         Rc<CommandPool> pool;
     369          10 :         Rc<Fence> presentFence;
     370             : 
     371          10 :         Rc<Image> sourceImage = (Image *)object->getImage().get();
     372          10 :         Rc<ImageStorage> targetImage;
     373             : 
     374          10 :         Vector<const CommandBuffer *> buffers;
     375          10 :         Loop *loop = (Loop *)_glLoop.get();
     376             : 
     377           0 :         auto cleanup = [&] {
     378           0 :                 if (presentFence) {
     379           0 :                         presentFence = nullptr;
     380             :                 }
     381           0 :                 if (pool) {
     382           0 :                         dev->releaseCommandPoolUnsafe(move(pool));
     383           0 :                         pool = nullptr;
     384             :                 }
     385           0 :                 if (queue) {
     386           0 :                         dev->releaseQueue(move(queue));
     387           0 :                         queue = nullptr;
     388             :                 }
     389           0 :                 return false;
     390          10 :         };
     391             : 
     392             : #if XL_VKAPI_DEBUG
     393             :         auto t = xenolith::platform::clock(core::ClockType::Monotonic);
     394             : #endif
     395             : 
     396          10 :         if (_options.waitOnSwapchainPassFence) {
     397           0 :                 waitForFences(_frameOrder);
     398             :         }
     399             : 
     400             :         XL_VKAPI_LOG("[PresentImmediate] [waitForFences] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     401             : 
     402          10 :         if (!scheduleCb) {
     403          10 :                 presentFence = loop->acquireFence(0, false);
     404             :         }
     405             : 
     406          10 :         auto swapchainAcquiredImage = _swapchain->acquire(true, presentFence);;
     407          10 :         if (!swapchainAcquiredImage) {
     408             :                 XL_VKAPI_LOG("[PresentImmediate] [acquire-failed] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     409           0 :                 if (presentFence) {
     410           0 :                         presentFence->schedule(*loop);
     411             :                 }
     412           0 :                 return cleanup();
     413             :         }
     414             : 
     415          10 :         targetImage = Rc<SwapchainImage>::create(Rc<SwapchainHandle>(_swapchain), *swapchainAcquiredImage->data, move(swapchainAcquiredImage->sem));
     416             : 
     417             :         XL_VKAPI_LOG("[PresentImmediate] [acquire] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     418             : 
     419          10 :         pool = dev->acquireCommandPool(ops);
     420             : 
     421          10 :         auto buf = pool->recordBuffer(*dev, [&] (CommandBuffer &buf) {
     422          10 :                 auto targetImageObj = (Image *)targetImage->getImage().get();
     423          10 :                 auto sourceLayout = VkImageLayout(object->getLayout());
     424             : 
     425          10 :                 Vector<ImageMemoryBarrier> inputImageBarriers;
     426          10 :                 inputImageBarriers.emplace_back(ImageMemoryBarrier(targetImageObj,
     427             :                         VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
     428             :                         VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL));
     429             : 
     430          10 :                 Vector<ImageMemoryBarrier> outputImageBarriers;
     431          10 :                 outputImageBarriers.emplace_back(ImageMemoryBarrier(targetImageObj,
     432             :                         VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT,
     433             :                         VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR));
     434             : 
     435          10 :                 if (sourceLayout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
     436           0 :                         inputImageBarriers.emplace_back(ImageMemoryBarrier(sourceImage,
     437             :                                 VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
     438             :                                 sourceLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL));
     439             :                 }
     440             : 
     441          10 :                 if (!inputImageBarriers.empty()) {
     442          10 :                         buf.cmdPipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, inputImageBarriers);
     443             :                 }
     444             : 
     445          10 :                 buf.cmdCopyImage(sourceImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, targetImageObj, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, filter);
     446             : 
     447          10 :                 if (!outputImageBarriers.empty()) {
     448          10 :                         buf.cmdPipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, outputImageBarriers);
     449             :                 }
     450             : 
     451          10 :                 return true;
     452          20 :         });
     453             : 
     454          10 :         buffers.emplace_back(buf);
     455             : 
     456          10 :         core::FrameSync frameSync;
     457          10 :         object->rearmSemaphores(*loop);
     458             : 
     459          20 :         frameSync.waitAttachments.emplace_back(core::FrameSyncAttachment{nullptr, object->getWaitSem(),
     460          10 :                 object.get(), core::PipelineStage::Transfer});
     461          20 :         frameSync.waitAttachments.emplace_back(core::FrameSyncAttachment{nullptr, targetImage->getWaitSem(),
     462          10 :                 targetImage.get(), core::PipelineStage::Transfer});
     463             : 
     464          20 :         frameSync.signalAttachments.emplace_back(core::FrameSyncAttachment{nullptr, targetImage->getSignalSem(),
     465          10 :                 targetImage.get()});
     466             : 
     467             :         XL_VKAPI_LOG("[PresentImmediate] [writeBuffers] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     468             : 
     469          10 :         if (presentFence) {
     470          10 :                 presentFence->check(*(Loop *)_glLoop.get(), false);
     471             :         }
     472             : 
     473             :         XL_VKAPI_LOG("[PresentImmediate] [acquireFence] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     474             : 
     475          10 :         queue = dev->tryAcquireQueueSync(ops, true);
     476          10 :         if (!queue) {
     477           0 :                 return cleanup();
     478             :         }
     479             : 
     480             :         XL_VKAPI_LOG("[PresentImmediate] [acquireQueue] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     481             : 
     482          10 :         if (!presentFence) {
     483           0 :                 presentFence = loop->acquireFence(0, false);
     484             :         }
     485             : 
     486          10 :         if (!queue->submit(frameSync, *presentFence, *pool, buffers)) {
     487           0 :                 return cleanup();
     488             :         }
     489             : 
     490             :         XL_VKAPI_LOG("[PresentImmediate] [submit] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     491             : 
     492          10 :         auto result = _swapchain->present(*queue, targetImage);
     493          10 :         updateFrameInterval();
     494             : 
     495             :         XL_VKAPI_LOG("[PresentImmediate] [present] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     496             : 
     497          10 :         if (result == VK_SUCCESS) {
     498          10 :                 if (queue) {
     499          10 :                         dev->releaseQueue(move(queue));
     500          10 :                         queue = nullptr;
     501             :                 }
     502          10 :                 if (scheduleCb) {
     503           0 :                         pool->autorelease(object);
     504           0 :                         presentFence->addRelease([dev, pool = pool ? move(pool) : nullptr, scheduleCb = move(scheduleCb), object = move(object), loop] (bool success) mutable {
     505           0 :                                 if (pool) {
     506           0 :                                         dev->releaseCommandPoolUnsafe(move(pool));
     507             :                                 }
     508           0 :                                 loop->releaseImage(move(object));
     509           0 :                                 scheduleCb(success);
     510           0 :                         }, this, "View::presentImmediate::releaseCommandPoolUnsafe");
     511           0 :                         scheduleFence(move(presentFence));
     512             :                 } else {
     513          10 :                         presentFence->check(*((Loop *)_glLoop.get()), false);
     514          10 :                         dev->releaseCommandPoolUnsafe(move(pool));
     515          10 :                         loop->releaseImage(move(object));
     516             :                 }
     517             :                 XL_VKAPI_LOG("[PresentImmediate] [presentFence] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     518          10 :                 presentFence = nullptr;
     519             :                 XL_VKAPI_LOG("[PresentImmediate] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     520          10 :                 return true;
     521             :         } else {
     522           0 :                 if (queue) {
     523           0 :                         queue->waitIdle();
     524           0 :                         dev->releaseQueue(move(queue));
     525           0 :                         queue = nullptr;
     526             :                 }
     527           0 :                 if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR) {
     528           0 :                         _swapchain->deprecate(false);
     529           0 :                         presentFence->check(*loop, false);
     530             :                         XL_VKAPI_LOG("[PresentImmediate] [presentFence] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     531           0 :                         presentFence = nullptr;
     532             : 
     533           0 :                         dev->releaseCommandPoolUnsafe(move(pool));
     534           0 :                         pool = nullptr;
     535             :                 }
     536             :                 XL_VKAPI_LOG("[PresentImmediate] [", xenolith::platform::clock(core::ClockType::Monotonic) - t, "]");
     537           0 :                 return cleanup();
     538             :         }
     539          10 : }
     540             : 
     541           0 : void View::invalidateTarget(Rc<ImageStorage> &&object) {
     542             :         XL_VKVIEW_LOG("invalidateTarget");
     543           0 :         if (!object) {
     544           0 :                 return;
     545             :         }
     546             : 
     547           0 :         if (object->isSwapchainImage()) {
     548           0 :                 auto img = (SwapchainImage *)object.get();
     549           0 :                 img->invalidateImage();
     550             :         } else {
     551             : 
     552             :         }
     553             : }
     554             : 
     555           0 : Rc<Ref> View::getSwapchainHandle() const {
     556           0 :         if (_swapchain) {
     557           0 :                 return _swapchain.get();
     558             :         } else {
     559           0 :                 return nullptr;
     560             :         }
     561             : }
     562             : 
     563           0 : void View::captureImage(StringView name, const Rc<core::ImageObject> &image, AttachmentLayout l) const {
     564           0 :         auto str = name.str<Interface>();
     565           0 :         _device->getTextureSetLayout()->readImage(*_device, *(Loop *)_glLoop.get(), (Image *)image.get(), l,
     566           0 :                 [str] (const ImageInfoData &info, BytesView view) mutable {
     567           0 :                         if (!StringView(str).ends_with(".png")) {
     568           0 :                                 str = str + String(".png");
     569             :                         }
     570           0 :                         if (!view.empty()) {
     571           0 :                                 auto fmt = core::getImagePixelFormat(info.format);
     572           0 :                                 bitmap::PixelFormat pixelFormat = bitmap::PixelFormat::Auto;
     573           0 :                                 switch (fmt) {
     574           0 :                                 case core::PixelFormat::A: pixelFormat = bitmap::PixelFormat::A8; break;
     575           0 :                                 case core::PixelFormat::IA: pixelFormat = bitmap::PixelFormat::IA88; break;
     576           0 :                                 case core::PixelFormat::RGB: pixelFormat = bitmap::PixelFormat::RGB888; break;
     577           0 :                                 case core::PixelFormat::RGBA: pixelFormat = bitmap::PixelFormat::RGBA8888; break;
     578           0 :                                 default: break;
     579             :                                 }
     580           0 :                                 if (pixelFormat != bitmap::PixelFormat::Auto) {
     581           0 :                                         Bitmap bmp(view.data(), info.extent.width, info.extent.height, pixelFormat);
     582           0 :                                         bmp.save(str);
     583           0 :                                 }
     584             :                         }
     585           0 :                 });
     586           0 : }
     587             : 
     588           0 : void View::captureImage(Function<void(const ImageInfoData &info, BytesView view)> &&cb, const Rc<core::ImageObject> &image, AttachmentLayout l) const {
     589           0 :         _device->getTextureSetLayout()->readImage(*_device, *(Loop *)_glLoop.get(), (Image *)image.get(), l, move(cb));
     590           0 : }
     591             : 
     592           0 : void View::scheduleFence(Rc<Fence> &&fence) {
     593             :         XL_VKVIEW_LOG("scheduleFence");
     594           0 :         if (_running.load()) {
     595           0 :                 performOnThread([this, fence = move(fence)] () mutable {
     596           0 :                         auto loop = (Loop *)_glLoop.get();
     597           0 :                         if (!fence->check(*loop, true)) {
     598           0 :                                 auto frame = fence->getFrame();
     599           0 :                                 if (frame != 0 && (_fenceOrder == 0 || _fenceOrder > frame)) {
     600           0 :                                         _fenceOrder = frame;
     601             :                                 }
     602           0 :                                 _fences.emplace_back(move(fence));
     603             :                         }
     604           0 :                 }, this, true);
     605             :         } else {
     606           0 :                 auto loop = (Loop *)_glLoop.get();
     607           0 :                 fence->check(*loop, false);
     608             :         }
     609           0 : }
     610             : 
     611          10 : void View::mapWindow() {
     612          10 :         if (_options.renderOnDemand) {
     613          10 :                 setReadyForNextFrame();
     614             :         } else {
     615           0 :                 _scheduledTime = 0;
     616           0 :                 scheduleNextImage(0, true);
     617             :         }
     618          10 : }
     619             : 
     620        7051 : void View::setReadyForNextFrame() {
     621        7051 :         performOnThread([this] {
     622        7041 :                 if (!_readyForNextFrame) {
     623        4640 :                         _scheduledTime = 0;
     624        4640 :                         if (_swapchain && _options.renderOnDemand && _framesInProgress == 0 && _swapchain->getAcquiredImagesCount() == 0) {
     625             :                                 XL_VKVIEW_LOG("setReadyForNextFrame - scheduleNextImage");
     626          10 :                                 scheduleNextImage(0, true);
     627             :                         } else {
     628        4630 :                                 _readyForNextFrame = true;
     629             :                         }
     630             :                 }
     631        7041 :         }, this, true);
     632        7051 : }
     633             : 
     634           0 : void View::setRenderOnDemand(bool value) {
     635           0 :         performOnThread([this, value] {
     636           0 :                 _options.renderOnDemand = value;
     637           0 :         }, this, true);
     638           0 : }
     639             : 
     640           0 : bool View::isRenderOnDemand() const {
     641           0 :         return _options.renderOnDemand;
     642             : }
     643             : 
     644           0 : bool View::pollInput(bool frameReady) {
     645           0 :         return false;
     646             : }
     647             : 
     648          20 : core::SurfaceInfo View::getSurfaceOptions() const {
     649          20 :         return _instance->getSurfaceOptions(_surface->getSurface(), _device->getPhysicalDevice());
     650             : }
     651             : 
     652           0 : void View::invalidate() {
     653             : 
     654           0 : }
     655             : 
     656        4640 : void View::scheduleNextImage(uint64_t windowOffset, bool immediately) {
     657             :         XL_VKVIEW_LOG("scheduleNextImage");
     658        4640 :         performOnThread([this, windowOffset, immediately] {
     659        4640 :                 _scheduledTime = xenolith::platform::clock(core::ClockType::Monotonic) + _info.frameInterval + config::OnDemandFrameInterval;
     660        4640 :                 if (!_options.renderOnDemand || _readyForNextFrame || immediately) {
     661        4640 :                         _frameEmitter->setEnableBarrier(_options.enableFrameEmitterBarrier);
     662             : 
     663        4640 :                         if (_options.renderImageOffscreen) {
     664           0 :                                 scheduleSwapchainImage(windowOffset, AcquireOffscreenImage);
     665        4640 :                         } else if (_options.acquireImageImmediately || immediately) {
     666          20 :                                 scheduleSwapchainImage(windowOffset, AcquireSwapchainImageImmediate);
     667             :                         } else {
     668        4620 :                                 scheduleSwapchainImage(windowOffset, AcquireSwapchainImageAsync);
     669             :                         }
     670             : 
     671        4640 :                         _readyForNextFrame = false;
     672             :                 }
     673        4640 :         }, this, true);
     674        4640 : }
     675             : 
     676        4640 : void View::scheduleSwapchainImage(uint64_t windowOffset, ScheduleImageMode mode) {
     677             :         XL_VKVIEW_LOG("scheduleSwapchainImage");
     678        4640 :         Rc<SwapchainImage> swapchainImage;
     679        4640 :         Rc<FrameRequest> newFrameRequest;
     680        4640 :         auto constraints = _constraints;
     681             : 
     682        4640 :         if (mode != ScheduleImageMode::AcquireOffscreenImage) {
     683        4640 :         if (!_swapchain) {
     684           0 :             return;
     685             :         }
     686             : 
     687        4640 :                 auto fullOffset = getUpdateInterval() + windowOffset;
     688        4640 :                 if (fullOffset > _info.frameInterval) {
     689           0 :                         swapchainImage = Rc<SwapchainImage>::create(Rc<SwapchainHandle>(_swapchain), _frameOrder, 0);
     690             :                 } else {
     691        4640 :                         swapchainImage = Rc<SwapchainImage>::create(Rc<SwapchainHandle>(_swapchain), _frameOrder, _nextPresentWindow);
     692             :                 }
     693             : 
     694        4640 :                 swapchainImage->setReady(false);
     695        4640 :                 constraints.extent = Extent2(swapchainImage->getInfo().extent.width, swapchainImage->getInfo().extent.height);
     696             :         }
     697             : 
     698        4640 :         ++ _framesInProgress;
     699        4640 :         if (_framesInProgress > _swapchain->getConfig().imageCount - 1 && _framesInProgress > 1) {
     700             :                 XL_VKVIEW_LOG("scheduleSwapchainImage: extra frame: ", _framesInProgress);
     701             :         }
     702             : 
     703        4640 :         newFrameRequest = _frameEmitter->makeRequest(constraints);
     704             : 
     705             :         // make new frame request immediately
     706        4640 :         _mainLoop->performOnMainThread([this, req = move(newFrameRequest), swapchainImage, swapchain = _swapchain] () mutable {
     707             :                 XL_VKVIEW_LOG("scheduleSwapchainImage: _director->acquireFrame");
     708        4640 :                 if (_director->acquireFrame(req)) {
     709             :                         XL_VKVIEW_LOG("scheduleSwapchainImage: frame acquired");
     710        4640 :                         _glLoop->performOnGlThread([this, req = move(req), swapchainImage = move(swapchainImage), swapchain] () mutable {
     711        4640 :                                 if (_glLoop->isRunning() && swapchain) {
     712             :                                         XL_VKVIEW_LOG("scheduleSwapchainImage: setup frame request");
     713        4640 :                                         auto &queue = req->getQueue();
     714        4640 :                                         auto a = queue->getPresentImageOutput();
     715        4640 :                                         if (!a) {
     716           0 :                                                 a = queue->getTransferImageOutput();
     717             :                                         }
     718        4640 :                                         if (!a) {
     719           0 :                                                 -- _framesInProgress;
     720           0 :                                                 log::error("vk::View", "Fail to run view with queue '", queue->getName(),  "': no usable output attachments found");
     721           0 :                                                 return;
     722             :                                         }
     723             : 
     724        4640 :                                         req->autorelease(swapchain);
     725        4640 :                                         req->setRenderTarget(a, Rc<core::ImageStorage>(swapchainImage));
     726        9280 :                                         req->setOutput(a, [this, swapchain] (core::FrameAttachmentData &data, bool success, Ref *) {
     727             :                                                 XL_VKVIEW_LOG("scheduleSwapchainImage: output on frame");
     728        4640 :                                                 if (data.image) {
     729        4630 :                                                         if (success) {
     730        4630 :                                                                 return present(move(data.image));
     731             :                                                         } else {
     732           0 :                                                                 invalidateTarget(move(data.image));
     733           0 :                                                                 performOnThread([this] {
     734           0 :                                                                         -- _framesInProgress;
     735           0 :                                                                 }, this);
     736             :                                                         }
     737             :                                                 }
     738          10 :                                                 return true;
     739        4640 :                                         }, this);
     740             :                                         XL_VKVIEW_LOG("scheduleSwapchainImage: submit frame");
     741        4640 :                                         auto nextFrame = _frameEmitter->submitNextFrame(move(req));
     742        4640 :                                         if (nextFrame) {
     743        4640 :                                                 auto order = nextFrame->getOrder();
     744        4640 :                                                 swapchainImage->setFrameIndex(order);
     745             : 
     746        4640 :                                                 performOnThread([this, order] {
     747        4639 :                                                         _frameOrder = order;
     748        4639 :                                                 }, this);
     749             :                                         }
     750        4640 :                                 }
     751             :                         }, this);
     752             :                 }
     753        4640 :         }, this);
     754             : 
     755             :         // we should wait until all current fences become signaled
     756             :         // then acquire image and wait for fence
     757        4640 :         if (swapchainImage) {
     758        4640 :                 if (mode == AcquireSwapchainImageAsync && _options.waitOnSwapchainPassFence && _fenceOrder != 0) {
     759           0 :                         updateFences();
     760           0 :                         if (_fenceOrder < swapchainImage->getOrder()) {
     761           0 :                                 scheduleImage(move(swapchainImage));
     762             :                         } else {
     763           0 :                                 _fenceImages.emplace_back(move(swapchainImage));
     764             :                         }
     765             :                 } else {
     766        4640 :                         if (!acquireScheduledImageImmediate(swapchainImage)) {
     767           0 :                                 scheduleImage(move(swapchainImage));
     768             :                         }
     769             :                 }
     770             :         }
     771        4640 : }
     772             : 
     773        4640 : bool View::acquireScheduledImageImmediate(const Rc<SwapchainImage> &image) {
     774             :         XL_VKVIEW_LOG("acquireScheduledImageImmediate");
     775        4640 :         if (image->getSwapchain() != _swapchain) {
     776           0 :                 image->invalidate();
     777           0 :                 return true;
     778             :         }
     779             : 
     780        4640 :         if (!_swapchainImages.empty()) {
     781           0 :                 auto acquiredImage = _swapchainImages.front();
     782           0 :                 _swapchainImages.pop_front();
     783           0 :                 _glLoop->performOnGlThread([tmp = image.get(), acquiredImage = move(acquiredImage)] () mutable {
     784           0 :                         tmp->setAcquisitionTime(xenolith::platform::clock(core::ClockType::Monotonic));
     785           0 :                         tmp->setImage(move(acquiredImage->swapchain), *acquiredImage->data, move(acquiredImage->sem));
     786           0 :                         tmp->setReady(true);
     787           0 :                 }, image, true);
     788           0 :                 return true;
     789           0 :         }
     790             : 
     791        4640 :         if (!_requestedSwapchainImage.empty()) {
     792           0 :                 return false;
     793             :         }
     794             : 
     795        4640 :         if (!_scheduledImages.empty() && _requestedSwapchainImage.empty()) {
     796           0 :                 acquireScheduledImage();
     797           0 :                 return false;
     798             :         }
     799             : 
     800        4640 :         auto nimages = _swapchain->getConfig().imageCount - _swapchain->getSurfaceInfo().minImageCount;
     801        4640 :         if (_swapchain->getAcquiredImagesCount() > nimages) {
     802           0 :                 return false;
     803             :         }
     804             : 
     805        4640 :         auto loop = (Loop *)_glLoop.get();
     806        4640 :         auto fence = loop->acquireFence(0);
     807        4640 :         if (auto acquiredImage = _swapchain->acquire(false, fence)) {
     808        4640 :                 fence->check(*loop, false);
     809        4640 :                 fence = nullptr;
     810        4640 :                 loop->performOnGlThread([tmp = image.get(), acquiredImage = move(acquiredImage)] () mutable {
     811        4640 :                         tmp->setAcquisitionTime(xenolith::platform::clock(core::ClockType::Monotonic));
     812        4640 :                         tmp->setImage(move(acquiredImage->swapchain), *acquiredImage->data, move(acquiredImage->sem));
     813        4640 :                         tmp->setReady(true);
     814        4640 :                 }, image, true);
     815        4640 :                 return true;
     816             :         } else {
     817           0 :                 fence->schedule(*loop);
     818           0 :                 return false;
     819        4640 :         }
     820             : 
     821             :         return false;
     822        4640 : }
     823             : 
     824      217166 : bool View::acquireScheduledImage() {
     825      217166 :         if (!_requestedSwapchainImage.empty() || _scheduledImages.empty()) {
     826      217166 :                 return false;
     827             :         }
     828             : 
     829             :         XL_VKVIEW_LOG("acquireScheduledImage");
     830           0 :         auto loop = (Loop *)_glLoop.get();
     831           0 :         auto fence = loop->acquireFence(0);
     832           0 :         if (auto acquiredImage = _swapchain->acquire(true, fence)) {
     833           0 :                 _requestedSwapchainImage.emplace(acquiredImage);
     834           0 :                 fence->addRelease([this, f = fence.get(), acquiredImage] (bool success) mutable {
     835           0 :                         performOnThread([this, acquiredImage = move(acquiredImage), success] () mutable {
     836           0 :                                 if (success) {
     837           0 :                                         onSwapchainImageReady(move(acquiredImage));
     838             :                                 } else {
     839           0 :                                         _requestedSwapchainImage.erase(acquiredImage);
     840             :                                 }
     841           0 :                         }, this, true);
     842             : #if XL_VKAPI_DEBUG
     843             :                         XL_VKAPI_LOG("[", f->getFrame(),  "] vkAcquireNextImageKHR [complete]",
     844             :                                         " [", xenolith::platform::clock(core::ClockType::Monotonic) - f->getArmedTime(), "]");
     845             : #endif
     846           0 :                 }, this, "View::acquireScheduledImage");
     847           0 :                 scheduleFence(move(fence));
     848           0 :                 return true;
     849             :         } else {
     850           0 :                 fence->schedule(*loop);
     851           0 :                 return false;
     852           0 :         }
     853           0 : }
     854             : 
     855           0 : void View::scheduleImage(Rc<SwapchainImage> &&swapchainImage) {
     856             :         XL_VKVIEW_LOG("scheduleImage");
     857           0 :         if (!_swapchainImages.empty()) {
     858             :                 // pop one of the previously acquired images
     859           0 :                 auto acquiredImage = _swapchainImages.front();
     860           0 :                 _swapchainImages.pop_front();
     861           0 :                 _glLoop->performOnGlThread([tmp = swapchainImage.get(), acquiredImage = move(acquiredImage)] () mutable {
     862           0 :                         tmp->setAcquisitionTime(xenolith::platform::clock(core::ClockType::Monotonic));
     863           0 :                         tmp->setImage(move(acquiredImage->swapchain), *acquiredImage->data, move(acquiredImage->sem));
     864           0 :                         tmp->setReady(true);
     865           0 :                 }, swapchainImage, true);
     866           0 :         } else {
     867           0 :                 _scheduledImages.emplace_back(move(swapchainImage));
     868           0 :                 acquireScheduledImage();
     869             :         }
     870           0 : }
     871             : 
     872           0 : void View::onSwapchainImageReady(Rc<SwapchainHandle::SwapchainAcquiredImage> &&image) {
     873             :         XL_VKVIEW_LOG("onSwapchainImageReady");
     874           0 :         auto ptr = image.get();
     875             : 
     876           0 :         if (!_scheduledImages.empty()) {
     877             :                 // send new swapchain image to framebuffer
     878           0 :                 auto target = _scheduledImages.front();
     879           0 :                 _scheduledImages.pop_front();
     880             : 
     881           0 :                 _glLoop->performOnGlThread([image = move(image), target = move(target)] () mutable {
     882           0 :                         target->setAcquisitionTime(xenolith::platform::clock(core::ClockType::Monotonic));
     883           0 :                         target->setImage(move(image->swapchain), *image->data, move(image->sem));
     884           0 :                         target->setReady(true);
     885           0 :                 }, this, true);
     886           0 :         } else {
     887             :                 // hold image until next framebuffer request, if not active queries
     888           0 :                 _swapchainImages.emplace_back(move(image));
     889             :         }
     890             : 
     891           0 :         _requestedSwapchainImage.erase(ptr);
     892             : 
     893           0 :         if (!_scheduledImages.empty()) {
     894             :                 // run next image query if someone waits for it
     895           0 :                 acquireScheduledImage();
     896             :         }
     897           0 : }
     898             : 
     899          10 : bool View::recreateSwapchain(core::PresentMode mode) {
     900             :         XL_VKVIEW_LOG("recreateSwapchain");
     901             :         struct ResetData : public Ref {
     902             :                 Vector<Rc<SwapchainImage>> fenceImages;
     903             :                 std::deque<Rc<SwapchainImage>> scheduledImages;
     904             :                 Rc<core::FrameEmitter> frameEmitter;
     905             :         };
     906             : 
     907          10 :         auto data = Rc<ResetData>::alloc();
     908          10 :         data->fenceImages = move(_fenceImages);
     909          10 :         data->scheduledImages = move(_scheduledImages);
     910          10 :         data->frameEmitter = _frameEmitter;
     911             : 
     912          10 :         _scheduledTime = 0;
     913          10 :         _framesInProgress -= data->fenceImages.size();
     914          10 :         _framesInProgress -= data->scheduledImages.size();
     915             : 
     916          10 :         _glLoop->performOnGlThread([data] {
     917          10 :                 for (auto &it : data->fenceImages) {
     918           0 :                         it->invalidate();
     919             :                 }
     920          10 :                 for (auto &it : data->scheduledImages) {
     921           0 :                         it->invalidate();
     922             :                 }
     923          10 :                  data->frameEmitter->dropFrames();
     924          10 :         }, this);
     925             : 
     926          10 :         _fenceImages.clear();
     927          10 :         _scheduledImages.clear();
     928          10 :         _requestedSwapchainImage.clear();
     929          10 :         _swapchainImages.clear();
     930             : 
     931          10 :         if (!_surface || mode == core::PresentMode::Unsupported) {
     932           0 :                 _swapchainInvalidated = true;
     933           0 :                 return false;
     934             :         }
     935             : 
     936             : #if DEBUG
     937          10 :         if (core::FrameHandle::GetActiveFramesCount() > 1) {
     938           0 :                 core::FrameHandle::DescribeActiveFrames();
     939             :         }
     940             : #endif
     941             : 
     942          10 :         auto info = getSurfaceOptions();
     943          10 :         auto cfg = _info.selectConfig(*this, info);
     944             : 
     945          10 :         if (!info.isSupported(cfg)) {
     946           0 :                 log::error("Vk-Error", "Presentation with config ", cfg.description(), " is not supported for ", info.description());
     947           0 :                 _swapchainInvalidated = true;
     948           0 :                 return false;
     949             :         }
     950             : 
     951          10 :         if (cfg.extent.width == 0 || cfg.extent.height == 0) {
     952           0 :                 _swapchainInvalidated = true;
     953           0 :                 return false;
     954             :         }
     955             : 
     956          10 :         bool ret = false;
     957          10 :         if (mode == core::PresentMode::Unsupported) {
     958           0 :                 ret = createSwapchain(info, move(cfg), cfg.presentMode);
     959             :         } else {
     960          10 :                 ret = createSwapchain(info, move(cfg), mode);
     961             :         }
     962          10 :         if (ret) {
     963             :                 XL_VKVIEW_LOG("recreateSwapchain - scheduleNextImage");
     964          10 :                 _swapchainInvalidated = false;
     965             :                 // run frame as, no present window, no wait on fences
     966          10 :                 scheduleNextImage(0, true);
     967             :         }
     968          10 :         return ret;
     969          10 : }
     970             : 
     971          20 : bool View::createSwapchain(const core::SurfaceInfo &info, core::SwapchainConfig &&cfg, core::PresentMode presentMode) {
     972          20 :         auto devInfo = _device->getInfo();
     973             : 
     974          20 :         auto swapchainImageInfo = getSwapchainImageInfo(cfg);
     975          20 :         uint32_t queueFamilyIndices[] = { devInfo.graphicsFamily.index, devInfo.presentFamily.index };
     976             : 
     977             :         do {
     978          20 :                 auto oldSwapchain = move(_swapchain);
     979             : 
     980          40 :                 _swapchain = Rc<SwapchainHandle>::create(*_device, info, cfg, move(swapchainImageInfo), presentMode,
     981          40 :                                 _surface, queueFamilyIndices, oldSwapchain ? oldSwapchain.get() : nullptr);
     982             : 
     983          20 :                 if (_swapchain) {
     984          20 :                         _constraints.extent = cfg.extent;
     985          20 :                         _constraints.transform = cfg.transform;
     986             : 
     987          20 :                         Vector<uint64_t> ids;
     988          20 :                         auto &cache = _glLoop->getFrameCache();
     989          70 :                         for (auto &it : _swapchain->getImages()) {
     990         100 :                                 for (auto &iit : it.views) {
     991          50 :                                         auto id = iit.second->getIndex();
     992          50 :                                         ids.emplace_back(iit.second->getIndex());
     993          50 :                                         iit.second->setReleaseCallback([loop = _glLoop, cache, id] {
     994          50 :                                                 loop->performOnGlThread([cache, id] {
     995          50 :                                                         cache->removeImageView(id);
     996          50 :                                                 });
     997          50 :                                         });
     998             :                                 }
     999             :                         }
    1000             : 
    1001          20 :                         _glLoop->performOnGlThread([loop = _glLoop, ids] {
    1002          20 :                                 auto &cache = loop->getFrameCache();
    1003          70 :                                 for (auto &id : ids) {
    1004          50 :                                         cache->addImageView(id);
    1005             :                                 }
    1006          20 :                         });
    1007          20 :                 }
    1008             : 
    1009          20 :                 _config = move(cfg);
    1010             : 
    1011          20 :                 log::verbose("vk::View", "Swapchain: ", _config.description());
    1012             : 
    1013          20 :                 ++ _gen;
    1014          20 :         } while (0);
    1015             : 
    1016          40 :         return _swapchain != nullptr;
    1017          20 : }
    1018             : 
    1019          10 : bool View::isImagePresentable(const core::ImageObject &image, VkFilter &filter) const {
    1020          10 :         auto dev = (Device *)_device;
    1021             : 
    1022          10 :         auto &sourceImageInfo = image.getInfo();
    1023          10 :         if (sourceImageInfo.extent.depth != 1 || sourceImageInfo.format != _config.imageFormat
    1024          20 :                         || (sourceImageInfo.usage & core::ImageUsage::TransferSrc) == core::ImageUsage::None) {
    1025           0 :                 log::error("Swapchain", "Image can not be presented on swapchain");
    1026           0 :                 return false;
    1027             :         }
    1028             : 
    1029             :         VkFormatProperties sourceProps;
    1030             :         VkFormatProperties targetProps;
    1031             : 
    1032          20 :         dev->getInstance()->vkGetPhysicalDeviceFormatProperties(dev->getInfo().device,
    1033          10 :                         VkFormat(sourceImageInfo.format), &sourceProps);
    1034          20 :         dev->getInstance()->vkGetPhysicalDeviceFormatProperties(dev->getInfo().device,
    1035          10 :                         VkFormat(_config.imageFormat), &targetProps);
    1036             : 
    1037          10 :         if (_config.extent.width == sourceImageInfo.extent.width && _config.extent.height == sourceImageInfo.extent.height) {
    1038          10 :                 if ((targetProps.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_DST_BIT) == 0) {
    1039           0 :                         return false;
    1040             :                 }
    1041             : 
    1042          10 :                 if (sourceImageInfo.tiling == core::ImageTiling::Optimal) {
    1043          10 :                         if ((sourceProps.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_SRC_BIT) == 0) {
    1044           0 :                                 return false;
    1045             :                         }
    1046             :                 } else {
    1047           0 :                         if ((sourceProps.linearTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_SRC_BIT) == 0) {
    1048           0 :                                 return false;
    1049             :                         }
    1050             :                 }
    1051             :         } else {
    1052           0 :                 if ((targetProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0) {
    1053           0 :                         return false;
    1054             :                 }
    1055             : 
    1056           0 :                 if (sourceImageInfo.tiling == core::ImageTiling::Optimal) {
    1057           0 :                         if ((sourceProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0) {
    1058           0 :                                 return false;
    1059             :                         }
    1060             : 
    1061           0 :                         if ((sourceProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0) {
    1062           0 :                                 filter = VK_FILTER_LINEAR;
    1063             :                         }
    1064             :                 } else {
    1065           0 :                         if ((sourceProps.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0) {
    1066           0 :                                 return false;
    1067             :                         }
    1068             : 
    1069           0 :                         if ((sourceProps.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0) {
    1070           0 :                                 filter = VK_FILTER_LINEAR;
    1071             :                         }
    1072             :                 }
    1073             :         }
    1074             : 
    1075          10 :         return true;
    1076             : }
    1077             : 
    1078        4019 : void View::runScheduledPresent(Rc<SwapchainImage> &&object) {
    1079             :         XL_VKVIEW_LOG("runScheduledPresent");
    1080        4019 :         if (_options.presentImmediate) {
    1081           0 :                 auto queue = _device->tryAcquireQueueSync(QueueOperations::Present, true);
    1082           0 :                 if (object->getSwapchain() == _swapchain && object->isSubmitted()) {
    1083           0 :                         presentWithQueue(*queue, move(object));
    1084             :                 }
    1085           0 :                 _glLoop->performOnGlThread([this, queue = move(queue)] () mutable {
    1086           0 :                         _device->releaseQueue(move(queue));
    1087           0 :                 }, this);
    1088           0 :         } else {
    1089        4019 :                 _glLoop->performOnGlThread([this, object = move(object)] () mutable {
    1090        4019 :                         if (!_glLoop->isRunning()) {
    1091           0 :                                 return;
    1092             :                         }
    1093             : 
    1094       12057 :                         _device->acquireQueue(QueueOperations::Present, *(Loop*) _glLoop.get(),
    1095        4019 :                                         [this, object = move(object)](Loop&, const Rc<DeviceQueue> &queue) mutable {
    1096        4019 :                                 performOnThread([this, queue, object = move(object)]() mutable {
    1097        4019 :                                         if (object->getSwapchain() == _swapchain && object->isSubmitted()) {
    1098        4019 :                                                 presentWithQueue(*queue, move(object));
    1099             :                                         }
    1100        4019 :                                         _glLoop->performOnGlThread([this, queue = move(queue)]() mutable {
    1101        4019 :                                                 _device->releaseQueue(move(queue));
    1102        4019 :                                         }, this);
    1103        4019 :                                 }, this);
    1104        4019 :                         }, [this](Loop&) {
    1105           0 :                                 invalidate();
    1106        8038 :                         }, this);
    1107             :                 }, this);
    1108             :         }
    1109        4019 : }
    1110             : 
    1111        4630 : void View::presentWithQueue(DeviceQueue &queue, Rc<ImageStorage> &&image) {
    1112             :         XL_VKVIEW_LOG("presentWithQueue: ", _framesInProgress);
    1113        4630 :         auto res = _swapchain->present(queue, move(image));
    1114        4630 :         auto dt = updateFrameInterval();
    1115        4630 :         if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) {
    1116             :                 XL_VKVIEW_LOG("presentWithQueue - deprecate swapchain");
    1117           0 :                 _swapchain->deprecate(false);
    1118        4630 :         } else if (res != VK_SUCCESS) {
    1119           0 :                 log::error("vk::View", "presentWithQueue: error:", getVkResultName(res));
    1120             :         }
    1121             :         XL_VKVIEW_LOG("presentWithQueue - presented");
    1122        4630 :         _blockSwapchainRecreation = true;
    1123             : 
    1124             :         // DO NOT decrement active frame counter before poll input
    1125             :         // Input can call setReadyForNextFrame, that spawns new frame when _framesInProgress is 0
    1126             :         // so, scheduleNextImage will starts separate frame stream, that can cause deadlock on resources
    1127             : 
    1128        4630 :         if (!pollInput(true)) {
    1129           0 :                 _blockSwapchainRecreation = false;
    1130           0 :                 -- _framesInProgress;
    1131             :                 XL_VKVIEW_LOG("presentWithQueue - pollInputExit");
    1132           0 :                 return;
    1133             :         }
    1134             : 
    1135        4630 :         _blockSwapchainRecreation = false;
    1136        4630 :         -- _framesInProgress;
    1137             : 
    1138        4630 :         if (_swapchain->isDeprecated() && _swapchain->getAcquiredImagesCount() == 0) {
    1139          10 :                 waitForFences(_frameOrder);
    1140          10 :                 queue.waitIdle();
    1141             : 
    1142          10 :                 recreateSwapchain(_swapchain->getRebuildMode());
    1143             :         } else {
    1144        4620 :                 if (!_options.renderOnDemand || _readyForNextFrame) {
    1145        4620 :                         if (_options.followDisplayLink) {
    1146             :                                 XL_VKVIEW_LOG("presentWithQueue - scheduleNextImage - followDisplayLink");
    1147           0 :                                 scheduleNextImage(0, true);
    1148             :                                 XL_VKVIEW_LOG("presentWithQueue - end");
    1149           0 :                                 return;
    1150             :                         }
    1151        4620 :                         _nextPresentWindow = dt.clock + _info.frameInterval - getUpdateInterval();
    1152             : 
    1153             :                         // if current or average framerate below preferred - reduce present window to release new frame early
    1154        4620 :                         if (dt.dt > _info.frameInterval || dt.avg > _info.frameInterval) {
    1155        2294 :                                 _nextPresentWindow -= (std::max(dt.dt, dt.avg) - _info.frameInterval);
    1156             :                         }
    1157             : 
    1158             :                         XL_VKVIEW_LOG("presentWithQueue - scheduleNextImage");
    1159        4620 :                         scheduleNextImage(0, false);
    1160             :                 }
    1161             :         }
    1162             : 
    1163             :         XL_VKVIEW_LOG("presentWithQueue - end");
    1164             : }
    1165             : 
    1166           0 : void View::invalidateSwapchainImage(Rc<ImageStorage> &&image) {
    1167             :         XL_VKVIEW_LOG("invalidateSwapchainImage");
    1168           0 :         _swapchain->invalidateImage(move(image));
    1169             : 
    1170           0 :         if (_swapchain->isDeprecated() && _swapchain->getAcquiredImagesCount() == 0) {
    1171             :                 // log::debug("View", "recreateSwapchain - View::invalidateSwapchainImage (", renderqueue::FrameHandle::GetActiveFramesCount(), ")");
    1172           0 :                 recreateSwapchain(_swapchain->getRebuildMode());
    1173             :         } else {
    1174             :                 XL_VKVIEW_LOG("invalidateSwapchainImage - scheduleNextImage");
    1175           0 :                 scheduleNextImage(_info.frameInterval, false);
    1176             :         }
    1177           0 : }
    1178             : 
    1179        4640 : View::FrameTimeInfo View::updateFrameInterval() {
    1180             :         FrameTimeInfo ret;
    1181        4640 :         ret.clock = xenolith::platform::clock(core::ClockType::Monotonic);
    1182        4640 :         ret.dt = ret.clock - _lastFrameStart;
    1183        4640 :         _lastFrameInterval = ret.dt;
    1184        4640 :         _avgFrameInterval.addValue(ret.dt);
    1185        4640 :         _avgFrameIntervalValue = _avgFrameInterval.getAverage();
    1186        4640 :         _lastFrameStart = ret.clock;
    1187        4640 :         ret.avg = _avgFrameIntervalValue.load();
    1188        4640 :         return ret;
    1189             : }
    1190             : 
    1191          10 : void View::waitForFences(uint64_t min) {
    1192          10 :         auto loop = (Loop *)_glLoop.get();
    1193          10 :         auto it = _fences.begin();
    1194          10 :         while (it != _fences.end()) {
    1195           0 :                 if ((*it)->getFrame() <= min) {
    1196             :                         // log::debug("View", "waitForFences: ", (*it)->getTag());
    1197           0 :                         if ((*it)->check(*loop, false)) {
    1198           0 :                                 it = _fences.erase(it);
    1199             :                         } else {
    1200           0 :                                 ++ it;
    1201             :                         }
    1202             :                 } else {
    1203           0 :                         ++ it;
    1204             :                 }
    1205             :         }
    1206          10 : }
    1207             : 
    1208          10 : void View::finalize() {
    1209          10 :         _glLoop->performOnGlThread([this] {
    1210          10 :                 end();
    1211          10 :         }, this);
    1212             : 
    1213          10 :         std::unique_lock<Mutex> lock(_mutex);
    1214          10 :         _callbacks.clear();
    1215          10 : }
    1216             : 
    1217      217166 : void View::updateFences() {
    1218      217166 :         uint64_t fenceOrder = 0;
    1219             :         do {
    1220      217166 :                 auto loop = (Loop *)_glLoop.get();
    1221      217166 :                 auto it = _fences.begin();
    1222      217166 :                 while (it != _fences.end()) {
    1223           0 :                         if ((*it)->check(*loop, true)) {
    1224           0 :                                 it = _fences.erase(it);
    1225             :                         } else {
    1226           0 :                                 auto frame = (*it)->getFrame();
    1227           0 :                                 if (frame != 0 && (fenceOrder == 0 || fenceOrder > frame)) {
    1228           0 :                                         fenceOrder = frame;
    1229             :                                 }
    1230           0 :                                 ++ it;
    1231             :                         }
    1232             :                 }
    1233             :         } while (0);
    1234             : 
    1235      217166 :         _fenceOrder = fenceOrder;
    1236      217166 : }
    1237             : 
    1238          10 : void View::clearImages() {
    1239          10 :         _mutex.lock();
    1240             : 
    1241          10 :         auto loop = (Loop *)_glLoop.get();
    1242          10 :         for (auto &it : _fences) {
    1243           0 :                 it->check(*loop, false);
    1244             :         }
    1245          10 :         _fences.clear();
    1246          10 :         _mutex.unlock();
    1247             : 
    1248          10 :         for (auto &it :_fenceImages) {
    1249           0 :                 it->invalidateSwapchain();
    1250             :         }
    1251          10 :         _fenceImages.clear();
    1252             : 
    1253          10 :         for (auto &it :_scheduledImages) {
    1254           0 :                 it->invalidateSwapchain();
    1255             :         }
    1256          10 :         _scheduledImages.clear();
    1257             : 
    1258          10 :         for (auto &it :_scheduledPresent) {
    1259           0 :                 it->invalidateSwapchain();
    1260             :         }
    1261          10 :         _scheduledPresent.clear();
    1262          10 : }
    1263             : 
    1264        4019 : void View::schedulePresent(SwapchainImage *img, uint64_t) {
    1265        4019 :         _scheduledPresent.emplace_back(img);
    1266        4019 : }
    1267             : 
    1268             : }

Generated by: LCOV version 1.14