ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ImguiFilamentBridge.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 // 4068: Filament has some clang-specific vectorizing pragma's that MSVC flags
11 // 4305: LightManager.h needs to specify some constants as floats
12 #ifdef _MSC_VER
13 #pragma warning(push)
14 #pragma warning(disable : 4068 4305)
15 #endif // _MSC_VER
16 
17 #include <filament/Fence.h>
18 #include <filament/RenderableManager.h>
19 #include <filament/Scene.h>
20 #include <filament/TextureSampler.h>
21 #include <filament/TransformManager.h>
22 #include <utils/EntityManager.h>
23 
24 #ifdef _MSC_VER
25 #pragma warning(pop)
26 #endif // _MSC_VER
27 
28 #include <FileSystem.h>
29 #include <fcntl.h>
30 #include <imgui.h>
31 
32 #include <cerrno>
33 #include <iostream>
34 #include <map>
35 #include <vector>
36 
39 #include "visualization/gui/Gui.h"
48 
49 using namespace filament::math;
50 using namespace filament;
51 using namespace utils;
52 
53 namespace cloudViewer {
54 namespace visualization {
55 namespace gui {
56 
57 static Material* LoadMaterialTemplate(const std::string& path, Engine& engine) {
58  std::vector<char> bytes;
59  std::string error_str;
60  if (!utility::filesystem::FReadToBuffer(path, bytes, &error_str)) {
61  std::cout << "[ERROR] Could not read " << path << ": " << error_str
62  << std::endl;
63  return nullptr;
64  }
65 
66  return Material::Builder()
67  .package(bytes.data(), bytes.size())
68  .build(engine);
69 }
70 
71 class MaterialPool {
72 public:
74 
75  MaterialPool(filament::Engine* engine,
76  filament::Material* material_template) {
77  engine_ = engine;
78  template_ = material_template;
79  reset();
80  }
81 
82  // Drains the pool and deallocates all the objects.
83  void drain() {
84  for (auto* o : pool_) {
85  engine_->destroy(o);
86  }
87  pool_.clear();
88  }
89 
90  // Invalidates all the pointers previously given out and "refills"
91  // the pool with them.
92  void reset() { next_index_ = 0; }
93 
94  // Returns an object from the pool. The pool retains ownership.
95  filament::MaterialInstance* pull() {
96  if (next_index_ >= pool_.size()) {
97  pool_.push_back(template_->createInstance());
98  }
99  return pool_[next_index_++];
100  }
101 
102 private:
103  filament::Engine* engine_ = nullptr; // we do not own this
104  filament::Material* template_ = nullptr; // we do not own this
105  std::vector<filament::MaterialInstance*> pool_; // we DO own these
106  size_t next_index_ = 0;
107 };
108 
109 static const char* kUiBlitTexParamName = "albedo";
110 static const char* kImageTexParamName = "image";
111 
113  // Bridge manages filament resources directly
114  filament::Material* uiblit_material_ = nullptr;
115  filament::Material* image_material_ = nullptr;
116  std::vector<filament::VertexBuffer*> vertex_buffers_;
117  std::vector<filament::IndexBuffer*> index_buffers_;
118 
119  // Use material pools to avoid allocating materials during every draw
122 
123  utils::Entity renderable_;
124  filament::Texture* font_texture_ = nullptr;
125  bool has_synced_ = false;
126 
128  nullptr; // we do not own this
130  nullptr; // we do not own this
131 };
132 
133 ImguiFilamentBridge::ImguiFilamentBridge(
135  const Size& window_size)
136  : impl_(new ImguiFilamentBridge::Impl()) {
137  impl_->renderer_ = renderer;
138  // The UI needs a special material (just a pass-through blit)
139  std::string resource_path = Application::GetInstance().GetResourcePath();
140  impl_->uiblit_material_ = LoadMaterialTemplate(
141  resource_path + "/ui_blit.filamat",
143  impl_->image_material_ = LoadMaterialTemplate(
144  resource_path + "/img_blit.filamat",
146 
148  impl_->uiblit_pool_ = MaterialPool(&engine, impl_->uiblit_material_);
149  impl_->image_pool_ = MaterialPool(&engine, impl_->image_material_);
150 
151  auto scene_handle = renderer->CreateScene();
152  renderer->ConvertToGuiScene(scene_handle);
153  auto scene = renderer->GetGuiScene();
154 
155  auto view_id = scene->AddView(0, 0, window_size.width, window_size.height);
156  impl_->view_ = dynamic_cast<visualization::rendering::FilamentView*>(
157  scene->GetView(view_id));
158 
159  auto native_view = impl_->view_->GetNativeView();
160  native_view->setPostProcessingEnabled(false);
161  native_view->setShadowingEnabled(false);
162 
163  EntityManager& em = utils::EntityManager::get();
164  impl_->renderable_ = em.create();
165  scene->GetNativeScene()->addEntity(impl_->renderable_);
166 }
167 
169  int width,
170  int height,
171  int bytes_per_px) {
173 
174  engine.destroy(impl_->font_texture_);
175 
176  size_t size = (size_t)(width * height);
177  Texture::PixelBufferDescriptor pb(pixels, size, Texture::Format::R,
178  Texture::Type::UBYTE);
179  impl_->font_texture_ = Texture::Builder()
180  .width((uint32_t)width)
181  .height((uint32_t)height)
182  .levels((uint8_t)1)
183  .format(Texture::InternalFormat::R8)
184  .sampler(Texture::Sampler::SAMPLER_2D)
185  .build(engine);
186  impl_->font_texture_->setImage(engine, 0, std::move(pb));
187 
188  TextureSampler sampler(TextureSampler::MinFilter::LINEAR,
189  TextureSampler::MagFilter::LINEAR);
190  impl_->uiblit_material_->setDefaultParameter(kUiBlitTexParamName,
191  impl_->font_texture_, sampler);
192 }
193 
196 
197  engine.destroy(impl_->renderable_);
198  impl_->uiblit_pool_.drain();
199  impl_->image_pool_.drain();
200  engine.destroy(impl_->uiblit_material_);
201  engine.destroy(impl_->image_material_);
202  engine.destroy(impl_->font_texture_);
203  for (auto& vb : impl_->vertex_buffers_) {
204  engine.destroy(vb);
205  }
206  for (auto& ib : impl_->index_buffers_) {
207  engine.destroy(ib);
208  }
209 }
210 
211 // To help with mapping unique scissor rectangles to material instances, we
212 // create a 64-bit key from a 4-tuple that defines an AABB in screen space.
214 public:
215  ScissorRectKey(int fb_height, const ImVec4& clip_rect, ImTextureID tex_id) {
216  left_ = (uint16_t)clip_rect.x;
217  bottom_ = (uint16_t)(fb_height - clip_rect.w);
218  width_ = (uint16_t)(clip_rect.z - clip_rect.x);
219  height_ = (uint16_t)(clip_rect.w - clip_rect.y);
220  rect_ = ((uint64_t)left_ << 0ull) | ((uint64_t)bottom_ << 16ull) |
221  ((uint64_t)width_ << 32ull) | ((uint64_t)height_ << 48ull);
222  id_ = tex_id;
223  }
224 
225  bool operator==(const ScissorRectKey& other) const {
226  if (id_ == other.id_) {
227  return (rect_ == other.rect_);
228  }
229  return false;
230  }
231 
232  bool operator!=(const ScissorRectKey& other) const {
233  return !operator==(other);
234  }
235 
236  bool operator<(const ScissorRectKey& other) const {
237  if (id_ == other.id_) {
238  return (rect_ < other.rect_);
239  }
240  return (id_ < other.id_);
241  }
242 
243  // Used for comparisons
244  uint64_t rect_;
245  ImTextureID id_;
246 
247  // Not used for comparisons
248  uint16_t left_;
249  uint16_t bottom_;
250  uint16_t width_;
251  uint16_t height_;
252 };
253 
254 void ImguiFilamentBridge::Update(ImDrawData* imgui_data) {
255  impl_->has_synced_ = false;
256 
258 
259  auto& rcm = engine.getRenderableManager();
260 
261  // Avoid rendering when minimized and scale coordinates for retina displays.
262  ImGuiIO& io = ImGui::GetIO();
263  int fbwidth = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x);
264  int fbheight = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y);
265  if (fbwidth == 0 || fbheight == 0) return;
266  imgui_data->ScaleClipRects(io.DisplayFramebufferScale);
267 
268  // Ensure that we have enough vertex buffers and index buffers.
269  CreateBuffers(imgui_data->CmdListsCount);
270 
271  // Count how many primitives we'll need, then create a Renderable builder.
272  // Also count how many unique scissor rectangles are required.
273  size_t num_prims = 0;
274  std::map<ScissorRectKey, filament::MaterialInstance*> scissor_rects;
275  for (int idx = 0; idx < imgui_data->CmdListsCount; idx++) {
276  const ImDrawList* cmds = imgui_data->CmdLists[idx];
277  num_prims += cmds->CmdBuffer.size();
278  for (const auto& pcmd : cmds->CmdBuffer) {
279  scissor_rects[ScissorRectKey(fbheight, pcmd.ClipRect,
280  pcmd.TextureId)] = nullptr;
281  }
282  }
283  auto rbuilder = RenderableManager::Builder(num_prims);
284  rbuilder.boundingBox({{0, 0, 0}, {10000, 10000, 10000}}).culling(false);
285 
286  // Push each unique scissor rectangle to a MaterialInstance.
287  impl_->uiblit_pool_.reset();
288  impl_->image_pool_.reset();
289  TextureSampler sampler(TextureSampler::MinFilter::LINEAR,
290  TextureSampler::MagFilter::LINEAR);
291  for (auto& pair : scissor_rects) {
292  if (pair.first.id_ == 0) {
293  pair.second = impl_->uiblit_pool_.pull();
294  // Don't need to set texture, since we set the font texture
295  // as the default when we created this material.
296  } else {
297  pair.second = impl_->image_pool_.pull();
298  auto tex_id_long = reinterpret_cast<uintptr_t>(pair.first.id_);
299  auto tex_id = std::uint16_t(tex_id_long);
300  auto tex_handle = visualization::rendering::TextureHandle(tex_id);
303  .GetTexture(tex_handle);
304  auto tex_sh_ptr = tex.lock();
305  pair.second->setParameter(kImageTexParamName, tex_sh_ptr.get(),
306  sampler);
307  }
308  pair.second->setScissor(pair.first.left_, pair.first.bottom_,
309  pair.first.width_, pair.first.height_);
310  }
311 
312  // Recreate the Renderable component and point it to the vertex buffers.
313  rcm.destroy(impl_->renderable_);
314  int buffer_index = 0;
315  int prim_index = 0;
316  for (int cmd_idx = 0; cmd_idx < imgui_data->CmdListsCount; cmd_idx++) {
317  const ImDrawList* cmds = imgui_data->CmdLists[cmd_idx];
318  size_t indexOffset = 0;
319  PopulateVertexData(
320  buffer_index, cmds->VtxBuffer.Size * sizeof(ImDrawVert),
321  cmds->VtxBuffer.Data, cmds->IdxBuffer.Size * sizeof(ImDrawIdx),
322  cmds->IdxBuffer.Data);
323  for (const auto& pcmd : cmds->CmdBuffer) {
324  if (pcmd.UserCallback) {
325  pcmd.UserCallback(cmds, &pcmd);
326  } else {
327  auto skey =
328  ScissorRectKey(fbheight, pcmd.ClipRect, pcmd.TextureId);
329  auto miter = scissor_rects.find(skey);
330  if (miter != scissor_rects.end()) {
331  rbuilder.geometry(
332  prim_index,
333  RenderableManager::PrimitiveType::TRIANGLES,
334  impl_->vertex_buffers_[buffer_index],
335  impl_->index_buffers_[buffer_index],
336  indexOffset, pcmd.ElemCount)
337  .blendOrder(prim_index, prim_index)
338  .material(prim_index, miter->second);
339  prim_index++;
340  } else {
341  utility::LogError("Internal error: material not found.");
342  }
343  }
344  indexOffset += pcmd.ElemCount;
345  }
346  buffer_index++;
347  }
348  if (imgui_data->CmdListsCount > 0) {
349  rbuilder.build(engine, impl_->renderable_);
350  }
351 }
352 
354  auto size = window.GetSize();
355  impl_->view_->SetViewport(0, 0, size.width, size.height);
356 
357  auto camera = impl_->view_->GetCamera();
359  0.0, size.width, size.height, 0.0, 0.0, 1.0);
360 }
361 
362 void ImguiFilamentBridge::CreateVertexBuffer(size_t buffer_index,
363  size_t capacity) {
364  SyncThreads();
365 
367 
368  engine.destroy(impl_->vertex_buffers_[buffer_index]);
369  impl_->vertex_buffers_[buffer_index] =
370  VertexBuffer::Builder()
371  .vertexCount(std::uint32_t(capacity))
372  .bufferCount(1)
373  .attribute(VertexAttribute::POSITION, 0,
374  VertexBuffer::AttributeType::FLOAT2, 0,
375  sizeof(ImDrawVert))
376  .attribute(VertexAttribute::UV0, 0,
377  VertexBuffer::AttributeType::FLOAT2,
378  sizeof(filament::math::float2),
379  sizeof(ImDrawVert))
380  .attribute(VertexAttribute::COLOR, 0,
381  VertexBuffer::AttributeType::UBYTE4,
382  2 * sizeof(filament::math::float2),
383  sizeof(ImDrawVert))
384  .normalized(VertexAttribute::COLOR)
385  .build(engine);
386 }
387 
388 void ImguiFilamentBridge::CreateIndexBuffer(size_t buffer_index,
389  size_t capacity) {
390  SyncThreads();
391 
393 
394  engine.destroy(impl_->index_buffers_[buffer_index]);
395  impl_->index_buffers_[buffer_index] =
396  IndexBuffer::Builder()
397  .indexCount(std::uint32_t(capacity))
398  .bufferType(IndexBuffer::IndexType::USHORT)
399  .build(engine);
400 }
401 
402 void ImguiFilamentBridge::CreateBuffers(size_t num_required_buffers) {
403  if (num_required_buffers > impl_->vertex_buffers_.size()) {
404  size_t previous_size = impl_->vertex_buffers_.size();
405  impl_->vertex_buffers_.resize(num_required_buffers, nullptr);
406  for (size_t i = previous_size; i < impl_->vertex_buffers_.size(); i++) {
407  // Pick a reasonable starting capacity; it will grow if needed.
408  CreateVertexBuffer(i, 1000);
409  }
410  }
411  if (num_required_buffers > impl_->index_buffers_.size()) {
412  size_t previous_size = impl_->index_buffers_.size();
413  impl_->index_buffers_.resize(num_required_buffers, nullptr);
414  for (size_t i = previous_size; i < impl_->index_buffers_.size(); i++) {
415  // Pick a reasonable starting capacity; it will grow if needed.
416  CreateIndexBuffer(i, 5000);
417  }
418  }
419 }
420 
421 void ImguiFilamentBridge::PopulateVertexData(size_t buffer_index,
422  size_t vb_size_in_bytes,
423  void* vb_imgui_data,
424  size_t ib_size_in_bytes,
425  void* ib_imgui_data) {
427 
428  // Create a new vertex buffer if the size isn't large enough, then copy the
429  // ImGui data into a staging area since Filament's render thread might
430  // consume the data at any time.
431  size_t required_vert_count = vb_size_in_bytes / sizeof(ImDrawVert);
432  size_t capacity_vert_count =
433  impl_->vertex_buffers_[buffer_index]->getVertexCount();
434  if (required_vert_count > capacity_vert_count) {
435  CreateVertexBuffer(buffer_index, required_vert_count);
436  }
437  size_t num_vb_bytes = required_vert_count * sizeof(ImDrawVert);
438  void* vb_filament_data = malloc(num_vb_bytes);
439  memcpy(vb_filament_data, vb_imgui_data, num_vb_bytes);
440  impl_->vertex_buffers_[buffer_index]->setBufferAt(
441  engine, 0,
442  VertexBuffer::BufferDescriptor(
443  vb_filament_data, num_vb_bytes,
444  [](void* buffer, size_t size, void* user) { free(buffer); },
445  /* user = */ nullptr));
446 
447  // Create a new index buffer if the size isn't large enough, then copy the
448  // ImGui data into a staging area since Filament's render thread might
449  // consume the data at any time.
450  size_t required_index_count = ib_size_in_bytes / 2;
451  size_t capacity_index_count =
452  impl_->index_buffers_[buffer_index]->getIndexCount();
453  if (required_index_count > capacity_index_count) {
454  CreateIndexBuffer(buffer_index, required_index_count);
455  }
456  size_t num_ib_bytes = required_index_count * 2;
457  void* ib_filament_data = malloc(num_ib_bytes);
458  memcpy(ib_filament_data, ib_imgui_data, num_ib_bytes);
459  impl_->index_buffers_[buffer_index]->setBuffer(
460  engine,
461  IndexBuffer::BufferDescriptor(
462  ib_filament_data, num_ib_bytes,
463  [](void* buffer, size_t size, void* user) { free(buffer); },
464  /* user = */ nullptr));
465 }
466 
467 void ImguiFilamentBridge::SyncThreads() {
468 #if UTILS_HAS_THREADING
469  if (!impl_->has_synced_) {
471 
472  // This is called only when ImGui needs to grow a vertex buffer, which
473  // occurs a few times after launching and rarely (if ever) after that.
474  Fence::waitAndDestroy(engine.createFence());
475  impl_->has_synced_ = true;
476  }
477 #endif
478 }
479 
480 } // namespace gui
481 } // namespace visualization
482 } // namespace cloudViewer
int width
int size
int height
void * bytes
std::vector< UVAtlasVertex > vb
std::vector< uint8_t > ib
void CreateAtlasTextureAlpha8(unsigned char *pixels, int width, int height, int bytes_per_px)
MaterialPool(filament::Engine *engine, filament::Material *material_template)
ScissorRectKey(int fb_height, const ImVec4 &clip_rect, ImTextureID tex_id)
bool operator!=(const ScissorRectKey &other) const
bool operator==(const ScissorRectKey &other) const
bool operator<(const ScissorRectKey &other) const
static FilamentResourceManager & GetResourceManager()
std::weak_ptr< filament::Texture > GetTexture(const TextureHandle &id)
ViewHandle AddView(std::int32_t x, std::int32_t y, std::uint32_t w, std::uint32_t h) override
#define LogError(...)
Definition: Logging.h:60
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
static const std::string path
Definition: PointCloud.cpp:59
bool FReadToBuffer(const std::string &path, std::vector< char > &bytes, std::string *errorStr)
Definition: FileSystem.cpp:678
static Material * LoadMaterialTemplate(const std::string &path, Engine &engine)
REHandle< EntityType::Texture > TextureHandle
Generic file read and write utility for python interface.