From e23b5042013a1f7219d2950bf913ac981627bc61 Mon Sep 17 00:00:00 2001 From: Matija Kecman Date: Sun, 26 Jul 2026 13:34:18 -0700 Subject: [PATCH] Change how ImGui clip rect scaling and scissor computation works in imgui_bridge to fix flickering and clipping errors in the web viewer Avoid mutating ImGui draw lists in place by scaling clip rects locally during scissor computation instead of calling ScaleClipRects. This prevents clip rects from growing exponentially on persistent draw lists, such as streamed remote UI. Additionally, clamp the scaled clip rects to the viewport boundaries to prevent Filament's scissor test from rejecting out-of-bounds rects, while avoiding resetting to full-window scissors which would disable clipping. The draw geometry is now immutable in the Update function (ImGui texture state is still mutated). PiperOrigin-RevId: 954277141 Change-Id: Ifdf6866bb4eabaca44b020ea40acc246428b4f0b --- src/experimental/platform/ux/imgui_bridge.cc | 39 ++++++++++++-------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/experimental/platform/ux/imgui_bridge.cc b/src/experimental/platform/ux/imgui_bridge.cc index 1dac5060..9baa1db9 100644 --- a/src/experimental/platform/ux/imgui_bridge.cc +++ b/src/experimental/platform/ux/imgui_bridge.cc @@ -14,6 +14,7 @@ #include "experimental/platform/ux/imgui_bridge.h" +#include #include #include #include @@ -178,7 +179,6 @@ void ImguiBridge::Update() { PrepareRenderables(0); return; } - commands->ScaleClipRects(scale); // 2 floats for position, 2 floats for uv, 4 bytes for color. constexpr size_t kExpectedVertexSize = @@ -260,19 +260,27 @@ void ImguiBridge::Update() { material.color_texture = GetTexture(command.GetTexID()); material.decor_ux = true; - material.scissor[0] = command.ClipRect.x; - material.scissor[1] = height - command.ClipRect.w; - material.scissor[2] = command.ClipRect.z - command.ClipRect.x; - material.scissor[3] = command.ClipRect.w - command.ClipRect.y; - // Modal dialogs try to cover the whole window, but also a little outside - // of it. This doesn't work well with filament's scissor test, so we clip - // them to the window. - if (material.scissor[0] < 0 || material.scissor[1] < 0) { - material.scissor[0] = 0; - material.scissor[1] = 0; - material.scissor[2] = width; - material.scissor[3] = height; - } + // Scale clip rects to physical pixels here instead of using + // ScaleClipRects, which previously caused flickering and clipping bugs: + // it mutates draw data in place, so draw lists that are re-rendered + // across frames get scaled repeatedly (this happens in the web viewer, + // where the same draw lists are rendered until a new network frame + // arrives). We also clamp to the viewport because clip rects may extend + // slightly outside it (modal dialogs by design; fractional DPI rounding + // for bottom or right-docked windows), and filament rejects out-of-window + // scissor rects. + const float clip_x0 = std::clamp(command.ClipRect.x * scale.x, 0.0f, + static_cast(width)); + const float clip_y0 = std::clamp(command.ClipRect.y * scale.y, 0.0f, + static_cast(height)); + const float clip_x1 = std::clamp(command.ClipRect.z * scale.x, clip_x0, + static_cast(width)); + const float clip_y1 = std::clamp(command.ClipRect.w * scale.y, clip_y0, + static_cast(height)); + material.scissor[0] = clip_x0; + material.scissor[1] = height - clip_y1; + material.scissor[2] = clip_x1 - clip_x0; + material.scissor[3] = clip_y1 - clip_y0; mjrf_setRenderableMaterial(renderable.get(), &material); const float size[] = {scale.x, scale.y, 1.0f}; @@ -291,7 +299,8 @@ void ImguiBridge::PrepareRenderables(int count) { params.cast_shadows = false; params.receive_shadows = false; params.blend_order = static_cast(renderables_.size() + 1); - auto& renderable = renderables_.emplace_back(CreateRenderable(ctx_, params)); + auto& renderable = + renderables_.emplace_back(CreateRenderable(ctx_, params)); mjrf_addRenderableToScene(scene_, renderable.get()); } while (renderables_.size() > count) {