Commit graph

2,973 commits

Author SHA1 Message Date
Kevin Boos
fec9bb6f2e Introduce knowledge of device screen bounds/"safe inset areas" (#990)
* Introduce knowledge of device screen bounds/cutous/"safe inset areas"

Tested working on iOS, implemented for Android but not yet tested.

The approach may need to be improved, because it currently restricts
the whole app window to being fully within the safe areas.
We may not necessarily want that, or if we do, then we probably also
need to support setting the base color of the reserved system areas
(beneath the app bounds and above in the notification bar area).

* Use metal scissor rect to prevent SVGs/icons from being mis-drawn in safe areas

This prevents anything from being accidentally drawn in the safe
inset areas when the pass clear_color is transparent. Of course,
we can still draw the pass clear_color in those areas.

* Workaround: apply a scissor rect within safe inset area

Only apply it to clip any DrawSvg/DrawVector-specific draw calls
within the safe inset area.

This is unfortunately still just a hacky solution, because if we
actually do want to draw svg/vectors within that safe inset area,
then we won't be able to.

* Properly fix gpu artifacts when rendering SVGs

The `DrawSvg` vertex shader had a GPU fringe expansion pass designed for `fill_gpu()` mode, where fringe vertices encode per-vertex normals in the `v` and `stroke_dist` fields. SVG rendering used `fill_gpu()`, which produces **coincident-vertex fringe triangles** (body and outer fringe at the same CPU position, expanded on the GPU). These zero-area triangles caused **Metal GPU rasterization artifacts** — stray fragments appearing at unexpected screen positions.

**`draw/src/svg/render.rs`** — Switch SVG fill from `fill_gpu()` to `fill()`. Pre-computed fringe produces vertices at physically different positions (no coincident vertices).

**`draw/src/shader/draw_svg.rs`** — Remove the GPU fringe expansion code from the vertex shader. With pre-computed fringe, the `v` and `stroke_dist` fields are constants (`1.0` and `0.0`), not per-vertex normals. The expansion code was misinterpreting `v=1.0` as a horizontal normal, corrupting vertex positions.

**`libs/apple_sys/src/lib.rs`** — Added `MTLScissorRect` struct (unused now but available for future use).

**`src/home/rooms_sidebar.rs`** — Changed shadow offset from `vec2(1.0, 0.0)` to `vec2(0.0, 10.0)` so the `RoundedShadowView` shadow only draws below the header, eliminating the gray line at the top of the screen (issue 1).

* Expose safe area inset padding to app, don't forcibly apply it to root window

* Fixed safe area insets padding, with support for rotation

We now make these values available to the app dev (see below)
instead of forcibly inserting them as padding on all root windows.

This will allow each app to choose how and when they want to apply said pad values
(or if they want to at all) in an easy way, both at the Splash level
or more dynamically/programmatically at the Rust level.

Required quite a few changes to how things work in the iOS platform plumbing,
also described below in the generated summary:

On iOS and Android, Makepad apps render content behind device cutouts (Dynamic Island, camera notch), home indicators, and rounded screen corners because the framework has no awareness of safe area insets.

Added platform-level safe area inset querying on iOS and Android, exposed the values through both the Splash DSL (`mod.widgets.SAFE_INSET_PAD_*`) and Rust (`cx.display_context.safe_area_insets`), and ensured they update correctly on device rotation.

**New types:**
- `UIEdgeInsets` struct in `libs/apple_sys` for Objective-C interop
- `SafeAreaInsets` struct in `platform/src/event/window.rs` (top/right/bottom/left in logical points)
- Added `safe_area_insets` field to `WindowGeom` and `DisplayContext`

**iOS (`platform/src/os/apple/ios/`):**
- Query `[UIView safeAreaInsets]` from the MTKView in `check_window_geom()`
- Added `viewSafeAreaInsetsDidChange` callback on `MakepadViewController` to detect inset changes on rotation
- Populate `display_context` before `Event::Startup` so values are available during app script initialization
- Fixed MTKView setup: removed redundant `addSubview:` (conflicted with `setRootViewController:`) and added autoresizing mask — both required for safe area propagation on rotation

**Android (`platform/src/os/linux/android/`):**
- Added `SafeAreaInsets` variant to `FromJavaMessage` and corresponding JNI function
- Java side (`ResizingLayout.onApplyWindowInsets`): queries `WindowInsets.Type.systemBars() | displayCutout()` and sends insets to Rust (converted from px to dp)
- Added `safe_area_insets` field to `CxOs`, populated on `SafeAreaInsets` message and included in `WindowGeom` construction
- Added `surfaceOnSafeAreaInsets` native method to `MakepadNative.java`

**Splash DSL variables (`widgets/src/lib.rs`):**
- `mod.widgets.SAFE_INSET_PAD_TOP`
- `mod.widgets.SAFE_INSET_PAD_BOTTOM`
- `mod.widgets.SAFE_INSET_PAD_LEFT`
- `mod.widgets.SAFE_INSET_PAD_RIGHT`
- Values read from `display_context` at widget module initialization (during `Event::Startup`)
- Updated on the script heap via `Cx::update_safe_inset_script_values()` on `WindowGeomChange`

**Rotation support:**
- Added `pending_script_reapply` flag on `Cx` — set when safe area insets change, checked at the end of the platform event loop iteration
- Fires a deferred `LiveEdit` event to re-evaluate and re-apply all Splash widget definitions with updated inset values
- Implemented in both iOS and Android event loops

**StackNavigationView fix (`widgets/src/stack_navigation.rs`):**
- Full-screen stack views now position at `max(safe_area_insets.top, parent_rect.pos.y)` instead of hardcoded `y: 0`, respecting both mobile safe areas and desktop title bars

**All other platforms:**
- Added `..Default::default()` to all `WindowGeom` constructors (macOS, Windows, Linux X11/Wayland/Direct, web, tvOS, OpenHarmony) so the new `safe_area_insets` field defaults to zeros
2026-03-31 08:57:36 +02:00
Admin
307de4153c cleanup 2026-03-30 09:54:53 +02:00
Admin
808229c1c1 cleanup 2026-03-30 09:52:31 +02:00
Admin
0d507b2d81 cleanup 2026-03-30 09:22:56 +02:00
Admin
fae9971b79 lz4 opt 2026-03-30 01:06:33 +02:00
Admin
b3e81d466a missing 2026-03-30 00:55:55 +02:00
Admin
b2a36e1dd6 lz4 wire protocol 2026-03-30 00:55:33 +02:00
Admin
bccb69c04c cleanup 2026-03-29 23:33:47 +02:00
Admin
bf5882855d cleanup 2026-03-29 23:21:47 +02:00
Admin
85a485b463 cleanup 2026-03-29 22:57:11 +02:00
Admin
d077e864ce cleanup 2026-03-29 22:53:22 +02:00
Admin
7ac28493cd cleanup 2026-03-29 22:47:40 +02:00
Admin
ff07672927 optimize 2026-03-29 22:12:33 +02:00
Admin
00ef275b0c optimisations 2026-03-29 21:46:25 +02:00
Admin
32091a2a14 optimisations 2026-03-29 21:36:19 +02:00
Admin
618bcf11e7 optimisations 2026-03-29 21:01:06 +02:00
Admin
12bd874eda optimisations 2026-03-29 20:27:52 +02:00
Admin
d61b52d6df optimisations 2026-03-29 18:46:50 +02:00
Admin
49560cad25 optimisations 2026-03-29 18:42:48 +02:00
Admin
a37d4fe7b4 optimisations 2026-03-29 18:32:46 +02:00
Admin
de370867f2 fixing tracing 2026-03-29 18:12:16 +02:00
Admin
daed7da284 fixing tracing 2026-03-29 18:06:14 +02:00
Admin
b1cf71b573 deptmap tweaks 2026-03-29 15:47:27 +02:00
Admin
2812ad9d00 depth align trying 2026-03-29 11:10:52 +02:00
Admin
5ee9525d20 clean maps lock 2026-03-28 22:56:30 +01:00
Admin
bc945face2 refactor 2026-03-28 21:45:46 +01:00
Admin
4a51377484 fix refactor 2026-03-28 21:45:27 +01:00
Admin
5197904900 optimising 2026-03-28 20:11:51 +01:00
Admin
328424b710 optimising 2026-03-28 20:03:37 +01:00
Admin
aff2110422 optimising 2026-03-28 19:56:09 +01:00
Admin
42da283dbe working refactor 2026-03-28 18:46:43 +01:00
Admin
c9a7c15eeb working refactor 2026-03-28 17:58:49 +01:00
Admin
0c1465a9e2 actually working alignment 2026-03-28 16:36:27 +01:00
Admin
8dac4bcaad cleanup 2026-03-28 13:22:52 +01:00
Admin
2f33a8300c contour map 2026-03-28 12:03:05 +01:00
Admin
3e047d1567 contour map 2026-03-28 12:02:56 +01:00
Admin
b46501b70a auto alignment working 2026-03-27 16:31:02 +01:00
Admin
cf46675a32 xr room mapping 2026-03-27 13:51:54 +01:00
Admin
f23b2f9494 fix xr UIs 2026-03-26 15:25:55 +01:00
Admin
3d16b00083 wrist ui 2026-03-26 15:25:55 +01:00
wyenox
28a90f211d fix openxr compile error on non-vulkan android builds (#989) 2026-03-26 08:25:25 +01:00
Kevin Boos
ee4f3b0069 Additional opptimizations for windows shader compilation (#988)
* Additional opptimizations for windows shader compilation

`hlsl_compile_shaders` was called unconditionally after every draw event, even on
frames where no new shaders needed compilation. Added an early return:

```rust
if self.draw_shaders.compile_set.is_empty() {
    return;
}
```

The loop previously collected `compile_set` into a temporary `Vec` before
iterating, in order to release the borrow on `compile_set` so the loop body
could mutate other `draw_shaders` fields. This caused a heap allocation and a
full copy of all indices on every compilation batch.

`std::mem::take` atomically replaces `compile_set` with an empty `BTreeSet` and
returns ownership of the original — no intermediate allocation, no copy, and no
separate `.clear()` needed at the end:

```rust
let compile_set = std::mem::take(&mut self.draw_shaders.compile_set);
for draw_shader_id in compile_set { ... }
// no .clear() needed
```

Previously `shader_cache_dir()` was an inner function called inside
`CxOsDrawShader::new`, meaning it ran once **per shader** on every compilation.
Each call performs two syscalls: `env::var("LOCALAPPDATA")` and
`fs::create_dir_all`. With N shaders compiling on first launch, this was 2N
unnecessary syscalls.

`shader_cache_dir()` is now a module-level function called **once** before the
loop in `hlsl_compile_shaders`, and the resulting `Option<&Path>` is passed into
`new` as a parameter.

The HLSL source already lives in `cx_shader.mapping.code`. The previous code
cloned it into an owned `String` before passing it to `new`, even though all
downstream uses (hashing, `D3DCompile`, cache I/O, error printing) only need a
`&str`. Changed the parameter type to `&str` and restructured the loop body into
a block scope so the immutable borrow on `cx_shader` ends before the mutable
reborrow — eliminating the clone entirely.

`CxOsDrawShader::new` already took `&UniformBufferBindings` by reference. The
clone existed only because the immutable borrow on `cx_shader` had to be released
before the mutable reborrow. The same block-scope restructuring from fix #4
resolves this: `&cx_shader.mapping.uniform_buffer_bindings` is now passed
directly.

This field was written once on construction and **never read** — the only reader
was the O(n) deduplication scan removed in the previous round of fixes. It held
a full copy of each shader's HLSL source for the entire lifetime of the
application. At tens of KB per shader and dozens of shaders, this was megabytes
of permanently retained dead storage. The field is gone.

* More shader optimizations on windows

Properly get the Local AppData directory instead of using the
env var %LOCALAPPDATA, which may not always be there.
Now we do it with `SHGetKnownFolderPath(FOLDERID_LocalAppData)`,
which is canonically correct.

We also cache the directory path itself.
2026-03-26 08:25:01 +01:00
Admin
e9031b5edd xr test 2026-03-26 00:32:24 +01:00
Kevin Boos
cdfc14d557 Cache shader compilation on windows to avoid long UI hangs (#987)
On Windows, a large app like Robrix freezes for 10–20+ seconds after login while sync begins.
Profiling (`sc.user_aux.etl` from Visual Studio Performance Profiler) showed:

| Module | Exclusive CPU samples | % of total |
|---|---|---|
| `d3dcompiler_47.dll` | 24,239 | **76.76%** |
| `robrix.exe` | 2,715 | 8.60% |

A single thread (TID 20308) consumed **27.9 seconds of CPU** over the 35-second trace.
Every other robrix thread combined used under 2 seconds.

The butterfly call graph confirmed: `robrix.exe → d3dcompiler_47.dll` with 25,337
inclusive hits (80.24%). The UI was blocked the entire time.

In `makepad/platform/src/os/windows/windows.rs`, the main Win32 event loop calls:

```rust
if self.need_redrawing() {
    self.call_draw_event(time_now);
    self.hlsl_compile_shaders(&d3d11_cx);  // blocks here
}
```

`hlsl_compile_shaders` iterates over every shader in `compile_set` and calls
`CxOsDrawShader::new`, which calls `D3DCompile` (from `d3dcompiler_47.dll`)
**synchronously on the UI thread** for each unique shader. After login, many
new UI panels render for the first time, flooding `compile_set`. `D3DCompile`
is a full software HLSL→DXBC compiler with no OS-level cache — it is CPU-bound
and cannot yield.

This affects all makepad apps on Windows, not just Robrix.

**File changed:** `makepad/platform/src/os/windows/d3d11.rs`

Added a disk-based shader bytecode cache so that `D3DCompile` is only called
once per unique shader source, on first launch. Subsequent launches load the
pre-compiled DXBC bytecode directly, skipping `D3DCompile` entirely.

**Specific changes:**

1. `CxOsDrawShader` struct: changed `pixel_shader_blob` and `vertex_shader_blob`
   field types from `ID3DBlob` to `Vec<u8>`. These fields were stored but never
   read after construction, so there is no behavioral difference.

2. `compile_shader` (inner fn): changed return type from `ID3DBlob` to `Vec<u8>`,
   copying the blob bytes out before returning.

3. Three new inner helper functions added to `CxOsDrawShader::new`:
   - `hlsl_cache_key(hlsl: &str) -> u64` — FNV-1a 64-bit hash of the HLSL
     source string, stable across Rust versions, used as the cache key.
   - `shader_cache_dir() -> Option<PathBuf>` — resolves
     `%LOCALAPPDATA%\makepad\d3d11_shader_cache\`, creating it if needed.
     Returns `None` gracefully if `LOCALAPPDATA` is unset or the directory
     cannot be created, in which case compilation proceeds as before.
   - `get_shader_bytes(...)` — checks for a cached `<hash>_vs.dxbc` /
     `<hash>_ps.dxbc` file; on a cache miss, compiles via `D3DCompile` and
     writes the result to disk before returning.

- **First launch:** all shaders compile as before; each VS/PS blob is written to
  `%LOCALAPPDATA%\makepad\d3d11_shader_cache\<hash>_vs.dxbc` and `<hash>_ps.dxbc`.
- **Subsequent launches:** bytecode is read from disk; `CreateVertexShader` /
  `CreatePixelShader` / `CreateInputLayout` are called directly with the cached
  bytes — `D3DCompile` is never invoked.
- **Cache invalidation:** the cache key is the FNV-1a hash of the HLSL source,
  so entries automatically become stale (and are recompiled + re-cached) whenever
  the shader source changes.
- **Failure safety:** file I/O errors are silently ignored — a failed write means
  the cache is just skipped next time, and a failed read falls through to
  recompilation.
2026-03-26 00:03:59 +01:00
Admin
e9cc2e2035 optimising quest renderpath with physics 2026-03-25 23:29:41 +01:00
Admin
80f943e6dc optimising quest renderpath with physics 2026-03-25 22:44:16 +01:00
Admin
c33b1d43c4 optimising quest renderpath with physics 2026-03-25 22:44:16 +01:00
Admin
c953b348d9 optimising quest renderpath with physics 2026-03-25 22:44:16 +01:00
Admin
ce27a80738 optimising quest renderpath with physics 2026-03-25 22:44:16 +01:00
Admin
5f51f5a27b optimising quest renderpath with physics 2026-03-25 22:44:16 +01:00