Blue Brain BioExplorer
DeflectPlugin.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-2024, EPFL/Blue Brain Project
3  * All rights reserved. Do not distribute without permission.
4  * Responsible Author: Daniel Nachbaur <daniel.nachbaur@epfl.ch>
5  *
6  * This file is part of Blue Brain BioExplorer <https://github.com/BlueBrain/BioExplorer>
7  *
8  * This library is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU Lesser General Public License version 3.0 as published
10  * by the Free Software Foundation.
11  *
12  * This library is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15  * details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #include "DeflectPlugin.h"
23 #include "DeflectParameters.h"
24 #include "utils.h"
25 
33 
35 
37 
38 #ifdef USE_NETWORKING
39 #include <uvw.hpp>
40 #endif
41 
42 namespace
43 {
44 const float wheelFactor = 1.f / 40.f;
45 }
46 
47 namespace core
48 {
50 {
51 public:
53  : _api(*api)
54  , _engine(api->getEngine())
55  , _appParams{api->getParametersManager().getApplicationParameters()}
56  , _params(std::move(params))
57  {
58  if (auto ai = api->getActionInterface())
59  {
60  const RpcParameterDescription desc{"stream", "Stream to a displaywall", Execution::sync, "param",
61  "Stream parameters"};
62  ai->registerNotification(desc, _params.getPropertyMap(),
63  [&](const PropertyMap& prop)
64  {
65  _params.getPropertyMap().merge(prop);
66  _params.markModified();
67  _engine.triggerRender();
68  });
69  }
70  }
71 
72  void preRender()
73  {
74  if (_stream && utils::needsReset(*_stream, _params))
75  _closeStream();
76 
77  if (_params.getEnabled() && !_stream)
78  _startStream();
79 
80  if (_stream)
81  _handleDeflectEvents();
82 
83  if (_params.usePixelOp() && _params.isModified())
84  {
85  for (auto frameBuffer : _engine.getFrameBuffers())
86  frameBuffer->updatePixelOp(_params.getPropertyMap());
87  }
88 
89  _params.resetModified();
90  }
91 
92  void postRender()
93  {
94  if (!_params.usePixelOp() && _stream)
95  _sendDeflectFrame(_engine);
96  }
97 
98 private:
99  struct Image
100  {
101  std::vector<char> data;
102  Vector2ui size;
103  FrameBufferFormat format;
104  };
105 
106  void _startStream()
107  {
108  try
109  {
110  if (_params.usePixelOp())
111  {
112  _stream.reset(new deflect::Observer(_params.getId(), _params.getHostname(), _params.getPort()));
113  }
114  else
115  {
116  _stream.reset(new deflect::Stream(_params.getId(), _params.getHostname(), _params.getPort()));
117  }
118 
119  if (_stream->registerForEvents())
120  _setupSocketListener();
121  else
122  CORE_ERROR("Deflect failed to register for events!");
123 
124  _params.setId(_stream->getId());
125  _params.setHost(_stream->getHost());
126 
127  // distributed streaming requires a properly setup stream ID (either
128  // from DEFLECT_ID env variable or from here)
129  if (_params.usePixelOp() && !_params.getId().empty())
130  {
131  for (auto frameBuffer : _engine.getFrameBuffers())
132  {
133  // Use format 'none' for the per-tile streaming to avoid
134  // tile readback to the MPI master
135  frameBuffer->setFormat(FrameBufferFormat::none);
136  frameBuffer->createPixelOp(TEXTIFY(deflectPixelOp));
137  frameBuffer->updatePixelOp(_params.getPropertyMap());
138  }
139  }
140 
141  _sendSizeHints();
142 
143  CORE_INFO("Deflect successfully connected to Tide on host " << _stream->getHost());
144  }
145  catch (const std::runtime_error& ex)
146  {
147  CORE_ERROR("Deflect failed to initialize. " << ex.what());
148  _params.setEnabled(false);
149  }
150  }
151 
152  void _closeStream()
153  {
154  CORE_INFO("Closing Deflect stream");
155 
156  _waitOnFutures();
157  _lastImages.clear();
158 #ifdef USE_NETWORKING
159  if (_pollHandle)
160  {
161  _pollHandle->stop();
162  _pollHandle.reset();
163  }
164 #endif
165  _stream.reset();
166  }
167 
168  void _setupSocketListener()
169  {
170 #ifdef USE_NETWORKING
171  assert(_stream->isConnected());
172 
173  auto loop = uvw::Loop::getDefault();
174  _pollHandle = loop->resource<uvw::PollHandle>(_stream->getDescriptor());
175 
176  _pollHandle->on<uvw::PollEvent>([&engine = _engine](const auto&, auto&) { engine.triggerRender(); });
177 
178  _pollHandle->start(uvw::PollHandle::Event::READABLE);
179 
180  _stream->setDisconnectedCallback(
181  [&]
182  {
183  _pollHandle->stop();
184  _pollHandle.reset();
185  });
186 #endif
187  }
188 
189  void _handleDeflectEvents()
190  {
191  auto& cameraManipulator = _api.getCameraManipulator();
192  const auto& windowSize = _appParams.getWindowSize();
193  while (_stream->hasEvent())
194  {
195  const deflect::Event& event = _stream->getEvent();
196  switch (event.type)
197  {
198  case deflect::Event::EVT_PRESS:
199  _previousPos = _getWindowPos(event, windowSize);
200  _pan = _pinch = false;
201  break;
202  case deflect::Event::EVT_MOVE:
203  case deflect::Event::EVT_RELEASE:
204  {
205  const auto pos = _getWindowPos(event, windowSize);
206  if (!_pan && !_pinch)
207  cameraManipulator.dragLeft(pos, _previousPos);
208  _previousPos = pos;
209  _pan = _pinch = false;
210  break;
211  }
212  case deflect::Event::EVT_PAN:
213  {
214  if (_pinch)
215  break;
216  const auto pos = _getWindowPos(event, windowSize);
217  cameraManipulator.dragMiddle(pos, _previousPos);
218  _previousPos = pos;
219  _pan = true;
220  break;
221  }
222  case deflect::Event::EVT_PINCH:
223  {
224  if (_pan)
225  break;
226  const auto pos = _getWindowPos(event, windowSize);
227  const auto delta = _getZoomDelta(event, windowSize);
228  cameraManipulator.wheel(pos, delta * wheelFactor);
229  _pinch = true;
230  break;
231  }
232  case deflect::Event::EVT_KEY_PRESS:
233  {
234  _api.getKeyboardHandler().handleKeyboardShortcut(event.text[0]);
235  break;
236  }
237  case deflect::Event::EVT_VIEW_SIZE_CHANGED:
238  {
239  Vector2ui newSize(event.dx, event.dy);
240  if (_params.isResizingEnabled())
241  _appParams.setWindowSize(newSize);
242  break;
243  }
244  case deflect::Event::EVT_CLOSE:
245  {
246  _params.setEnabled(false);
247  _closeStream();
248  return;
249  }
250  default:
251  break;
252  }
253  }
254  }
255 
256  void _sendSizeHints()
257  {
258  const auto& frameBuffers = _engine.getFrameBuffers();
259  if (frameBuffers.empty())
260  return;
261 
262  const auto& minSize = _engine.getMinimumFrameSize();
263 
264  auto sizeHints = deflect::SizeHints();
265  sizeHints.maxWidth = std::numeric_limits<unsigned int>::max();
266  sizeHints.maxHeight = std::numeric_limits<unsigned int>::max();
267  sizeHints.minWidth = minSize.x;
268  sizeHints.minHeight = minSize.y;
269 
270  // only send preferred size if we have no multi-channel setup (e.g.
271  // OpenDeck)
272  const uint8_t channel = utils::getChannel(frameBuffers[0]->getName());
273  Vector2ui preferredSize = frameBuffers[0]->getSize();
274  for (auto frameBuffer : frameBuffers)
275  {
276  if (channel != utils::getChannel(frameBuffer->getName()))
277  {
278  preferredSize = {0, 0};
279  break;
280  }
281  }
282 
283  sizeHints.preferredWidth = preferredSize.x;
284  sizeHints.preferredHeight = preferredSize.y;
285  _stream->sendSizeHints(sizeHints);
286  }
287 
288  void _sendDeflectFrame(Engine& engine)
289  {
290  const bool error = _waitOnFutures();
291 
292  if (error)
293  {
294  if (!_stream->isConnected())
295  {
296  CORE_INFO("Stream closed, exiting.");
297  }
298  else
299  {
300  CORE_ERROR("failure in _sendDeflectFrame()");
301  _params.setEnabled(false);
302  }
303  return;
304  }
305 
306  const auto& frameBuffers = engine.getFrameBuffers();
307  for (size_t i = 0; i < frameBuffers.size(); ++i)
308  {
309  auto frameBuffer = frameBuffers[i];
310  frameBuffer->map();
311  if (frameBuffer->getColorBuffer())
312  {
313  const deflect::View view = utils::getView(frameBuffer->getName());
314  const uint8_t channel = utils::getChannel(frameBuffer->getName());
315 
316  if (i <= _lastImages.size())
317  _lastImages.push_back({});
318  auto& image = _lastImages[i];
319  _copyToImage(image, *frameBuffer);
320  _futures.push_back(_sendImage(image, view, channel));
321  }
322  frameBuffer->unmap();
323  }
324  _futures.push_back(static_cast<deflect::Stream&>(*_stream).finishFrame());
325  }
326 
327  void _copyToImage(Image& image, FrameBuffer& frameBuffer)
328  {
329  const auto& size = frameBuffer.getSize();
330  const size_t bufferSize = size.x * size.y * frameBuffer.getColorDepth();
331  const auto data = frameBuffer.getColorBuffer();
332 
333  image.data.resize(bufferSize);
334  memcpy(image.data.data(), data, bufferSize);
335  image.size = size;
336  image.format = frameBuffer.getFrameBufferFormat();
337  }
338 
339  deflect::Stream::Future _sendImage(const Image& image, const deflect::View& view, const uint8_t channel)
340  {
341  const auto format = _getDeflectImageFormat(image.format);
342 
343  deflect::ImageWrapper deflectImage(image.data.data(), image.size.x, image.size.y, format);
344 
345  deflectImage.view = view;
346  deflectImage.channel = channel;
347  deflectImage.compressionQuality = _params.getQuality();
348  deflectImage.compressionPolicy = _params.getCompression() ? deflect::COMPRESSION_ON : deflect::COMPRESSION_OFF;
349  deflectImage.rowOrder = _params.isTopDown() ? deflect::RowOrder::top_down : deflect::RowOrder::bottom_up;
350  deflectImage.subsampling = _params.getChromaSubsampling();
351 
352  return static_cast<deflect::Stream&>(*_stream).send(deflectImage);
353  }
354 
355  deflect::PixelFormat _getDeflectImageFormat(const FrameBufferFormat format) const
356  {
357  switch (format)
358  {
360  return deflect::BGRA;
362  return deflect::RGB;
363  default:
364  return deflect::RGBA;
365  }
366  }
367 
368  Vector2d _getWindowPos(const deflect::Event& event, const Vector2ui& windowSize) const
369  {
370  return {event.mouseX * windowSize.x, event.mouseY * windowSize.y};
371  }
372 
373  double _getZoomDelta(const deflect::Event& pinchEvent, const Vector2ui& windowSize) const
374  {
375  const auto dx = pinchEvent.dx * windowSize.x;
376  const auto dy = pinchEvent.dy * windowSize.y;
377  return std::copysign(std::sqrt(dx * dx + dy * dy), dx + dy);
378  }
379 
380  bool _waitOnFutures()
381  {
382  bool error = false;
383  for (auto& future : _futures)
384  {
385  if (!future.get())
386  error = true;
387  }
388  _futures.clear();
389  return error;
390  }
391 
392  PluginAPI& _api;
393  Engine& _engine;
394  ApplicationParameters& _appParams;
395  DeflectParameters _params;
396  Vector2d _previousPos;
397  bool _pan = false;
398  bool _pinch = false;
399  std::unique_ptr<deflect::Observer> _stream;
400  std::vector<Image> _lastImages;
401  std::vector<deflect::Stream::Future> _futures;
402 
403 #ifdef USE_NETWORKING
404  std::shared_ptr<uvw::PollHandle> _pollHandle;
405 #endif
406 };
407 
409  : _params(std::move(params))
410 {
411 }
412 
414 {
415  _impl = std::make_shared<Impl>(_api, std::move(_params));
416 }
417 
419 {
420  _impl->preRender();
421 }
422 
424 {
425  _impl->postRender();
426 }
427 } // namespace core
428 
429 extern "C" core::ExtensionPlugin* brayns_plugin_create(const int argc, const char** argv)
430 {
432  if (!params.getPropertyMap().parse(argc, argv))
433  return nullptr;
434  return new core::DeflectPlugin(std::move(params));
435 }
core::ExtensionPlugin * brayns_plugin_create(const int argc, const char **argv)
void setWindowSize(const Vector2ui &size)
const Vector2ui getWindowSize() const
void resetModified()
Definition: BaseObject.h:64
bool isModified() const
Definition: BaseObject.h:60
unsigned getQuality() const
std::string getHostname() const
const PropertyMap & getPropertyMap() const
void setHost(const std::string &host)
std::string getId() const
void setId(const std::string &id)
unsigned getPort() const
deflect::ChromaSubsampling getChromaSubsampling() const
void setEnabled(const bool enabled)
Impl(PluginAPI *api, DeflectParameters &&params)
void preRender() final
void postRender() final
DeflectPlugin(DeflectParameters &&params)
PLATFORM_API const std::vector< FrameBufferPtr > & getFrameBuffers() const
Returns all registered frame buffers that are used during rendering.
Definition: Engine.h:231
virtual PLATFORM_API Vector2ui getMinimumFrameSize() const =0
Returns the minimum frame size in pixels supported by this engine.
void handleKeyboardShortcut(const unsigned char key)
Handles a keyboard shortcut.
virtual ActionInterface * getActionInterface()=0
virtual KeyboardHandler & getKeyboardHandler()=0
virtual AbstractManipulator & getCameraManipulator()=0
bool parse(int argc, const char **argv)
deflect::View getView(const std::string &name)
Definition: utils.h:46
uint8_t getChannel(const std::string &name)
Definition: utils.h:63
bool needsReset(const deflect::Observer &stream, const DeflectParameters &params)
Definition: utils.h:70
glm::vec< 2, double > Vector2d
Definition: MathTypes.h:142
FrameBufferFormat
Definition: Types.h:205
glm::vec< 2, uint32_t > Vector2ui
Definition: MathTypes.h:133
#define CORE_INFO(__msg)
Definition: Logs.h:33
#define CORE_ERROR(__msg)
Definition: Logs.h:31
#define deflectPixelOp
Definition: utils.h:28
#define TEXTIFY(A)
Definition: utils.h:29