#include "MetaCoreRender/MetaCoreImGuiHelper.h" #include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h" #include #include #include #include #include #include #include #include #include #include #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace filament::math; using namespace filament; using namespace utils; using MinFilter = TextureSampler::MinFilter; using MagFilter = TextureSampler::MagFilter; namespace MetaCore { namespace { [[nodiscard]] std::filesystem::path MetaCoreGetExecutableDirectory() { std::array executablePath{}; const DWORD length = GetModuleFileNameW( nullptr, executablePath.data(), static_cast(executablePath.size()) ); if (length == 0 || length >= executablePath.size()) { return {}; } return std::filesystem::path(executablePath.data()).parent_path(); } [[nodiscard]] std::ifstream MetaCoreOpenUiBlitMaterial() { std::vector candidates{ "uiBlit.filamat", "../uiBlit.filamat", "build/uiBlit.filamat" }; const std::filesystem::path executableDirectory = MetaCoreGetExecutableDirectory(); if (!executableDirectory.empty()) { candidates.push_back(executableDirectory / "uiBlit.filamat"); } for (const std::filesystem::path& candidate : candidates) { std::ifstream file(candidate, std::ios::binary); if (file.is_open()) { return file; } } return {}; } } // namespace MetaCoreImGuiHelper::MetaCoreImGuiHelper(Engine* engine, filament::View* view, const std::filesystem::path& fontPath, ImGuiContext *imGuiContext) : mEngine(engine), mView(view), mScene(engine->createScene()), mImGuiContext(imGuiContext ? imGuiContext : ImGui::CreateContext()), mOwnsImGuiContext(imGuiContext == nullptr), mPreviousImGuiContext(ImGui::GetCurrentContext()) { ImGui::SetCurrentContext(mImGuiContext); ImGuiIO& io = ImGui::GetIO(); // 加载我们编译好的材质 std::ifstream file = MetaCoreOpenUiBlitMaterial(); if (!file.is_open()) { std::cerr << "MetaCoreImGuiHelper: Failed to open uiBlit.filamat from any expected location!" << std::endl; } std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); if (buffer.empty()) { std::cerr << "MetaCoreImGuiHelper: Material buffer is empty! Skipping material creation to avoid crash." << std::endl; mMaterial2d = nullptr; } else { mMaterial2d = Material::Builder() .package(buffer.data(), buffer.size()) .build(*engine); } if (!fontPath.empty() && std::filesystem::exists(fontPath)) { io.Fonts->AddFontFromFileTTF(fontPath.string().c_str(), 16.0f, nullptr, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); } createAtlasTexture(engine); utils::EntityManager& em = utils::EntityManager::get(); mCameraEntity = em.create(); mCamera = mEngine->createCamera(mCameraEntity); view->setCamera(mCamera); view->setPostProcessingEnabled(false); view->setBlendMode(View::BlendMode::TRANSLUCENT); view->setShadowingEnabled(false); // 绑定场景 view->setScene(mScene); mRenderable = em.create(); mScene->addEntity(mRenderable); if (mOwnsImGuiContext) { // 只有在拥有私有 ImGui 上下文时才调用 StyleColorsDark() ImGui::StyleColorsDark(); } } void MetaCoreImGuiHelper::createAtlasTexture(Engine* engine) { if (mTexture) { engine->destroy(mTexture); } ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); size_t size = (size_t) (width * height * 4); Texture::PixelBufferDescriptor pb( pixels, size, Texture::Format::RGBA, Texture::Type::UBYTE); mTexture = Texture::Builder() .width((uint32_t) width) .height((uint32_t) height) .levels((uint8_t) 1) .format(Texture::InternalFormat::RGBA8) .sampler(Texture::Sampler::SAMPLER_2D) .build(*engine); mTexture->setImage(*engine, 0, std::move(pb)); mSampler = TextureSampler(MinFilter::LINEAR, MagFilter::LINEAR); if (mMaterial2d) { mMaterial2d->setDefaultParameter("albedo", mTexture, mSampler); } } MetaCoreImGuiHelper::~MetaCoreImGuiHelper() { if (mView != nullptr) { mView->setScene(nullptr); mView->setCamera(nullptr); } if (mScene != nullptr) { mScene->remove(mRenderable); } mEngine->destroy(mRenderable); for (auto& mi : mMaterial2dInstances) { if (mi != nullptr) { mEngine->destroy(mi); } } mMaterial2dInstances.clear(); if (mMaterial2d != nullptr) { mEngine->destroy(mMaterial2d); mMaterial2d = nullptr; } if (mTexture != nullptr) { mEngine->destroy(mTexture); mTexture = nullptr; } if (mWhiteTexture != nullptr) { mEngine->destroy(mWhiteTexture); mWhiteTexture = nullptr; } for (auto& vb : mVertexBuffers) { if (vb != nullptr) { mEngine->destroy(vb); } } mVertexBuffers.clear(); for (auto& ib : mIndexBuffers) { if (ib != nullptr) { mEngine->destroy(ib); } } mIndexBuffers.clear(); for (auto* texture : mImGuiTextures) { if (texture != nullptr) { mEngine->destroy(texture); } } mImGuiTextures.clear(); for (auto& [handle, textureState] : mRuntimeUiTextures) { (void)handle; if (textureState.Texture != nullptr) { mEngine->destroy(textureState.Texture); textureState.Texture = nullptr; } } mRuntimeUiTextures.clear(); if (mScene != nullptr) { mEngine->destroy(mScene); mScene = nullptr; } if (mCamera != nullptr) { mEngine->destroyCameraComponent(mCameraEntity); mCamera = nullptr; } EntityManager& em = utils::EntityManager::get(); em.destroy(mRenderable); em.destroy(mCameraEntity); if (mOwnsImGuiContext) { ImGui::DestroyContext(mImGuiContext); } else if (mImGuiContext == ImGui::GetCurrentContext()) { ImGui::SetCurrentContext(mPreviousImGuiContext); } } void MetaCoreImGuiHelper::setDisplaySize(int width, int height, float scaleX, float scaleY, bool flipVertical) { if (width <= 0 || height <= 0) { return; } ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2(width, height); io.DisplayFramebufferScale.x = scaleX; io.DisplayFramebufferScale.y = scaleY; mFlipVertical = flipVertical; if (flipVertical) { mCamera->setProjection(Camera::Projection::ORTHO, 0.0, double(width), 0.0, double(height), 0.0, 1.0); } else { mCamera->setProjection(Camera::Projection::ORTHO, 0.0, double(width), double(height), 0.0, 0.0, 1.0); } } void MetaCoreImGuiHelper::render(float timeStepInSeconds, Callback imguiCommands) { ImGui::SetCurrentContext(mImGuiContext); ImGuiIO& io = ImGui::GetIO(); io.DeltaTime = timeStepInSeconds; ImGui::NewFrame(); imguiCommands(mEngine, mView); ImGui::Render(); processImGuiCommands(ImGui::GetDrawData(), ImGui::GetIO()); } void MetaCoreImGuiHelper::processImGuiCommands(ImDrawData* commands, const ImGuiIO& io) { ImGui::SetCurrentContext(mImGuiContext); mHasSynced = false; auto& rcm = mEngine->getRenderableManager(); int fbwidth = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); int fbheight = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); if (fbwidth == 0 || fbheight == 0) return; commands->ScaleClipRects(io.DisplayFramebufferScale); createBuffers(commands->CmdListsCount); size_t nPrims = 0; for (int cmdListIndex = 0; cmdListIndex < commands->CmdListsCount; cmdListIndex++) { const ImDrawList* cmds = commands->CmdLists[cmdListIndex]; nPrims += cmds->CmdBuffer.size(); } auto rbuilder = RenderableManager::Builder(nPrims); rbuilder.boundingBox({{ 0, 0, 0 }, { 10000, 10000, 10000 }}).culling(false); rcm.destroy(mRenderable); int bufferIndex = 0; int primIndex = 0; int material2dIndex = 0; for (int cmdListIndex = 0; cmdListIndex < commands->CmdListsCount; cmdListIndex++) { const ImDrawList* cmds = commands->CmdLists[cmdListIndex]; 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 { auto texture = (Texture const*)pcmd.GetTexID(); MaterialInstance* materialInstance; if (material2dIndex == mMaterial2dInstances.size()) { if (mMaterial2d) { mMaterial2dInstances.push_back(mMaterial2d->createInstance()); } else { mMaterial2dInstances.push_back(nullptr); } } materialInstance = mMaterial2dInstances[material2dIndex++]; if (materialInstance) { auto scissorLeft = static_cast(std::max(0.0f, pcmd.ClipRect.x)); auto scissorBottom = static_cast(std::max(0.0f, mFlipVertical ? pcmd.ClipRect.y : (fbheight - pcmd.ClipRect.w))); auto scissorWidth = static_cast(pcmd.ClipRect.z - pcmd.ClipRect.x); auto scissorHeight = static_cast(pcmd.ClipRect.w - pcmd.ClipRect.y); materialInstance->setScissor(scissorLeft, scissorBottom, scissorWidth, scissorHeight); if (texture) { TextureSampler sampler(MinFilter::LINEAR, MagFilter::LINEAR); materialInstance->setParameter("albedo", texture, sampler); } else { materialInstance->setParameter("albedo", mTexture, mSampler); } rbuilder .geometry(primIndex, RenderableManager::PrimitiveType::TRIANGLES, mVertexBuffers[bufferIndex], mIndexBuffers[bufferIndex], pcmd.IdxOffset, pcmd.ElemCount) .blendOrder(primIndex, primIndex) .material(primIndex, materialInstance); primIndex++; } } } bufferIndex++; } if (commands->CmdListsCount > 0) { rbuilder.build(*mEngine, mRenderable); } } filament::Texture* MetaCoreImGuiHelper::getWhiteTexture() { if (mWhiteTexture != nullptr) { return mWhiteTexture; } const std::uint8_t whitePixel[4] = {255, 255, 255, 255}; void* whitePixelData = malloc(sizeof(whitePixel)); if (whitePixelData == nullptr) { return nullptr; } std::memcpy(whitePixelData, whitePixel, sizeof(whitePixel)); Texture::PixelBufferDescriptor whitePixelBuffer( whitePixelData, sizeof(whitePixel), Texture::Format::RGBA, Texture::Type::UBYTE, [](void* buffer, size_t, void*) { free(buffer); }, nullptr); mWhiteTexture = Texture::Builder() .width(1) .height(1) .levels(1) .format(Texture::InternalFormat::RGBA8) .sampler(Texture::Sampler::SAMPLER_2D) .build(*mEngine); mWhiteTexture->setImage(*mEngine, 0, std::move(whitePixelBuffer)); return mWhiteTexture; } filament::Texture* MetaCoreImGuiHelper::syncRuntimeUiTexture(const MetaCoreRuntimeUiFrame& frame, std::uint64_t handle) { if (handle == 0) { return nullptr; } const auto textureIterator = std::find_if( frame.Textures.begin(), frame.Textures.end(), [handle](const MetaCoreRuntimeUiTexture& texture) { return texture.Handle == handle; }); if (textureIterator == frame.Textures.end()) { return nullptr; } const MetaCoreRuntimeUiTexture& texture = *textureIterator; if (texture.Width <= 0 || texture.Height <= 0) { return nullptr; } const std::size_t expectedByteCount = static_cast(texture.Width) * static_cast(texture.Height) * 4U; if (expectedByteCount == 0 || texture.Rgba.size() < expectedByteCount) { return nullptr; } RuntimeUiTextureState& state = mRuntimeUiTextures[handle]; if (state.Texture != nullptr && state.Width == texture.Width && state.Height == texture.Height && state.Revision == texture.Revision) { return state.Texture; } if (state.Texture != nullptr) { mEngine->destroy(state.Texture); state.Texture = nullptr; } void* pixelData = malloc(expectedByteCount); if (pixelData == nullptr) { return nullptr; } std::memcpy(pixelData, texture.Rgba.data(), expectedByteCount); Texture::PixelBufferDescriptor pixelBuffer( pixelData, expectedByteCount, Texture::Format::RGBA, Texture::Type::UBYTE, [](void* buffer, size_t, void*) { free(buffer); }, nullptr); state.Texture = Texture::Builder() .width(static_cast(texture.Width)) .height(static_cast(texture.Height)) .levels(1) .format(Texture::InternalFormat::RGBA8) .sampler(Texture::Sampler::SAMPLER_2D) .build(*mEngine); state.Texture->setImage(*mEngine, 0, std::move(pixelBuffer)); state.Width = texture.Width; state.Height = texture.Height; state.Revision = texture.Revision; return state.Texture; } bool MetaCoreImGuiHelper::processRuntimeUiFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height) { ImGui::SetCurrentContext(mImGuiContext); mHasSynced = false; auto& rcm = mEngine->getRenderableManager(); rcm.destroy(mRenderable); if (width <= 0 || height <= 0) { return false; } if (frame.Commands.empty() || frame.Vertices.empty() || frame.Indices.empty()) { return true; } if (frame.Vertices.size() > static_cast(std::numeric_limits::max())) { return false; } std::unordered_set activeTextureHandles; activeTextureHandles.reserve(frame.Textures.size()); for (const MetaCoreRuntimeUiTexture& texture : frame.Textures) { if (texture.Handle != 0) { activeTextureHandles.insert(texture.Handle); } } for (auto iterator = mRuntimeUiTextures.begin(); iterator != mRuntimeUiTextures.end();) { if (activeTextureHandles.contains(iterator->first)) { ++iterator; continue; } if (iterator->second.Texture != nullptr) { mEngine->destroy(iterator->second.Texture); } iterator = mRuntimeUiTextures.erase(iterator); } createBuffers(1); std::vector vertices; vertices.reserve(frame.Vertices.size()); for (const MetaCoreRuntimeUiDrawVertex& vertex : frame.Vertices) { ImDrawVert converted{}; converted.pos = ImVec2(vertex.X, vertex.Y); converted.uv = ImVec2(vertex.U, vertex.V); converted.col = IM_COL32(vertex.R, vertex.G, vertex.B, vertex.A); vertices.push_back(converted); } std::vector indices; indices.reserve(frame.Indices.size()); for (const std::uint32_t index : frame.Indices) { if (index > static_cast(std::numeric_limits::max())) { return false; } indices.push_back(static_cast(index)); } populateVertexData( 0, vertices.size() * sizeof(ImDrawVert), vertices.data(), indices.size() * sizeof(std::uint16_t), indices.data()); std::size_t primitiveCount = 0; for (const MetaCoreRuntimeUiDrawCommand& command : frame.Commands) { if (command.IndexCount == 0 || command.VertexCount == 0 || command.VertexOffset + command.VertexCount > frame.Vertices.size() || command.IndexOffset + command.IndexCount > frame.Indices.size()) { continue; } ++primitiveCount; } if (primitiveCount == 0) { return true; } auto rbuilder = RenderableManager::Builder(primitiveCount); rbuilder.boundingBox({{ 0, 0, 0 }, { 10000, 10000, 10000 }}).culling(false); int primitiveIndex = 0; int material2dIndex = 0; for (const MetaCoreRuntimeUiDrawCommand& command : frame.Commands) { if (command.IndexCount == 0 || command.VertexCount == 0 || command.VertexOffset + command.VertexCount > frame.Vertices.size() || command.IndexOffset + command.IndexCount > frame.Indices.size()) { continue; } if (material2dIndex == static_cast(mMaterial2dInstances.size())) { if (mMaterial2d) { mMaterial2dInstances.push_back(mMaterial2d->createInstance()); } else { mMaterial2dInstances.push_back(nullptr); } } MaterialInstance* materialInstance = mMaterial2dInstances[material2dIndex++]; if (!materialInstance) { continue; } if (command.ScissorEnabled) { const int left = std::clamp(command.ScissorLeft, 0, width); const int top = std::clamp(command.ScissorTop, 0, height); const int right = std::clamp(command.ScissorRight, left, width); const int bottom = std::clamp(command.ScissorBottom, top, height); materialInstance->setScissor( static_cast(left), static_cast(height - bottom), static_cast(right - left), static_cast(bottom - top)); } else { materialInstance->unsetScissor(); } filament::Texture* runtimeTexture = syncRuntimeUiTexture(frame, command.TextureHandle); filament::Texture* fallbackTexture = getWhiteTexture(); materialInstance->setParameter( "albedo", runtimeTexture != nullptr ? runtimeTexture : (fallbackTexture != nullptr ? fallbackTexture : mTexture), mSampler); rbuilder .geometry(primitiveIndex, RenderableManager::PrimitiveType::TRIANGLES, mVertexBuffers[0], mIndexBuffers[0], static_cast(command.IndexOffset), static_cast(command.IndexCount)) .blendOrder(primitiveIndex, static_cast(primitiveIndex)) .material(primitiveIndex, materialInstance); ++primitiveIndex; } if (primitiveIndex > 0) { rbuilder.build(*mEngine, mRenderable); } return true; } void MetaCoreImGuiHelper::createVertexBuffer(size_t bufferIndex, size_t capacity) { syncThreads(); if (bufferIndex < mVertexBuffers.size() && mVertexBuffers[bufferIndex]) { mEngine->destroy(mVertexBuffers[bufferIndex]); } mVertexBuffers[bufferIndex] = VertexBuffer::Builder() .vertexCount(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(VertexAttribute::COLOR) .build(*mEngine); } void MetaCoreImGuiHelper::createIndexBuffer(size_t bufferIndex, size_t capacity) { syncThreads(); if (bufferIndex < mIndexBuffers.size() && mIndexBuffers[bufferIndex]) { mEngine->destroy(mIndexBuffers[bufferIndex]); } mIndexBuffers[bufferIndex] = IndexBuffer::Builder() .indexCount(capacity) .bufferType(IndexBuffer::IndexType::USHORT) .build(*mEngine); } void MetaCoreImGuiHelper::createBuffers(int numRequiredBuffers) { if (numRequiredBuffers > mVertexBuffers.size()) { size_t previousSize = mVertexBuffers.size(); mVertexBuffers.resize(numRequiredBuffers, nullptr); for (size_t i = previousSize; i < mVertexBuffers.size(); i++) { createVertexBuffer(i, 1000); } } if (numRequiredBuffers > mIndexBuffers.size()) { size_t previousSize = mIndexBuffers.size(); mIndexBuffers.resize(numRequiredBuffers, nullptr); for (size_t i = previousSize; i < mIndexBuffers.size(); i++) { createIndexBuffer(i, 5000); } } } void MetaCoreImGuiHelper::populateVertexData(size_t bufferIndex, size_t vbSizeInBytes, void* vbImguiData, size_t ibSizeInBytes, void* ibImguiData) { size_t requiredVertCount = vbSizeInBytes / sizeof(ImDrawVert); size_t capacityVertCount = mVertexBuffers[bufferIndex]->getVertexCount(); if (requiredVertCount > capacityVertCount) { createVertexBuffer(bufferIndex, requiredVertCount); } size_t nVbBytes = requiredVertCount * sizeof(ImDrawVert); void* vbFilamentData = malloc(nVbBytes); memcpy(vbFilamentData, vbImguiData, nVbBytes); mVertexBuffers[bufferIndex]->setBufferAt(*mEngine, 0, VertexBuffer::BufferDescriptor(vbFilamentData, nVbBytes, [](void* buffer, size_t size, void* user) { free(buffer); }, /* user = */ nullptr)); size_t requiredIndexCount = ibSizeInBytes / 2; size_t capacityIndexCount = mIndexBuffers[bufferIndex]->getIndexCount(); if (requiredIndexCount > capacityIndexCount) { createIndexBuffer(bufferIndex, requiredIndexCount); } size_t nIbBytes = requiredIndexCount * 2; void* ibFilamentData = malloc(nIbBytes); memcpy(ibFilamentData, ibImguiData, nIbBytes); mIndexBuffers[bufferIndex]->setBuffer(*mEngine, IndexBuffer::BufferDescriptor(ibFilamentData, nIbBytes, [](void* buffer, size_t size, void* user) { free(buffer); }, /* user = */ nullptr)); } void MetaCoreImGuiHelper::syncThreads() { #if UTILS_HAS_THREADING if (!mHasSynced) { Fence::waitAndDestroy(mEngine->createFence()); mHasSynced = true; } #endif } } // namespace MetaCore