#include "IUI/ImGuiFilamentBridge.h" #include #include #include "IUI/ImGui/imgui.h" #include #include #include #include #include #include #include #include #include #include #include "tl/narrow_cast.h" using namespace filament::math; using namespace filament; using namespace utils; namespace kit { static constexpr uint8_t IMGUI_PACKAGE[] = { // ReSharper disable All #include "Materials/ImGuiMaterial.h" // ReSharper restore All }; ImGuiFilamentBridge::ImGuiFilamentBridge(tl::lent_ref engineManager, tl::lent_ref flmScene) : m_engineManager(std::move(engineManager)) , m_scene(std::move(flmScene)) { // Create a simple alpha-blended 2D blitting material. auto builder = Material::Builder() .package(IMGUI_PACKAGE, sizeof(IMGUI_PACKAGE)); m_material = m_engineManager->adopt(builder.build(*m_engineManager->getEngine())); EntityManager& em = utils::EntityManager::get(); m_renderable = em.create(); m_scene->addEntity(m_renderable); } ImTextureID ImGuiFilamentBridge::createAtlasTexture() { const ImGuiIO& io = ImGui::GetIO(); // Create the grayscale texture that ImGui uses for its glyph atlas. unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); const size_t size = tl::narrow(width * height * 4); uint8_t* buffer = m_engineManager->acquireUploadMemory(size); memcpy(buffer, pixels, size); auto builder = Texture::Builder() .width(tl::narrow(width)) .height(tl::narrow(height)) .levels(tl::narrow(1)) .format(Texture::InternalFormat::RGBA8) .sampler(Texture::Sampler::SAMPLER_2D); m_texture = m_engineManager->adopt(builder.build(*m_engineManager->getEngine())); m_texture->setImage(*m_engineManager->getEngine(), 0, Texture::PixelBufferDescriptor(buffer, size, Texture::Format::RGBA, Texture::Type::UBYTE)); const TextureSampler sampler(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR, TextureSampler::MagFilter::LINEAR); m_material->setDefaultParameter("albedo", m_texture.get(), sampler); for (const auto& mi : m_materialInstances) mi->setParameter("albedo", m_texture.get(), sampler); return m_texture.get(); } ImGuiFilamentBridge::~ImGuiFilamentBridge() { m_engineManager->destroy(m_renderable); } //void ImGuiFilamentBridge::setDisplaySize(int width, int height, float scaleX, float scaleY) //{ // setContext(); // ImGuiIO& io = ImGui::GetIO(); // io.DisplaySize = ImVec2((float)width, (float)height); // io.DisplayFramebufferScale.x = scaleX; // io.DisplayFramebufferScale.y = scaleY; //} void ImGuiFilamentBridge::processImGuiCommands(ImDrawData& commands, const ImGuiIO& io) { auto& rcm = m_engineManager->getEngine()->getRenderableManager(); // Avoid rendering when minimized and scale coordinates for retina displays. const int fbwidth = static_cast(commands.DisplaySize.x * commands.FramebufferScale.x); const int fbheight = static_cast(commands.DisplaySize.y * commands.FramebufferScale.y); if (fbwidth == 0 || fbheight == 0) return; //commands.ScaleClipRects(commands.FramebufferScale); // Ensure that we have enough vertex buffers and index buffers. createBuffers(commands.CmdListsCount); // Count how many primitives we'll need, then create a Renderable builder. size_t primitiveCount = 0; //tl::unordered_map scissorRects; for (int cmdListIndex = 0; cmdListIndex < commands.CmdListsCount; cmdListIndex++) { const ImDrawList* cmds = commands.CmdLists[cmdListIndex]; primitiveCount += cmds->CmdBuffer.size(); } auto rbuilder = RenderableManager::Builder(primitiveCount); rbuilder.boundingBox({{0, 0, 0}, {10000, 10000, 10000}}).culling(false); // Ensure that we have a material instance for each primitive. const size_t previousSize = m_materialInstances.size(); if (primitiveCount > m_materialInstances.size()) { //m_materialInstances.resize(primitiveCount); for (size_t i = previousSize; i < primitiveCount; i++) m_materialInstances.push_back(m_engineManager->adopt(m_material->createInstance())); } const ImVec2 clipOff = commands.DisplayPos; // (0,0) unless using multi-viewports const ImVec2 clipScale = commands.FramebufferScale; // (1,1) unless using retina display which are often (2,2) // Recreate the Renderable component and point it to the vertex buffers. rcm.destroy(m_renderable); size_t bufferIndex = 0; size_t primIndex = 0; for (int cmdListIndex = 0; cmdListIndex < commands.CmdListsCount; cmdListIndex++) { const ImDrawList* cmds = commands.CmdLists[cmdListIndex]; size_t indexOffset = 0; populateVertexData(bufferIndex, cmds->VtxBuffer.Size * sizeof(ImDrawVert), cmds->VtxBuffer.Data, cmds->IdxBuffer.Size * sizeof(ImDrawIdx), cmds->IdxBuffer.Data); for (const auto& pcmd : cmds->CmdBuffer) { if (pcmd.UserCallback) pcmd.UserCallback(cmds, &pcmd); else { MaterialInstance& materialInstance = *m_materialInstances[primIndex]; // Project scissor/clipping rectangles into framebuffer space const ImVec2 clipMin((pcmd.ClipRect.x - clipOff.x) * clipScale.x, (pcmd.ClipRect.y - clipOff.y) * clipScale.y); const ImVec2 clipMax((pcmd.ClipRect.z - clipOff.x) * clipScale.x, (pcmd.ClipRect.w - clipOff.y) * clipScale.y); materialInstance.setScissor(uint32_t(clipMin.x), uint32_t(fbheight - clipMax.y), (uint16_t)(clipMax.x - clipMin.x), (uint16_t)(clipMax.y - clipMin.y)); TextureSampler sampler(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR, TextureSampler::MagFilter::LINEAR); if (pcmd.TextureId) materialInstance.setParameter("albedo", (Texture const*)pcmd.TextureId, sampler); else materialInstance.setParameter("albedo", m_texture.get(), sampler); rbuilder .geometry(primIndex, RenderableManager::PrimitiveType::TRIANGLES, m_vertexBuffers[bufferIndex].get(), m_indexBuffers[bufferIndex].get(), indexOffset, pcmd.ElemCount) .blendOrder(primIndex, tl::narrow(primIndex)) .material(primIndex, &materialInstance); primIndex++; } indexOffset += pcmd.ElemCount; } bufferIndex++; } if (commands.CmdListsCount > 0) rbuilder.build(*m_engineManager->getEngine(), m_renderable); } tl::unique_ref ImGuiFilamentBridge::createVertexBuffer(size_t capacity) const { auto builder = VertexBuffer::Builder() .vertexCount(uint32_t(capacity)) .bufferCount(1) .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, sizeof(ImDrawVert)) .attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, sizeof(filament::math::float2), sizeof(ImDrawVert)) .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 2 * sizeof(filament::math::float2), sizeof(ImDrawVert)) .normalized(COLOR); return m_engineManager->adopt(builder.build(*m_engineManager->getEngine())); } tl::unique_ref ImGuiFilamentBridge::createIndexBuffer(size_t capacity) const { auto builder = IndexBuffer::Builder() .indexCount(uint32_t(capacity)) .bufferType(IndexBuffer::IndexType::UINT); return m_engineManager->adopt(builder.build(*m_engineManager->getEngine())); } void ImGuiFilamentBridge::createBuffers(int numRequiredBuffers) { if (numRequiredBuffers > m_vertexBuffers.size()) { const size_t previousSize = m_vertexBuffers.size(); for (size_t i = previousSize; i < numRequiredBuffers; i++) { // Pick a reasonable starting capacity; it will grow if needed. m_vertexBuffers.push_back(createVertexBuffer(1000)); } } if (numRequiredBuffers > m_indexBuffers.size()) { const size_t previousSize = m_indexBuffers.size(); for (size_t i = previousSize; i < numRequiredBuffers; i++) { // Pick a reasonable starting capacity; it will grow if needed. m_indexBuffers.push_back(createIndexBuffer(1000)); } } } void ImGuiFilamentBridge::populateVertexData(size_t bufferIndex, size_t vbSizeInBytes, void* vbImguiData, size_t ibSizeInBytes, void* ibImguiData) { // Create a new vertex buffer if the size isn't large enough, then copy the ImGui data into // a staging area since Filament's render thread might consume the data at any time. const size_t requiredVertCount = vbSizeInBytes / sizeof(ImDrawVert); const size_t capacityVertCount = m_vertexBuffers[bufferIndex]->getVertexCount(); if (requiredVertCount > capacityVertCount) m_vertexBuffers[bufferIndex] = createVertexBuffer(requiredVertCount); { const size_t nVbBytes = requiredVertCount * sizeof(ImDrawVert); uint8_t* vbFilamentData = m_engineManager->acquireUploadMemory(nVbBytes); memcpy(vbFilamentData, vbImguiData, nVbBytes); m_vertexBuffers[bufferIndex]->setBufferAt(*m_engineManager->getEngine(), 0, VertexBuffer::BufferDescriptor(vbFilamentData, nVbBytes)); } // Create a new index buffer if the size isn't large enough, then copy the ImGui data into // a staging area since Filament's render thread might consume the data at any time. const size_t requiredIndexCount = ibSizeInBytes / sizeof(ImDrawIdx); const size_t capacityIndexCount = m_indexBuffers[bufferIndex]->getIndexCount(); if (requiredIndexCount > capacityIndexCount) m_indexBuffers[bufferIndex] = createIndexBuffer(requiredIndexCount); { const size_t nIbBytes = requiredIndexCount * sizeof(ImDrawIdx); uint8_t* ibFilamentData = m_engineManager->acquireUploadMemory(nIbBytes); memcpy(ibFilamentData, ibImguiData, nIbBytes); m_indexBuffers[bufferIndex]->setBuffer(*m_engineManager->getEngine(), IndexBuffer::BufferDescriptor(ibFilamentData, nIbBytes)); } } } // namespace filagui