From 1b9d34dd38650b2b6b55295cc9693bf800edfafc Mon Sep 17 00:00:00 2001 From: MarsDoge Date: Mon, 8 Jun 2026 10:18:55 +0800 Subject: [PATCH 1/3] test(smoke): CI-gate the LVGL overlay as a supported backend First step of the LVGL backend graduation (Docs/LvglProductizationPlan.md Gate 1): smoke exercised only native/modern overlays, so the LVGL backend was never CI-verified. Add lvgl to the overlay-generation matrix for the targets whose build script accepts it today (ovmf-x64 + loongarch), asserting the generated overlay wires the LVGL renderer library (ModernUiLvglRendererLib), LvglCoreLib, and the shared ModernDisplayEngineDxe interaction backend. The native/modern (GOP) overlays still must never pull in the LVGL renderer -- that prohibition is now scoped to non-lvgl engines so it stays an invariant for the GOP path while permitting lvgl in its own overlay. armvirt and riscvvirt build scripts do not accept lvgl yet, so they remain native/modern only here; adding lvgl support to them is a prerequisite for an all-target lvgl default (tracked in the plan). Co-Authored-By: Claude Opus 4.8 --- Tests/Smoke/smoke_validate.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Tests/Smoke/smoke_validate.py b/Tests/Smoke/smoke_validate.py index 882f601..26cb31f 100755 --- a/Tests/Smoke/smoke_validate.py +++ b/Tests/Smoke/smoke_validate.py @@ -2551,15 +2551,23 @@ def check_overlay_generation(root: Path) -> list[str]: ovmf_x64_fixture(workspace) riscvvirt_fixture(workspace) + # supports_lvgl marks the targets whose build script accepts + # MODERN_SETUP_DISPLAY_ENGINE=lvgl today (ovmf-x64 + loongarch). The LVGL + # backend is the SAME ModernDisplayEngineDxe with the LVGL renderer + # library swapped in, so it is CI-gated as a supported overlay here while + # native/modern overlays still must never pull it in. cases = ( - ("armvirt", "build-armvirt.sh", "ArmVirtQemuModernSetup.dsc"), - ("loongarch", "build-loongarchvirt.sh", "LoongArchVirtQemuModernSetup.dsc"), - ("ovmf-x64", "build-ovmf-x64.sh", "OvmfX64ModernSetup.dsc"), - ("riscvvirt", "build-riscvvirt.sh", "RiscVVirtQemuModernSetup.dsc"), + ("armvirt", "build-armvirt.sh", "ArmVirtQemuModernSetup.dsc", False), + ("loongarch", "build-loongarchvirt.sh", "LoongArchVirtQemuModernSetup.dsc", True), + ("ovmf-x64", "build-ovmf-x64.sh", "OvmfX64ModernSetup.dsc", True), + ("riscvvirt", "build-riscvvirt.sh", "RiscVVirtQemuModernSetup.dsc", False), ) - for platform, script_name, dsc_name in cases: + for platform, script_name, dsc_name, supports_lvgl in cases: script = workspace / "ModernSetupPkg" / "Scripts" / script_name - for engine in ("native", "modern"): + engines = ["native", "modern"] + if supports_lvgl: + engines.append("lvgl") + for engine in engines: overlay_dir = workspace / "Build" / "ModernSetupPkgOverlay" if overlay_dir.exists(): shutil.rmtree(overlay_dir) @@ -2580,9 +2588,10 @@ def check_overlay_generation(root: Path) -> list[str]: if not generated_path.exists(): raise SmokeFailure(f"missing generated overlay file: {generated_path}") assert_not_contains_any(generated_path, PROHIBITED_DEFAULT_OVERLAY_TOKENS) - # The LVGL backend is experimental and lvgl-mode only; it must - # never leak into a default native/modern overlay. - assert_not_contains_any(generated_path, PROHIBITED_DEFAULT_OVERLAY_LVGL_TOKENS) + # The LVGL renderer is allowed only in the lvgl overlay; it + # must never leak into a native/modern (GOP) overlay. + if engine != "lvgl": + assert_not_contains_any(generated_path, PROHIBITED_DEFAULT_OVERLAY_LVGL_TOKENS) dsc = workspace / "Build" / "ModernSetupPkgOverlay" / dsc_name if engine == "modern": @@ -2590,6 +2599,13 @@ def check_overlay_generation(root: Path) -> list[str]: assert_contains(dsc, "gModernSetupPkgTokenSpaceGuid.PcdModernSetupTheme|0x02") if not any("ModernDisplayEngineDxe" in path.read_text(encoding="utf-8") for path in generated): raise SmokeFailure(f"{platform} modern overlay did not reference ModernDisplayEngineDxe") + elif engine == "lvgl": + # Same interaction backend as modern, with the LVGL renderer + # library and LVGL core resolved. + assert_contains(dsc, "ModernUiRendererLib|ModernSetupPkg/Library/ModernUiLvglRendererLib/ModernUiRendererLib.inf") + assert_contains(dsc, "LvglCoreLib|LvglSpikePkg/Library/LvglLib/LvglCoreLib.inf") + if not any("ModernDisplayEngineDxe" in path.read_text(encoding="utf-8") for path in generated): + raise SmokeFailure(f"{platform} lvgl overlay did not reference ModernDisplayEngineDxe") else: for generated_path in generated: assert_not_contains_any( From cc3e199ba690532a7aa7af8e74ce72fe9183c8cf Mon Sep 17 00:00:00 2001 From: MarsDoge Date: Mon, 8 Jun 2026 10:19:47 +0800 Subject: [PATCH 2/3] docs: add LVGL backend productization plan Execution-grade plan to take the LVGL renderer from experimental sample to a shippable DisplayEngine backend across the four XArch targets: six gates (backend graduation, CJK coverage, memory/perf budget, resolution robustness, interaction completeness, validation/CI) with grounded measurements, checklists, effort sizing, a phased roadmap, and the maintainer's locked decisions (lvgl-as-default with its prerequisites; tiered CJK coverage). Companion to Docs/PRODUCTIZATION_STATUS.md. Co-Authored-By: Claude Opus 4.8 --- Docs/LvglProductizationPlan.md | 163 +++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Docs/LvglProductizationPlan.md diff --git a/Docs/LvglProductizationPlan.md b/Docs/LvglProductizationPlan.md new file mode 100644 index 0000000..65e1f9f --- /dev/null +++ b/Docs/LvglProductizationPlan.md @@ -0,0 +1,163 @@ + + +# LVGL Display Backend — Productization Plan + +The detailed, execution-grade plan to take the LVGL renderer +(`ModernUiLvglRendererLib`) from "experimental, great-looking sample" to a +**shippable** modern DisplayEngine backend across the four XArch targets +(OVMF X64, ArmVirtQemu, LoongArchVirtQemu, RiscVVirtQemu). + +This is the depth-companion to `Docs/PRODUCTIZATION_STATUS.md` (the snapshot) and +is scoped to the **engine LVGL line only**. The front-page app content track and +edk2's HII/FormBrowser ownership are out of scope and unchanged. + +Last updated: 2026-06-08. Effort key: **S** ≈ <1 day, **M** ≈ days, **L** ≈ +week+ / external dependency. + +## 0. Current architecture (grounded) + +``` +ModernDisplayEngineDxe (edk2 DisplayEngine fork; owns no HII semantics) + -> ModernUiCustomizedDisplayLib (text-grid -> modern draw bridge) + -> ModernUiEngineLib (row/page/value visual model) + -> ModernUiRendererLib class + -> ModernUiLvglRendererLib (selected by MODERN_SETUP_DISPLAY_ENGINE=lvgl) + -> LvglCoreLib (upstream lvgl + lv_conf, External/lvgl) + -> GOP +``` + +Measured facts that drive this plan: + +| Aspect | Reality | +| --- | --- | +| Shadow canvas | Full-screen **ARGB8888**, allocated once in init and reused; re-init only when the active GOP resolution changes. ~4 MB at 1280×800. | +| LVGL allocator | `LV_USE_STDLIB_MALLOC = LV_STDLIB_CUSTOM` → `LvglUefiPort.c` maps `lv_malloc`→`AllocatePool`. The 64 KB `LV_MEM_SIZE` is **unused**; no fixed heap ceiling. | +| Per-refresh cost | Each value widget is built as a transient `lv_obj`, `lv_snapshot_take(ARGB8888)`, composited, then `lv_draw_buf_destroy` — **alloc/free churn per widget per refresh**. | +| DXEFV (OVMF X64, DEBUG, lvgl) | **43% full** — comfortable. The ~99% case is LoongArch + REPLACE_UIAPP + DriverSample (DEBUG): a config/platform corner, not the base. | +| Resolution | Taken from `Gop->Mode->Info->HorizontalResolution` (the active mode); adaptive, but unvalidated across a mode matrix. | +| Gating | `ModernUiLvglRendererLib.inf` is labelled `experimental/lvgl-spike`; smoke blocks experimental libs from default overlays; selected only by `MODERN_SETUP_DISPLAY_ENGINE=lvgl`. | + +## Gate 1 — Backend graduation (experimental → product-eligible) + +**Goal:** the LVGL renderer is a *supported, validated* backend, buildable in CI +for all targets, without yet forcing it as the default. + +| ✓ | Step | Effort | Note | +| --- | --- | --- | --- | +| [ ] | **Decide the default policy** | S (decision) | Recommendation: keep `modern` (GOP) as the safe default; make `lvgl` a first-class *opt-in* until hardware-proven, then flip per validated platform (Gate 6). | +| [ ] | Re-frame the package boundary | M | Keep `External/lvgl` upstream-pinned; promote `ModernUiLvglRendererLib` from "spike" wording to a supported library, and decide whether `LvglCoreLib`/`LvglSpikePkg` keeps its name or graduates to `LvglPkg`. No code move required to ship — naming + docs. | +| [ ] | Smoke/CI: permit `lvgl` in a *supported* overlay | M | Today smoke asserts the LVGL libs never appear in a default overlay. Add a "supported opt-in" overlay class so `lvgl` builds are CI-gated without being default. Keep the experimental-only guard for `ModernUiHiiBridgeLib`/`PageAdapterLib`. | +| [ ] | Renderer API stability | S | `Include/ModernUi/ModernUiRenderer.h` is the contract for both backends; freeze it per `Docs/API_COMPATIBILITY.md` before declaring supported. | + +**Acceptance:** CI builds `MODERN_SETUP_DISPLAY_ENGINE=lvgl` for all four targets; +smoke passes with the LVGL libs in a supported (non-default) overlay. + +## Gate 2 — CJK / i18n coverage (the first true product gate) + +**Current:** demand-driven Noto Sans CJK SC subset (182 glyphs, **18×18 A8**, +OFL) generated by `Scripts/generate-font-glyphs.py` from the package's own +strings + selected demo `.uni`; anything outside falls back to the firmware HII +font. Single glyph size. + +| ✓ | Step | Effort | Note | +| --- | --- | --- | --- | +| [ ] | **Decide the coverage tier** | S (decision) | Options + memory (18×18 A8 ≈ 324 B/glyph): **(a)** demand-driven per-platform scan — lightest (~tens of KB), needs build-time string knowledge, misses late third-party driver strings; **(b)** standard **GB2312 L1** (~3,755) ≈ **1.2 MB** — predictable arbitrary coverage; **(c)** runtime font from an FFS/font HII package — no embed cost, platform supplies it. Recommendation: **tiered** — keep (a) for our own UI + ship an optional **(b)** behind a PCD (`PcdModernSetupCjkSubset = none\|gb2312l1`) for platforms that need it; HII fallback last. | +| [ ] | **Size matching** | M | The embedded glyph is composited at its native 18 px; the Latin run uses Montserrat 14–24. Decide: generate the CJK subset at the UI's actual sizes (14/16/18/20 → ~4× memory) **or** standardize in-setup body text to one size and verify CJK/Latin baseline alignment. Verify mixed-run vertical alignment in `ModernUiDrawText`. | +| [ ] | zh HII form validation | M | Render a Chinese HII form (zh DriverSample / a platform zh formset) and confirm no missing glyphs at the chosen tier; confirm fallback quality when a glyph is absent (no tofu boxes — define a `?`/placeholder policy). | +| [ ] | Build integration | S | Keep the generated table committed (no build-time Python dep); document regeneration; OFL attribution already in `THIRD_PARTY_NOTICES.md`. | + +**Acceptance:** the target coverage tier renders a Chinese platform form with no +missing glyphs, size-consistent with Latin, within the platform memory budget. + +## Gate 3 — Memory & performance budget + +**Reality check:** base OVMF X64 lvgl DEBUG is 43% DXEFV; the canvas is a +one-time ~4 MB BS-pool allocation freed at ExitBootServices; LVGL uses the UEFI +pool (no 64 KB wall). The real costs are (1) per-refresh **snapshot churn** and +(2) tight FV on heavily-loaded platforms (LoongArch + app + DriverSample). + +| ✓ | Step | Effort | Note | +| --- | --- | --- | --- | +| [ ] | **Snapshot scratch-buffer reuse** | M | Replace per-widget `lv_snapshot_take`/`destroy` with a pre-allocated max-row-size ARGB8888 scratch buffer reused across widgets and refreshes. Cuts allocation churn and fragmentation on sustained navigation. Biggest perf lever. | +| [ ] | **RELEASE size baseline** | S | Measure DXEFV for `TARGET=RELEASE` lvgl on each target (DEBUG is not the ship config; `-Os`+LTO+`MDEPKG_NDEBUG` shrink it materially). Record per-target headroom. | +| [ ] | `lv_conf` trim | S | Disable unused LVGL widgets/features/decoders to shrink `LvglCoreLib` code; the renderer only needs label/rect/dropdown/checkbox/textarea/list/canvas/snapshot. | +| [ ] | Canvas footprint policy | M | For memory-tight platforms, options: keep full ARGB8888 (simplest), or a region-limited canvas. Decide a per-platform DXEFV budget and whether the 4 MB BS-pool canvas is acceptable there. | +| [ ] | Sustained-navigation soak | S | Drive a long key sequence (scroll, open/close popups, edit) and confirm no pool-exhaustion / leak (every `AllocatePool` has a matching `FreePool`). | + +**Acceptance:** RELEASE DXEFV within each platform's budget; no allocation +failures or leaks under sustained navigation. + +## Gate 4 — Resolution & robustness + +**Current:** canvas sizes to the active GOP mode and re-inits on change; guards +skip draws when a region is too small. No mode-matrix validation, no defined +GOP-absent path. + +| ✓ | Step | Effort | Note | +| --- | --- | --- | --- | +| [ ] | Resolution matrix | M | Validate 1024×768, 1280×800, 1920×1080, and a small mode (e.g. 800×600). Confirm chrome/rows/right-rail/popups/watermark scale and the size guards behave. | +| [ ] | Re-init correctness on mode change | S | Verify the `mCanvasW != Context->Width` re-init path frees+reallocates cleanly and the first post-change frame is correct. | +| [ ] | GOP-absent / degenerate fallback | M | Define behavior when GOP is unavailable or the mode is below the minimum usable size: the engine should degrade gracefully (skip modern draw, let native text render) rather than blank the screen. | + +**Acceptance:** correct rendering across the matrix; graceful degradation with no +GOP or an unusably small mode. + +## Gate 5 — Interaction completeness & polish + +| ✓ | Item | Effort | Status | +| --- | --- | --- | --- | +| [x] | Value opcodes → real widgets (one-of/checkbox/numeric/string/password/ordered-list/date-time) | — | Done (PR #40). | +| [x] | Confirm/error/selectable popups: full text, modern panel, no box-draw seam | — | Done. | +| [x] | Clean selection styling + localized chrome | — | Done. | +| [ ] | Date/time + ordered-list **editing** affordances (display is done; editing is native) | M | Confirm the native edit paths render acceptably; optionally elevate. | +| [ ] | Multi-line help / long-form text rendering audit | S | Confirm wrap/scroll in help and long values. | +| [ ] | Optional dialog elevation (accent title / Y-N affordances) | S | Cosmetic; defer. | + +**Acceptance:** no interaction path falls back to a raw text-grid seam. + +## Gate 6 — Validation & CI + +| ✓ | Step | Effort | Note | +| --- | --- | --- | --- | +| [ ] | CI: lvgl build all four targets | M | Extend the workflow beyond smoke to compile lvgl overlays per target. | +| [ ] | Screendump regression | M | Golden screendumps for key forms (DriverSample, a popup, zh chrome) compared per change on OVMF X64 (the one target with a capture helper). | +| [ ] | **Hardware bring-up** | L | At least one real platform per arch — the dominant external dependency and the gate between "validated sample" and "shipped". | + +## Sequenced roadmap + +| Phase | Contents | Rough size | +| --- | --- | --- | +| **A** (now) | Gate 1 decisions + smoke/CI opt-in class; Gate 3 snapshot reuse + RELEASE baseline + lv_conf trim | days | +| **B** | Gate 2 CJK tier + size matching + zh validation | week+ | +| **C** | Gate 4 resolution matrix + GOP-absent fallback; Gate 5 remaining audits | week+ | +| **D** | Gate 6 CI extension + screendump regression | week+ | +| **E** | Gate 6 hardware bring-up, then flip default per validated platform | external | + +## Decisions (locked 2026-06-08) + +1. **Default backend → `lvgl`.** The maintainer chose to make LVGL the default + backend (overriding the conservative "GOP-default until hardware-proven" + recommendation). This raises real prerequisites before it is safe to flip the + bare default on every target: (a) `build-armvirt.sh` / `build-riscvvirt.sh` + accept only `{modern, native}` today — they need an lvgl branch; (b) the + default build would require `External/lvgl`, so `Scripts/bootstrap-edk2.sh` + must init that submodule; (c) the hardware gate (Gate 6) still stands. So the + default flip is sequenced *after* all-target lvgl support + CI. +2. **CJK coverage → tiered.** Demand-driven Noto subset for the package's own UI + (done) + an optional **GB2312 L1** subset behind a PCD for platforms needing + arbitrary coverage + HII fallback last. +3. **Per-platform DXEFV budget** — open; measure RELEASE baselines (Gate 3) then + decide whether the ~4 MB BS-pool canvas is acceptable on the tightest target. +4. **Hardware availability** — open; which real boards exist per arch (Gate 6). + +## Progress log + +- 2026-06-08: Gate 1 started — smoke now CI-gates the lvgl overlay on ovmf-x64 + + loongarch (the targets that accept `lvgl` today). Remaining Gate 1: add lvgl + support to armvirt + riscvvirt, de-spike framing, then flip the default with + the bootstrap submodule init. From 6f46e2cef78557daa311fd68f4b3e1570b333562 Mon Sep 17 00:00:00 2001 From: MarsDoge Date: Mon, 8 Jun 2026 10:37:29 +0800 Subject: [PATCH 3/3] feat(build): add LVGL display-engine mode to armvirt + riscvvirt Bring the remaining two targets up to the ovmf-x64/loongarch pattern so all four XArch targets can build MODERN_SETUP_DISPLAY_ENGINE=lvgl -- the prerequisite for making lvgl the default backend (Docs/LvglProductizationPlan.md Gate 1). Per script: accept "lvgl"; resolve the renderer LibraryClass to the LVGL-backed ModernUiLvglRendererLib (vs the GOP rasterizer) via a renderer_inf switch; add the LvglCoreLib library block and force-link CryptoPkg IntrinsicLib into ModernDisplayEngineDxe in lvgl mode (memcpy/memset pulled by the LVGL software draw pipeline); expand the modern-only DSC/FDF rewrites to also fire for lvgl; and append Experimental/ to PACKAGES_PATH so LvglSpikePkg resolves. Smoke now exercises lvgl overlay generation for all four targets. Overlay generation verified for all four targets via smoke. Full AARCH64/RISCV64 lvgl cross-compilation is not exercised in this environment, but RISC-V64 LVGL already builds upstream (Scripts/build-lvgl-spike-riscv.sh) and the LVGL sources are architecture-agnostic C. The lvgl+REPLACE_UIAPP app-intrinsics combo on these two targets is a follow-up (not on the default or smoke path). Co-Authored-By: Claude Opus 4.8 --- Scripts/build-armvirt.sh | 48 +++++++++++++++++++++++++--------- Scripts/build-riscvvirt.sh | 49 +++++++++++++++++++++++++++-------- Tests/Smoke/smoke_validate.py | 4 +-- 3 files changed, 76 insertions(+), 25 deletions(-) diff --git a/Scripts/build-armvirt.sh b/Scripts/build-armvirt.sh index 4cdfad0..300185b 100755 --- a/Scripts/build-armvirt.sh +++ b/Scripts/build-armvirt.sh @@ -24,6 +24,9 @@ export PATH="/opt/homebrew/bin:/opt/homebrew/opt/llvm/bin:/opt/homebrew/opt/lld/ export CLANGDWARF_BIN="${CLANGDWARF_BIN:-/opt/homebrew/opt/llvm/bin/}" export WORKSPACE ConfigureModernSetupPackagePath +# Experimental/ hosts LvglSpikePkg, consumed only by MODERN_SETUP_DISPLAY_ENGINE=lvgl. +AppendPackagePath "${PKG_DIR}/Experimental" +export PACKAGES_PATH if [[ ! -d "${WORKSPACE}/MdePkg" || ! -d "${WORKSPACE}/ArmVirtPkg" ]]; then echo "WORKSPACE does not look like an edk2 checkout: ${WORKSPACE}" >&2 @@ -58,9 +61,9 @@ if theme_pcd is None: f"Unsupported MODERN_SETUP_THEME={theme_name!r}; " "use orange, amber, dark-orange, red, accent-red, dark-red, graphite, or graphite-gold" ) -if display_engine not in {"modern", "native"}: +if display_engine not in {"modern", "native", "lvgl"}: raise SystemExit( - f"Unsupported MODERN_SETUP_DISPLAY_ENGINE={display_engine!r}; use modern or native" + f"Unsupported MODERN_SETUP_DISPLAY_ENGINE={display_engine!r}; use modern, native, or lvgl" ) if replace_uiapp_flag not in {"0", "1", "false", "true", "no", "yes"}: raise SystemExit( @@ -74,14 +77,33 @@ modern_setup_app_component_boot_manager_fallback = """ ModernSetupPkg/Applicati }""" modern_setup_app_uiapp_fdf_inf = " INF RuleOverride = MODERN_SETUP_UIAPP ModernSetupPkg/Application/ModernSetupApp/ModernSetupApp.inf" modern_display_component = " ModernSetupPkg/Universal/ModernDisplayEngineDxe/ModernDisplayEngineDxe.inf" +# In lvgl mode the ModernDisplayEngine force-links compiler intrinsics +# (memcpy/memset) pulled by the LVGL software draw pipeline. +modern_display_component_lvgl = ( + " ModernSetupPkg/Universal/ModernDisplayEngineDxe/ModernDisplayEngineDxe.inf {\n" + " \n" + " NULL|CryptoPkg/Library/IntrinsicLib/IntrinsicLib.inf\n" + " }" +) modern_display_fdf_inf = " INF ModernSetupPkg/Universal/ModernDisplayEngineDxe/ModernDisplayEngineDxe.inf" driver_sample_component = " MdeModulePkg/Universal/DriverSampleDxe/DriverSampleDxe.inf" driver_sample_fdf_inf = " INF MdeModulePkg/Universal/DriverSampleDxe/DriverSampleDxe.inf" -library_block = """ ModernUiEngineLib|ModernSetupPkg/Library/ModernUiEngineLib/ModernUiEngineLib.inf - ModernUiRendererLib|ModernSetupPkg/Library/ModernUiRendererLib/ModernUiRendererLib.inf - ModernUiThemeLib|ModernSetupPkg/Library/ModernUiThemeLib/ModernUiThemeLib.inf - ModernUiStringLib|ModernSetupPkg/Library/ModernUiStringLib/ModernUiStringLib.inf -""" +# LVGL core (upstream lvgl sources as a BASE library); resolved only in lvgl mode +# and consumed transitively through the LVGL renderer library. +lvgl_library_block = " LvglCoreLib|LvglSpikePkg/Library/LvglLib/LvglCoreLib.inf\n" +# The renderer library class resolves to the LVGL-backed implementation in lvgl +# mode and to the hand-rolled GOP rasterizer otherwise (identical API). +renderer_inf = ( + "ModernSetupPkg/Library/ModernUiLvglRendererLib/ModernUiRendererLib.inf" + if display_engine == "lvgl" + else "ModernSetupPkg/Library/ModernUiRendererLib/ModernUiRendererLib.inf" +) +library_block = ( + " ModernUiEngineLib|ModernSetupPkg/Library/ModernUiEngineLib/ModernUiEngineLib.inf\n" + f" ModernUiRendererLib|{renderer_inf}\n" + " ModernUiThemeLib|ModernSetupPkg/Library/ModernUiThemeLib/ModernUiThemeLib.inf\n" + " ModernUiStringLib|ModernSetupPkg/Library/ModernUiStringLib/ModernUiStringLib.inf\n" +) app_library_block = """ ModernUiPlatformDataLib|ModernSetupPkg/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.inf ModernUiBootDataLib|ModernSetupPkg/Library/ModernUiBootDataLib/ModernUiBootDataLib.inf ModernUiDeviceDataLib|ModernSetupPkg/Library/ModernUiDeviceDataLib/ModernUiDeviceDataLib.inf @@ -118,8 +140,10 @@ dsc = dsc.replace( " FLASH_DEFINITION = Build/ModernSetupPkgOverlay/ArmVirtQemuModernSetup.fdf", ) if "ModernUiEngineLib|ModernSetupPkg" not in dsc: - if display_engine == "modern" or replace_uiapp: + if (display_engine == "modern" or display_engine == "lvgl") or replace_uiapp: dsc = dsc.replace("[LibraryClasses.common]\n", "[LibraryClasses.common]\n" + library_block, 1) +if display_engine == "lvgl" and "LvglCoreLib|LvglSpikePkg" not in dsc: + dsc = dsc.replace("[LibraryClasses.common]\n", "[LibraryClasses.common]\n" + lvgl_library_block, 1) if replace_uiapp and "ModernUiPlatformDataLib|ModernSetupPkg" not in dsc: dsc = dsc.replace("[LibraryClasses.common]\n", "[LibraryClasses.common]\n" + app_library_block, 1) if replace_uiapp: @@ -129,7 +153,7 @@ if replace_uiapp: modern_setup_app_component_boot_manager_fallback + "\n", "UiApp DSC component", ) -if display_engine == "modern": +if (display_engine == "modern" or display_engine == "lvgl"): dsc = dsc.replace( " CustomizedDisplayLib|MdeModulePkg/Library/CustomizedDisplayLib/CustomizedDisplayLib.inf", " CustomizedDisplayLib|ModernSetupPkg/Library/ModernUiCustomizedDisplayLib/ModernUiCustomizedDisplayLib.inf", @@ -137,7 +161,7 @@ if display_engine == "modern": ) dsc = dsc.replace( " MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe.inf", - modern_display_component, + modern_display_component_lvgl if display_engine == "lvgl" else modern_display_component, 1, ) if enable_driver_sample and driver_sample_component not in dsc: @@ -153,7 +177,7 @@ if enable_driver_sample and driver_sample_component not in dsc: driver_sample_component + "\n MdeModulePkg/Application/UiApp/UiApp.inf {", 1, ) -if display_engine == "modern": +if (display_engine == "modern" or display_engine == "lvgl"): dsc += ( "\n[PcdsFixedAtBuild]\n" f" gModernSetupPkgTokenSpaceGuid.PcdModernSetupTheme|{theme_pcd}\n" @@ -171,7 +195,7 @@ fdf = fdf.replace( fv = (workspace / "ArmVirtPkg/ArmVirtQemuFvMain.fdf.inc").read_text() fv = fv.replace("!include ArmVirtRules.fdf.inc", "!include ArmVirtPkg/ArmVirtRules.fdf.inc") -if display_engine == "modern": +if (display_engine == "modern" or display_engine == "lvgl"): fv = fv.replace( " INF MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe.inf", modern_display_fdf_inf, diff --git a/Scripts/build-riscvvirt.sh b/Scripts/build-riscvvirt.sh index 53afd1f..fc51c30 100755 --- a/Scripts/build-riscvvirt.sh +++ b/Scripts/build-riscvvirt.sh @@ -24,6 +24,9 @@ OVERLAY_DIR="${WORKSPACE}/Build/ModernSetupPkgOverlay" export WORKSPACE export GCC_RISCV64_PREFIX ConfigureModernSetupPackagePath +# Experimental/ hosts LvglSpikePkg, consumed only by MODERN_SETUP_DISPLAY_ENGINE=lvgl. +AppendPackagePath "${PKG_DIR}/Experimental" +export PACKAGES_PATH if [[ ! -d "${WORKSPACE}/MdePkg" || ! -d "${WORKSPACE}/OvmfPkg/RiscVVirt" ]]; then echo "WORKSPACE does not look like an edk2 checkout with MdePkg and OvmfPkg/RiscVVirt: ${WORKSPACE}" >&2 @@ -63,9 +66,9 @@ if theme_pcd is None: f"Unsupported MODERN_SETUP_THEME={theme_name!r}; " "use orange, amber, dark-orange, red, accent-red, dark-red, graphite, or graphite-gold" ) -if display_engine not in {"modern", "native"}: +if display_engine not in {"modern", "native", "lvgl"}: raise SystemExit( - f"Unsupported MODERN_SETUP_DISPLAY_ENGINE={display_engine!r}; use modern or native" + f"Unsupported MODERN_SETUP_DISPLAY_ENGINE={display_engine!r}; use modern, native, or lvgl" ) if replace_uiapp_flag not in {"0", "1", "false", "true", "no", "yes"}: raise SystemExit( @@ -79,14 +82,33 @@ modern_setup_app_component_boot_manager_fallback = """ ModernSetupPkg/Applicati }""" modern_setup_app_uiapp_fdf_inf = "INF RuleOverride = MODERN_SETUP_UIAPP ModernSetupPkg/Application/ModernSetupApp/ModernSetupApp.inf" modern_display_component = " ModernSetupPkg/Universal/ModernDisplayEngineDxe/ModernDisplayEngineDxe.inf" +# In lvgl mode the ModernDisplayEngine force-links compiler intrinsics +# (memcpy/memset) pulled by the LVGL software draw pipeline. +modern_display_component_lvgl = ( + " ModernSetupPkg/Universal/ModernDisplayEngineDxe/ModernDisplayEngineDxe.inf {\n" + " \n" + " NULL|CryptoPkg/Library/IntrinsicLib/IntrinsicLib.inf\n" + " }" +) modern_display_fdf_inf = "INF ModernSetupPkg/Universal/ModernDisplayEngineDxe/ModernDisplayEngineDxe.inf" boot_manager_menu_component = " MdeModulePkg/Application/BootManagerMenuApp/BootManagerMenuApp.inf" boot_manager_menu_fdf_inf = "INF MdeModulePkg/Application/BootManagerMenuApp/BootManagerMenuApp.inf" -library_block = """ ModernUiEngineLib|ModernSetupPkg/Library/ModernUiEngineLib/ModernUiEngineLib.inf - ModernUiRendererLib|ModernSetupPkg/Library/ModernUiRendererLib/ModernUiRendererLib.inf - ModernUiThemeLib|ModernSetupPkg/Library/ModernUiThemeLib/ModernUiThemeLib.inf - ModernUiStringLib|ModernSetupPkg/Library/ModernUiStringLib/ModernUiStringLib.inf -""" +# LVGL core (upstream lvgl sources as a BASE library); resolved only in lvgl mode +# and consumed transitively through the LVGL renderer library. +lvgl_library_block = " LvglCoreLib|LvglSpikePkg/Library/LvglLib/LvglCoreLib.inf\n" +# The renderer library class resolves to the LVGL-backed implementation in lvgl +# mode and to the hand-rolled GOP rasterizer otherwise (identical API). +renderer_inf = ( + "ModernSetupPkg/Library/ModernUiLvglRendererLib/ModernUiRendererLib.inf" + if display_engine == "lvgl" + else "ModernSetupPkg/Library/ModernUiRendererLib/ModernUiRendererLib.inf" +) +library_block = ( + " ModernUiEngineLib|ModernSetupPkg/Library/ModernUiEngineLib/ModernUiEngineLib.inf\n" + f" ModernUiRendererLib|{renderer_inf}\n" + " ModernUiThemeLib|ModernSetupPkg/Library/ModernUiThemeLib/ModernUiThemeLib.inf\n" + " ModernUiStringLib|ModernSetupPkg/Library/ModernUiStringLib/ModernUiStringLib.inf\n" +) app_library_block = """ ModernUiPlatformDataLib|ModernSetupPkg/Library/ModernUiPlatformDataLib/ModernUiPlatformDataLib.inf ModernUiBootDataLib|ModernSetupPkg/Library/ModernUiBootDataLib/ModernUiBootDataLib.inf ModernUiDeviceDataLib|ModernSetupPkg/Library/ModernUiDeviceDataLib/ModernUiDeviceDataLib.inf @@ -129,7 +151,7 @@ dsc = replace_regex_once( r"\1Build/ModernSetupPkgOverlay/RiscVVirtQemuModernSetup.fdf", "FLASH_DEFINITION", ) -if display_engine == "modern" or replace_uiapp: +if (display_engine == "modern" or display_engine == "lvgl") or replace_uiapp: if "ModernUiEngineLib|ModernSetupPkg" not in dsc: if "[LibraryClasses.common]\n" in dsc: dsc = replace_once(dsc, "[LibraryClasses.common]\n", "[LibraryClasses.common]\n" + library_block, "LibraryClasses.common") @@ -137,6 +159,11 @@ if display_engine == "modern" or replace_uiapp: dsc = replace_once(dsc, "[LibraryClasses]\n", "[LibraryClasses]\n" + library_block, "LibraryClasses") else: raise SystemExit("Expected LibraryClasses anchor: [LibraryClasses.common] or [LibraryClasses]") +if display_engine == "lvgl" and "LvglCoreLib|LvglSpikePkg" not in dsc: + if "[LibraryClasses.common]\n" in dsc: + dsc = replace_once(dsc, "[LibraryClasses.common]\n", "[LibraryClasses.common]\n" + lvgl_library_block, "LibraryClasses.common for lvgl") + elif "[LibraryClasses]\n" in dsc: + dsc = replace_once(dsc, "[LibraryClasses]\n", "[LibraryClasses]\n" + lvgl_library_block, "LibraryClasses for lvgl") if replace_uiapp and "ModernUiPlatformDataLib|ModernSetupPkg" not in dsc: if "[LibraryClasses.common]\n" in dsc: dsc = replace_once(dsc, "[LibraryClasses.common]\n", "[LibraryClasses.common]\n" + app_library_block, "LibraryClasses.common for app") @@ -158,7 +185,7 @@ if replace_uiapp: boot_manager_menu_component + r"\n\1", "QemuKernelLoaderFsDxe component", ) -if display_engine == "modern": +if (display_engine == "modern" or display_engine == "lvgl"): dsc = replace_regex_once( dsc, r"^\s*CustomizedDisplayLib\s*\|\s*MdeModulePkg/Library/CustomizedDisplayLib/CustomizedDisplayLib\.inf\s*$", @@ -168,7 +195,7 @@ if display_engine == "modern": dsc = replace_regex_once( dsc, r"^\s*MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe\.inf\s*$", - modern_display_component, + modern_display_component_lvgl if display_engine == "lvgl" else modern_display_component, "DisplayEngineDxe component", ) dsc += ( @@ -180,7 +207,7 @@ if display_engine == "modern": fdf = (workspace / "OvmfPkg/RiscVVirt/RiscVVirtQemu.fdf").read_text() fdf = replace_once(fdf, "!include RiscVVirt.fdf.inc", "!include OvmfPkg/RiscVVirt/RiscVVirt.fdf.inc", "RiscVVirt.fdf.inc include") fdf = replace_once(fdf, "!include VarStore.fdf.inc", "!include OvmfPkg/RiscVVirt/VarStore.fdf.inc", "VarStore.fdf.inc include") -if display_engine == "modern": +if (display_engine == "modern" or display_engine == "lvgl"): fdf = replace_regex_once( fdf, r"^\s*INF\s+MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe\.inf\s*$", diff --git a/Tests/Smoke/smoke_validate.py b/Tests/Smoke/smoke_validate.py index 26cb31f..577f696 100755 --- a/Tests/Smoke/smoke_validate.py +++ b/Tests/Smoke/smoke_validate.py @@ -2557,10 +2557,10 @@ def check_overlay_generation(root: Path) -> list[str]: # library swapped in, so it is CI-gated as a supported overlay here while # native/modern overlays still must never pull it in. cases = ( - ("armvirt", "build-armvirt.sh", "ArmVirtQemuModernSetup.dsc", False), + ("armvirt", "build-armvirt.sh", "ArmVirtQemuModernSetup.dsc", True), ("loongarch", "build-loongarchvirt.sh", "LoongArchVirtQemuModernSetup.dsc", True), ("ovmf-x64", "build-ovmf-x64.sh", "OvmfX64ModernSetup.dsc", True), - ("riscvvirt", "build-riscvvirt.sh", "RiscVVirtQemuModernSetup.dsc", False), + ("riscvvirt", "build-riscvvirt.sh", "RiscVVirtQemuModernSetup.dsc", True), ) for platform, script_name, dsc_name, supports_lvgl in cases: script = workspace / "ModernSetupPkg" / "Scripts" / script_name