* 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.
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.
* ft makepad_test
* Improve run handling, manifest parsing, and stdout newline
Replace dynamic free-port lookup with an ephemeral localhost SocketAddr in test runtime and remove the unused find_free_listen_address helper. Ensure headless stdout messages end with a newline. Simplify send_to_app error handling and add a test that queued bootstrap messages are delivered once an app socket connects. Substantially enhance process_manager: unify cargo flag parsing, parse Cargo.toml to determine package/bin targets, resolve the correct binary name for direct stdio runs, and build the cargo/build+exec script from the resolved args. Add unit tests for manifest parsing and script generation and adjust related call sites.
* test harness
* Preserve test attrs; return Vec for gateway binds
In the test macro (libs/makepad_test/macros/src/lib.rs) preserve wrapper-only attributes (ignore and should_panic) on the generated wrapper test while removing them from the inner function. Added Attribute import, is_wrapper_only_test_attr helper, adjusted attribute filtering and emission, and added unit tests to verify attribute placement and expansion.
In the hub (studio/hub/src/hub.rs) change gateway_bind_candidates to return a Vec<SocketAddr> instead of an iterator and special-case ephemeral port 0 to preserve ephemeral binding; otherwise collect the range of candidate ports into a Vec. Added tests to validate candidate behavior. Also minor formatting/whitespace tweaks and a small IPv6 formatting adjustment.
* Add visible Studio mode and remote client
Enable running UI tests visibly through a running Makepad Studio. Adds a new makepad-network dependency and studio_remote client (libs/makepad_test/src/studio_remote.rs) and integrates it into the runtime via a TestConnection enum. Introduces visible-mode tooling: env vars (MAKEPAD_TEST_VISIBLE, MAKEPAD_TEST_STUDIO, MAKEPAD_TEST_STUDIO_MOUNT, MAKEPAD_TEST_STARTUP_DELAY_MS, MAKEPAD_TEST_ACTION_DELAY_MS, MAKEPAD_TEST_KEEP_OPEN_MS), pacing/delays after actions, and pause-before-shutdown. Splits startup into start_headless_app/start_visible_app, clears existing visible builds before launching, and updates tests, docs (GUIDE.md, README.md), and selector/runtime minor cleanups/formatting.
TL;DR: the shader compiler needed explicit casts in for loop bounds,
and the window positioning was messed up, causing the app-level title bar
to overlap with the native OS-level title bar (which means you couldn't
see or press the window chrome buttons)
--------
here's an AI-generated summary of the changes, for more details:
On OpenGL ES 3.0 targets (Linux/EGL, Android), shaders containing `for` loops
over `uint` variables failed to compile with a GLSL type-error. The shader
compiler emitted the loop header as:
```glsl
for(uint i = 0; i < 4; i++) { … }
```
The integer literals `0` and `4` are of type `int` in GLSL. GLSL ES 3.0 forbids
implicit casts between `int` and `uint`, so the initialiser and the comparison
both produce a compile error. The other shader backends (Metal/WGSL/Rust) do not
have this restriction, so no corresponding arm existed for GLSL.
**`platform/script/src/shader_control.rs`** — Add a `ShaderBackend::Glsl` arm to
`handle_for_1` that wraps both loop bounds in an explicit constructor call for the
loop variable's type:
```glsl
for(uint i = uint(0); i < uint(4); i++) { … }
```
This satisfies GLSL ES 3.0's strict no-implicit-cast rule and matches the
behavior already implemented for the WGSL backend (which uses typed variable
declarations for the same reason).
**`platform/src/os/linux/opengl.rs`** — Gate the helper functions
`shader_source_hash` and `shader_source_preview` (and their call-sites) behind
`#[cfg(target_os = "android")]`. These functions are only referenced from
Android-specific shader-cache code paths; without the attribute the compiler
emits dead-code warnings on every other Linux/OpenGL build.
- `platform/script/src/shader_control.rs`
- `platform/src/os/linux/opengl.rs`
----------------------------------------------------------------------------
On GNOME (and likely other modern WMs), restoring a saved window position would
consistently produce two visual artifacts:
1. **No WM title bar visible** — the client area was rendered where the title bar
should appear.
2. **Black bar at the bottom** — the bottom portion of the window surface was not
covered by rendered content.
The root cause was two related issues in `xlib_window.rs`:
**Issue 1 — `XMoveWindow` called after `XMapWindow` (races with WM reparenting)**
After `XMapWindow`, the window manager asynchronously reparents the client window
into a decoration frame. If `XMoveWindow` is called after reparenting has occurred,
the coordinates are interpreted relative to the WM frame rather than the root
window. For example, calling `XMoveWindow(client, 23, 89)` after GNOME reparents
places the client 89 px from the top of the WM frame. Since the title bar is only
~37 px tall, the client ends up 52 px below the title bar, and its bottom edge
extends 52 px *beyond* the bottom of the WM frame. GNOME responds by resizing or
repositioning the client, producing the rendering artifacts described above.
**Issue 2 — No `USPosition` hint set**
Without the `USPosition` flag in `WM_NORMAL_HINTS`, GNOME ignores the position
provided to `XCreateWindow` and applies its own smart-placement algorithm. This
meant the application relied entirely on the post-map `XMoveWindow` call described
above, which was itself broken.
**`platform/src/os/linux/x11/x11_sys.rs`** — Add the standard `XSizeHints` flag
constants:
- `USPosition` (`1 << 0`) — user-specified x, y
- `USSize` (`1 << 1`) — user-specified width, height
- `PPosition` (`1 << 2`) — program-specified position
- `PSize` (`1 << 3`) — program-specified size
**`platform/src/os/linux/x11/xlib_window.rs`** — Two changes in `XlibWindow::init()`:
1. Before calling `Xutf8SetWMProperties`, populate an `XSizeHints` struct with
`flags = USPosition | PPosition` (and `x`/`y` set to the requested coordinates)
when a position was provided. Pass this struct as the `WM_NORMAL_HINTS` argument
instead of the previous `ptr::null_mut()`. This tells GNOME/Mutter to honor the
requested position rather than running its own placement heuristic.
2. Move the `XMoveWindow` call to *before* `XMapWindow`. At that point the window is
still a direct child of the root window, so the coordinates are unambiguously
root-relative. This eliminates the race with WM reparenting entirely.
* Schedule loader removal after presented frame
Introduce loader_after_presented_frame_id and add schedule/cancel helpers to remove the canvas loader after a presented animation frame. Cancel any pending requestAnimationFrame when the loader is removed or conditions change, and update update_startup_loader to use the new scheduling logic (remove loader only after seen animation frame and quiet frames threshold). This prevents premature removal and visual glitches while preserving the existing fallback timer.
* small fonts should be used when --profile=small
* dont include large fonts on small profile + fix compress serving
* Refactor window initialization and caption sync
Extract sync_caption_bar_state and sync_caption_title from ensure_initialized and call them before the initialized early-return so caption bar state and title are kept in sync even when the widget is re-applied. ensure_initialized still performs the original one-time setup (pass, depth texture, demo frame), but runtime chrome (VR button and caption visibility/title) is now updated up-front to avoid stale UI state.
* fix --bindgen
* Set imports.env in wasm import patches
Add additional string replacements to ensure imports.env = env is injected into generated JS for different formatting variants of the __wbg_get_imports(...) call. This makes the patch robust to variations like missing const or spacing so the env object is always attached to the wasm imports.
* Revert "Set imports.env in wasm import patches"
This reverts commit 37933234d36b4ee867690cf167474638e81febf7.
* Revert "fix --bindgen"
This reverts commit cb236b202b92a46eda3cb1ee4c678bdf6507081a.
* Reapply "fix --bindgen"
This reverts commit 27df4175bd1889222b928ffb68213aa865d1c839.
* Reapply "Set imports.env in wasm import patches"
This reverts commit 23695b4fa646d8f1b7a31a3fc27eb92f5bce0cf8.
* fix xr compilation error
* Fix windows build: add missing constants in vendored windows-rs bindings
* fix more build errors and warnings in windows-rs vendored copy
* Fix SVG parsing and vector drawing
* Fix erroneous unzip glob pattern that doesn't work on normal linux
* try another approach: unzip everything, cp needed dirs