mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-12 10:21:17 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfab16c2f4 | ||
|
|
d007134001 |
@@ -569,16 +569,12 @@ on the **first** call. So:
|
||||
#endif
|
||||
```
|
||||
|
||||
**Before GTK starts**, only the environment exists. `CLI::run` (`src/OrcaSlicer.cpp`) sets the NVIDIA
|
||||
DMABUF workaround and then branches on `GDK_BACKEND` (an `x11` prefix means the X11 opt-in). On both
|
||||
paths, when `/proc/driver/nvidia/version` exists and the linked WebKitGTK honours the switch (>= 2.44,
|
||||
`WebView::SupportsForceSharedMemory()`), it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and
|
||||
`WEBKIT_FORCE_DMABUF_RENDERER=1` (non-replacing): the proprietary driver cannot back WebKit's hardware
|
||||
DMABUF renderer. On the default path it forces `GDK_BACKEND=x11` when wx lacks EGL and `WAYLAND_DISPLAY`
|
||||
is set. It sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) only when both `DISPLAY` and
|
||||
`WAYLAND_DISPLAY` are set and the linked WebKitGTK is older than 2.42, and calls `XInitThreads()`
|
||||
**Before GTK starts**, only the environment exists. `CLI::run` (`src/OrcaSlicer.cpp`) branches on
|
||||
`GDK_BACKEND` (an `x11` prefix means the X11 opt-in). On the default path it forces `GDK_BACKEND=x11`
|
||||
when wx lacks EGL and `WAYLAND_DISPLAY` is set. It sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
|
||||
(non-replacing) only when both `DISPLAY` and `WAYLAND_DISPLAY` are set, and calls `XInitThreads()`
|
||||
only when `DISPLAY` is set. With neither variable set, the GUI refuses to start ("Neither DISPLAY nor
|
||||
WAYLAND_DISPLAY set"). The details and the WebKit rules (c12912e0df) are in
|
||||
WAYLAND_DISPLAY set"). The details and the WebKit rule (c12912e0df) are in
|
||||
`references/webview-gl-aui-media.md` §WebKitGTK on Linux sessions.
|
||||
|
||||
**wx's own detectors**, for reference:
|
||||
|
||||
@@ -61,8 +61,7 @@ available; GTK2/WebKit1 is an opt-out configuration the GUI does not support.
|
||||
11. A web host that re-themes in place handles `EVT_WEBVIEW_RECREATED` without `Skip()`; one that
|
||||
needs a reload lets it through. §[Orca wrapper](#orca-webview-wrapper-widgetswebview)
|
||||
12. Keep `WebViewWebKit`'s destructor removing the `"wx"` handler. §[Orca wrapper](#orca-webview-wrapper-widgetswebview)
|
||||
13. Do not widen `WEBKIT_DISABLE_COMPOSITING_MODE` beyond Orca's default-path XWayland case for
|
||||
WebKitGTK older than 2.42, and never
|
||||
13. Do not widen `WEBKIT_DISABLE_COMPOSITING_MODE` beyond Orca's default-path XWayland case, and never
|
||||
make a page's progress depend only on a C++→JS callback. §[WebKitGTK on Linux](#webkitgtk-on-linux-sessions)
|
||||
14. GL attribute lists: legacy `int[]` lists end with `0` and spell out `WX_GL_RGBA` and
|
||||
`WX_GL_DOUBLEBUFFER`; `wxGLAttributes`/`wxGLContextAttrs` end with `EndList()`; MSAA is requested
|
||||
@@ -568,66 +567,33 @@ vue-resize workaround script and sets a persistent SQLite cookie store on the vi
|
||||
## WebKitGTK on Linux sessions
|
||||
|
||||
**Mechanism.** `CLI::run` (`src/OrcaSlicer.cpp`, under `#ifdef __WXGTK__`, before GTK initialises)
|
||||
sets the NVIDIA DMABUF workaround and then branches on `GDK_BACKEND`:
|
||||
- **Both paths**, before the split: when `/proc/driver/nvidia/version` exists and the linked WebKitGTK
|
||||
honours the switch (>= 2.44, `WebView::SupportsForceSharedMemory()`), it sets
|
||||
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and `WEBKIT_FORCE_DMABUF_RENDERER=1` non-replacing. The
|
||||
proprietary driver cannot back WebKit's hardware DMABUF renderer, so the shared-memory transport keeps
|
||||
accelerated compositing instead of dropping it; `WEBKIT_FORCE_DMABUF_RENDERER` clears the
|
||||
Debian/Ubuntu/openSUSE `isNVIDIA()` early return, which otherwise returns before the FORCE_SHM check.
|
||||
branches on `GDK_BACKEND`:
|
||||
- **Default path** (`GDK_BACKEND` unset or not starting with `x11`): if wx lacks EGL (`!wxHAS_EGL`) and
|
||||
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`; it sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
|
||||
non-replacing **only** when the linked WebKitGTK predates 2.42 (`WebView::NeedsCompositingWorkaround()`)
|
||||
**and** both `DISPLAY` and `WAYLAND_DISPLAY` are non-empty (a Wayland session with
|
||||
non-replacing **only** when both `DISPLAY` and `WAYLAND_DISPLAY` are non-empty (a Wayland session with
|
||||
XWayland available — the session, not the backend GTK will pick); `XInitThreads()` runs only when
|
||||
`DISPLAY` is set.
|
||||
- **`GDK_BACKEND=x11…` opt-in path**: sets `DRI_PRIME=1` (and the NVIDIA PRIME variables when
|
||||
`/proc/driver/nvidia/version` exists), calls `XInitThreads()`, and deliberately does **not** disable
|
||||
compositing, which keeps hardware acceleration for Device / Setup Wizard / login pages.
|
||||
compositing (the comment: the WebKit2GTK XWayland bug appears fixed in WebKit2GTK ≥ 2.42, and leaving
|
||||
compositing on keeps hardware acceleration for Device / Setup Wizard / login pages).
|
||||
|
||||
Runtime backend detection elsewhere uses Orca's `is_running_on_wayland()` / `is_running_on_x11()`
|
||||
(see `references/platforms.md`), not these environment variables.
|
||||
|
||||
**Pitfall.**
|
||||
- **Rule:** On NVIDIA reach for `WEBKIT_DMABUF_RENDERER_FORCE_SHM` before
|
||||
`WEBKIT_DISABLE_COMPOSITING_MODE`. **Why:** the proprietary driver cannot back WebKit's hardware
|
||||
DMABUF renderer, leaving web views blank or slow; FORCE_SHM keeps accelerated compositing and only
|
||||
skips the hardware path, while disabling compositing drops it. Threaded scrolling is a separate,
|
||||
release-dependent default (off in 2.44/2.45, on from 2.46). `WEBKIT_DISABLE_DMABUF_RENDERER` is not a
|
||||
substitute — it is read first, leaves the transport set empty (no compositing from 2.44 on, where the
|
||||
X11/Wayland fallback renderers were removed) and has a reported crash path. On Debian/Ubuntu and
|
||||
openSUSE the `isNVIDIA()` patch returns before the FORCE_SHM check, so `WEBKIT_FORCE_DMABUF_RENDERER=1`
|
||||
must clear it first; that variable exists only in those patches. WebKit reads all of these once per
|
||||
process, before the first WebView.
|
||||
The test is `/proc/driver/nvidia/version`, so a hybrid laptop that renders on its iGPU also takes the
|
||||
shared-memory path, which is a per-frame GPU readback rather than zero-copy; exporting
|
||||
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=0` in the environment restores the hardware path.
|
||||
```cpp
|
||||
// Wrong: kills compositing and threaded scrolling (and can crash on modern WebKit)
|
||||
::setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", false);
|
||||
// Right: keep compositing, skip only the hardware DMABUF path
|
||||
if (::access("/proc/driver/nvidia/version", F_OK) == 0 && WebView::SupportsForceSharedMemory()) {
|
||||
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /*replace*/ false);
|
||||
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /*replace*/ false);
|
||||
}
|
||||
```
|
||||
Cite: WebKit 2.52.6 `Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.cpp`
|
||||
(`rendererBufferTransportMode()`), Debian `disable-nvidia-dmabuf.patch`, WebKit bug 262607.
|
||||
- **Rule:** Set `WEBKIT_DISABLE_COMPOSITING_MODE` only on the default path in Wayland sessions,
|
||||
non-replacing (`setenv(..., false)`, so a value the user already exported wins), before GTK
|
||||
initialises; never unconditionally, and only for the WebKitGTK releases that need it (older than
|
||||
2.42, `WebView::NeedsCompositingWorkaround()`). Keep JS-side fallbacks so a page never
|
||||
initialises; never unconditionally. Keep JS-side fallbacks so a page never
|
||||
waits forever for a C++→JS call.
|
||||
**Why:** WebKit2GTK accelerated compositing could fail silently under XWayland before 2.42, leaving
|
||||
web views blank (Setup Wizard stuck on "Loading..."); the variable also turns off threaded scrolling,
|
||||
so applying it on a newer WebKit needlessly drops both GPU compositing and async scrolling (the
|
||||
Device tab loses its smoothness); without the JS timeout the user is stuck when the signal is lost.
|
||||
**Why:** WebKit2GTK accelerated compositing could fail silently under XWayland, leaving web views
|
||||
blank (Setup Wizard stuck on "Loading..."); disabling it everywhere needlessly drops hardware
|
||||
acceleration; without the JS timeout the user is stuck when the signal is lost.
|
||||
```cpp
|
||||
// Wrong: unconditional, kills HW acceleration everywhere
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", false);
|
||||
// Right (default path only): non-replacing, before GTK init, WebKitGTK < 2.42,
|
||||
// Wayland session with XWayland
|
||||
if (WebView::NeedsCompositingWorkaround() && display && *display && wayland && *wayland)
|
||||
// Right (default path only): non-replacing, before GTK init, Wayland session with XWayland
|
||||
if (display && *display && wayland && *wayland)
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /*replace*/ false);
|
||||
```
|
||||
Cite: c12912e0df (`src/OrcaSlicer.cpp`; `resources/web/guide/0/load.js` `OnInit`
|
||||
|
||||
@@ -7478,7 +7478,7 @@ msgid "Update"
|
||||
msgstr "更新"
|
||||
|
||||
msgid "Assistant(HMS)"
|
||||
msgstr "助理(HMS)"
|
||||
msgstr "助手(HMS)"
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "Network plug-in v%s"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# WebKit's NVIDIA DMABUF workaround (gnome-build-meta#754) is applied by the
|
||||
# binary itself, gated on the runtime WebKitGTK version (src/OrcaSlicer.cpp).
|
||||
# Work-around https://gitlab.gnome.org/GNOME/gnome-build-meta/-/issues/754
|
||||
grep -q org.freedesktop.Platform.GL.nvidia /.flatpak-info && export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
||||
|
||||
# Work-around https://github.com/bambulab/BambuStudio/issues/3440
|
||||
# Use LC_NUMERIC instead of LC_ALL to prevent decimal separator issues
|
||||
|
||||
+8
-18
@@ -161,7 +161,6 @@ using json = nlohmann::json;
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include "slic3r/GUI/Widgets/WebView.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
@@ -1154,9 +1153,8 @@ int CLI::run(int argc, char **argv)
|
||||
// opt-in path. The ~2020-era WebKit2GTK XWayland compositor bug it
|
||||
// worked around appears fixed in WebKit2GTK >= 2.42; leaving it
|
||||
// unset preserves WebKit hardware acceleration on Device / Setup
|
||||
// Wizard / login / store. The default path applies it only on
|
||||
// XWayland sessions and only when the linked WebKitGTK predates the
|
||||
// fix (< 2.42), as a conservative fallback for older WebKit.
|
||||
// Wizard / login / store. The default path still applies it on
|
||||
// XWayland sessions as a conservative fallback for older WebKit.
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
const char* gdk_backend = ::getenv("GDK_BACKEND");
|
||||
@@ -1164,14 +1162,6 @@ int CLI::run(int argc, char **argv)
|
||||
// honours the first backend in the comma-separated list).
|
||||
const bool x11_opt_in = gdk_backend && boost::starts_with(gdk_backend, "x11");
|
||||
|
||||
// NVIDIA's proprietary driver cannot back WebKitGTK's DMABUF renderer.
|
||||
// Force the shared-memory transport, which keeps compositing and threaded
|
||||
// scrolling; the second variable clears Debian/Ubuntu/openSUSE's isNVIDIA().
|
||||
if (::access("/proc/driver/nvidia/version", F_OK) == 0 && WebView::SupportsForceSharedMemory()) {
|
||||
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /* replace */ false);
|
||||
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /* replace */ false);
|
||||
}
|
||||
|
||||
if (x11_opt_in) {
|
||||
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
||||
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
||||
@@ -1215,15 +1205,15 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
#endif
|
||||
|
||||
// WebKit2GTK accelerated compositing could fail under XWayland
|
||||
// on WebKit releases before 2.42. Disable it only then, and only
|
||||
// when both DISPLAY and WAYLAND_DISPLAY are set (an XWayland
|
||||
// session). Disabling it also turns off threaded scrolling, so on
|
||||
// newer WebKit it only costs performance.
|
||||
// WebKit2GTK compositing can fail under XWayland on older
|
||||
// WebKit releases. Disable it only when both DISPLAY and
|
||||
// WAYLAND_DISPLAY are set (i.e. an XWayland session is in
|
||||
// use). On pure X11 or native Wayland, compositing is left
|
||||
// enabled so WebKit retains HW acceleration.
|
||||
{
|
||||
const char* display_env_wk = ::getenv("DISPLAY");
|
||||
const char* wayland_env_wk = ::getenv("WAYLAND_DISPLAY");
|
||||
if (WebView::NeedsCompositingWorkaround() && display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
|
||||
if (display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,8 +429,7 @@ if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ];
|
||||
export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
|
||||
export MESA_LOADER_DRIVER_OVERRIDE=zink
|
||||
export GALLIUM_DRIVER=zink
|
||||
# WebKit's NVIDIA DMABUF workaround is applied by the binary itself,
|
||||
# gated on the runtime WebKitGTK version (src/OrcaSlicer.cpp).
|
||||
export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -771,9 +771,12 @@ struct TextureColorRequest
|
||||
// single facets; an image detail a few facets wide still leaves thousands of pinhead islands
|
||||
// that the slicer's multi-material segmentation cannot digest. 0 turns it off.
|
||||
float min_color_region_mm2 = 0.5f;
|
||||
// Filled per *base mesh* triangle (the bake is topology-preserving, so this indexes the returned
|
||||
// mesh too): the quantize callback's index plus one, or 0 for "this triangle takes no colour from
|
||||
// the texture". The +1 lines up with EnforcerBlockerType, where 0 is NONE ("use the volume's own
|
||||
// Filled per triangle of the *returned* mesh: the quantize callback's index plus one, or 0 for
|
||||
// "this triangle takes no colour from the texture". That is the same indexing as the base mesh
|
||||
// only on the classic path, which moves vertices and leaves the topology alone; the one-run
|
||||
// pipeline rebuilds the mesh, so nothing recorded against the base mesh - an existing paint mask,
|
||||
// say - may be carried over by triangle id (see TriangleSelector::remap_painting()).
|
||||
// The +1 lines up with EnforcerBlockerType, where 0 is NONE ("use the volume's own
|
||||
// filament"): where every palette entry is a filament, these go straight to a TriangleSelector. A
|
||||
// palette with mixes maps each index to the mix's filament slot first (see
|
||||
// GLGizmoTextureDisplacement::palette_filaments()).
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
@@ -76,6 +77,23 @@ void TextureDisplacementBakeJob::process(Ctl &ctl)
|
||||
},
|
||||
color, m_input.volume_to_world, nullptr, &m_stats));
|
||||
|
||||
// Bring the colour that was already painted onto the mesh this bake just built, while still on
|
||||
// the worker thread. Triangle ids only mean the same thing on both meshes when the bake preserved
|
||||
// the topology - the classic path moves vertices and nothing else - and the one-run pipeline
|
||||
// rebuilds the mesh from scratch. Deserializing the old mask against the new one only checks that
|
||||
// the ids are in range, and a bake almost always *grows* the triangle count, so every stale id
|
||||
// passes that check and lands on an unrelated face: colour debris where nothing was painted, and
|
||||
// the earlier pass's colour destroyed rather than kept. remap_painting() transfers it by position
|
||||
// instead, which is what lets successive bakes accumulate. It is spatial work over both meshes,
|
||||
// which is why it belongs here and not in finalize().
|
||||
if (!m_input.mmu_segmentation.bitstream.empty() && !m_result.empty() && !ctl.was_canceled()) {
|
||||
if (m_result.its.indices.size() == m_input.base_mesh.indices.size())
|
||||
m_carried_mmu = m_input.mmu_segmentation;
|
||||
else
|
||||
m_carried_mmu = TriangleSelector::remap_painting(m_input.base_mesh, m_input.mmu_segmentation,
|
||||
m_result.its, Transform3d::Identity(), {});
|
||||
}
|
||||
|
||||
// Always finish at 100: this is what closes the notification. Reported even on cancel, where
|
||||
// build_texture_displacement() returns an empty mesh and finalize() commits nothing.
|
||||
ctl.update_status(100, status);
|
||||
@@ -126,6 +144,18 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept
|
||||
{ mix.a_percent(), 100 - mix.a_percent() });
|
||||
});
|
||||
|
||||
// Read while the volume still holds the mesh the paint was recorded against, and after the
|
||||
// filament slots above - adding one can clamp the paint, and that clamp has to survive.
|
||||
// Normally process() has already carried the colour onto the baked topology; it only has to
|
||||
// be redone here when the paint changed after the job was queued, because the snapshot
|
||||
// process() worked from is then not what the user is looking at.
|
||||
const bool mmu_changed = volume->mmu_segmentation_facets.timestamp() != m_input.mmu_segmentation_timestamp;
|
||||
const indexed_triangle_set pre_bake_mesh = mmu_changed ? volume->mesh().its
|
||||
: indexed_triangle_set{};
|
||||
const TriangleSelector::TriangleSplittingData live_mmu = mmu_changed
|
||||
? volume->mmu_segmentation_facets.get_data()
|
||||
: TriangleSelector::TriangleSplittingData{};
|
||||
|
||||
volume->set_mesh(std::move(m_result));
|
||||
volume->set_new_unique_id();
|
||||
volume->calculate_convex_hull();
|
||||
@@ -134,13 +164,19 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept
|
||||
// rather than replacing it: a triangle the texture does not colour keeps its existing filament,
|
||||
// and one the user never painted at all stays at NONE, which already means "the volume's own
|
||||
// filament". That is what confines the effect to the painted area without having to invent a
|
||||
// colour for everything outside it. Safe to index straight onto the new mesh - the bake is
|
||||
// topology-preserving, so triangle i is still triangle i.
|
||||
// colour for everything outside it. What was already painted comes from m_carried_mmu, which
|
||||
// process() brought onto this mesh's topology; this bake's own colour goes on top of it, so it
|
||||
// wins wherever it painted and leaves the rest alone.
|
||||
if (!m_triangle_color.empty() && m_triangle_color.size() == volume->mesh().its.indices.size()) {
|
||||
if (mmu_changed && !live_mmu.bitstream.empty())
|
||||
m_carried_mmu = pre_bake_mesh.indices.size() == volume->mesh().its.indices.size()
|
||||
? live_mmu
|
||||
: TriangleSelector::remap_painting(pre_bake_mesh, live_mmu,
|
||||
volume->mesh().its,
|
||||
Transform3d::Identity(), {});
|
||||
TriangleSelector selector(volume->mesh());
|
||||
const TriangleSelector::TriangleSplittingData &existing = volume->mmu_segmentation_facets.get_data();
|
||||
if (!existing.bitstream.empty())
|
||||
selector.deserialize(existing, false);
|
||||
if (!m_carried_mmu.bitstream.empty())
|
||||
selector.deserialize(m_carried_mmu, false);
|
||||
for (size_t i = 0; i < m_triangle_color.size(); ++i) {
|
||||
const size_t entry = size_t(m_triangle_color[i]);
|
||||
if (entry > 0 && entry <= filament_of.size() && filament_of[entry - 1] >= 0)
|
||||
@@ -222,6 +258,8 @@ void queue_texture_displacement_bake(const ModelVolume &volume, const TextureCol
|
||||
input.layers = volume.texture_displacement_layers;
|
||||
input.options = volume.texture_displacement_options;
|
||||
input.volume_to_world = texture_displacement_volume_to_world(volume);
|
||||
input.mmu_segmentation = volume.mmu_segmentation_facets.get_data();
|
||||
input.mmu_segmentation_timestamp = volume.mmu_segmentation_facets.timestamp();
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
input.facets_data[size_t(i)] = volume.texture_displacement_facet(i).get_data();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/TextureDisplacement.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
@@ -34,6 +35,13 @@ struct TextureDisplacementBakeInput
|
||||
// writes the volume's mmu_segmentation_facets - the same per-triangle filament assignment the MMU
|
||||
// paint gizmo writes - alongside the displaced geometry.
|
||||
TextureColorSettings color;
|
||||
// The colour already painted on the volume, indexed against `base_mesh`. Carried here rather than
|
||||
// read at commit time because the one-run pipeline rebuilds the mesh, and bringing this across to
|
||||
// the new topology is spatial work (see process()) that has no business on the UI thread.
|
||||
TriangleSelector::TriangleSplittingData mmu_segmentation;
|
||||
// What `mmu_segmentation` was taken from, so the commit can tell whether the volume was painted
|
||||
// while the bake ran and fall back to the live data if it was.
|
||||
ObjectBase::Timestamp mmu_segmentation_timestamp = 0;
|
||||
// Whether this job pushes its own undo step when it commits. False when the caller has already
|
||||
// taken one that is meant to cover the displacement as well - Standard mode's Bake, which remeshes
|
||||
// and subdivides first and has to undo as a single action.
|
||||
@@ -60,6 +68,9 @@ private:
|
||||
// Empty unless a layer asked for colour. See TextureColorRequest; finalize() turns these into
|
||||
// filaments.
|
||||
std::vector<uint8_t> m_triangle_color;
|
||||
// m_input.mmu_segmentation brought onto m_result's topology, so finalize() can lay this bake's
|
||||
// colour over the colour that was already there. Indexed against m_result, not base_mesh.
|
||||
TriangleSelector::TriangleSplittingData m_carried_mmu;
|
||||
std::function<void()> m_on_finished;
|
||||
};
|
||||
|
||||
|
||||
@@ -69,10 +69,6 @@ webkit_web_view_run_javascript_finish (WebKitWebView
|
||||
GError **error);
|
||||
WEBKIT_API void
|
||||
webkit_javascript_result_unref (WebKitJavascriptResult *js_result);
|
||||
WEBKIT_API guint
|
||||
webkit_get_major_version (void);
|
||||
WEBKIT_API guint
|
||||
webkit_get_minor_version (void);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -405,29 +401,6 @@ bool WebView::NeedsRecreateOnShow()
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebView::NeedsCompositingWorkaround()
|
||||
{
|
||||
#ifdef __linux__
|
||||
const guint major = webkit_get_major_version();
|
||||
const guint minor = webkit_get_minor_version();
|
||||
return major < 2 || (major == 2 && minor < 42);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebView::SupportsForceSharedMemory()
|
||||
{
|
||||
#ifdef __linux__
|
||||
const guint major = webkit_get_major_version();
|
||||
const guint minor = webkit_get_minor_version();
|
||||
return major > 2 || (major == 2 && minor >= 44);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if wxUSE_WEBVIEW_EDGE
|
||||
bool WebView::CheckWebViewRuntime()
|
||||
{
|
||||
|
||||
@@ -30,16 +30,6 @@ public:
|
||||
// ignoring every navigation. A panel that gets true here recreates its view on first Show().
|
||||
static bool NeedsRecreateOnShow();
|
||||
|
||||
// True when the linked WebKitGTK predates 2.42, where accelerated compositing
|
||||
// could fail silently on XWayland sessions, so it is forced off as a fallback.
|
||||
// False on every other platform. Safe to call before GTK initialises.
|
||||
static bool NeedsCompositingWorkaround();
|
||||
|
||||
// True when the linked WebKitGTK is new enough (>= 2.44) to honour
|
||||
// WEBKIT_DMABUF_RENDERER_FORCE_SHM, the shared-memory transport switch.
|
||||
// False on every other platform. Safe to call before GTK initialises.
|
||||
static bool SupportsForceSharedMemory();
|
||||
|
||||
static void RecreateAll();
|
||||
};
|
||||
|
||||
|
||||
@@ -35,10 +35,12 @@
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "libslic3r/AABBMesh.hpp"
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
#include "libslic3r/TextureDisplacement.hpp"
|
||||
#include "libslic3r/TextureBake/TextureBakeDecimate.hpp"
|
||||
#include "libslic3r/TextureBake/TextureBakeFlip.hpp"
|
||||
#include "libslic3r/TextureBake/TextureBakeMesh.hpp"
|
||||
#include "libslic3r/TextureBake/TextureBakeRepair.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
@@ -2174,3 +2176,151 @@ TEST_CASE("whether a layer's texture has colour agrees with its decode", "[Textu
|
||||
|
||||
CHECK_FALSE(height_texture_has_color(TextureDisplacementLayer{}));
|
||||
}
|
||||
|
||||
TEST_CASE("Baking twice keeps the mesh closed and carries the first bake's colours across",
|
||||
"[TextureDisplacement][Regression]")
|
||||
{
|
||||
// Bake, colour part of what came back, bake again. The one-run pipeline rebuilds the topology, so
|
||||
// the colour recorded against the first bake's mesh indexes triangles that no longer mean the same
|
||||
// thing. Taken across by triangle id it lands on unrelated faces - a bake almost always grows the
|
||||
// triangle count, so every stale id passes the only bound check deserialize() makes - which both
|
||||
// scatters colour over surfaces nothing painted and destroys what the first pass laid down.
|
||||
// TriangleSelector::remap_painting() carries it by position instead, which is what makes successive
|
||||
// bakes accumulate. See TextureDisplacementBakeJob, which is where the two meet.
|
||||
constexpr float side = 20.f;
|
||||
constexpr float refine = 0.5f;
|
||||
constexpr float radius = 4.f;
|
||||
const indexed_triangle_set cube = its_make_cube(side, side, side);
|
||||
const Vec2f first_stroke(6.f, 6.f), second_stroke(14.f, 14.f);
|
||||
|
||||
TextureDisplacementLayer layer;
|
||||
layer.slot = 0;
|
||||
layer.image_data = make_rgb_png_2x2();
|
||||
layer.tiling_scale = 5.f;
|
||||
layer.depth_mm = 0.5f;
|
||||
layer.color_enabled = true;
|
||||
|
||||
TextureDisplacementOptions options;
|
||||
options.pipeline_v2 = true;
|
||||
options.v2_refine_mm = refine;
|
||||
options.v2_max_triangles_k = 750;
|
||||
|
||||
// Three primaries, so a palette entry is legible in the assertions below.
|
||||
const std::array<Vec3f, 3> palette = { Vec3f(1, 0, 0), Vec3f(0, 1, 0), Vec3f(0, 0, 1) };
|
||||
TextureColorRequest request;
|
||||
request.quantize = [&palette](const Vec3f &rgb) {
|
||||
int best = 0;
|
||||
float bd = std::numeric_limits<float>::max();
|
||||
for (int i = 0; i < 3; ++i)
|
||||
if (const float d = (palette[size_t(i)] - rgb).squaredNorm(); d < bd) { bd = d; best = i; }
|
||||
return best;
|
||||
};
|
||||
|
||||
const auto bake = [&](const indexed_triangle_set &mesh, const Vec2f &stroke, std::vector<uint8_t> &color) {
|
||||
TextureDisplacementFacetsData facets;
|
||||
facets[0] = paint_top_disc(mesh, side, stroke, radius);
|
||||
TextureColorRequest req = request;
|
||||
req.out_triangle = &color;
|
||||
return build_texture_displacement(mesh, { layer }, facets, options, {}, &req);
|
||||
};
|
||||
// The pipeline's own counters, over welded positions, so a mesh watertight only after welding
|
||||
// still reads as closed.
|
||||
const auto defects = [](const indexed_triangle_set &mesh) {
|
||||
return TextureBake::count_edge_defects(TextureBake::to_soup(mesh));
|
||||
};
|
||||
|
||||
std::vector<uint8_t> color1;
|
||||
const indexed_triangle_set first = bake(cube, first_stroke, color1);
|
||||
REQUIRE(first.indices.size() > cube.indices.size());
|
||||
REQUIRE(color1.size() == first.indices.size());
|
||||
const TextureBake::EdgeDefects d1 = defects(first);
|
||||
INFO("first bake: " << d1.triangles << " triangles, " << d1.open << " open, " << d1.non_manifold << " non-manifold");
|
||||
CHECK(d1.open == 0);
|
||||
CHECK(d1.non_manifold == 0);
|
||||
|
||||
// The colour the first bake committed, as TextureDisplacementBakeJob writes it.
|
||||
TriangleSelector::TriangleSplittingData painted_first;
|
||||
{
|
||||
const TriangleMesh mesh(first);
|
||||
TriangleSelector selector(mesh);
|
||||
for (size_t i = 0; i < color1.size(); ++i)
|
||||
if (color1[i] != 0)
|
||||
selector.set_facet(int(i), EnforcerBlockerType(color1[i]));
|
||||
painted_first = selector.serialize();
|
||||
}
|
||||
REQUIRE_FALSE(painted_first.bitstream.empty());
|
||||
|
||||
std::vector<uint8_t> color2;
|
||||
const indexed_triangle_set second = bake(first, second_stroke, color2);
|
||||
REQUIRE(color2.size() == second.indices.size());
|
||||
REQUIRE(second.indices.size() != first.indices.size()); // the topology really did change
|
||||
const TextureBake::EdgeDefects d2 = defects(second);
|
||||
INFO("second bake: " << d2.triangles << " triangles, " << d2.open << " open, " << d2.non_manifold << " non-manifold");
|
||||
CHECK(d2.open == 0);
|
||||
CHECK(d2.non_manifold == 0);
|
||||
|
||||
// The commit: the earlier colour carried onto the new topology, this bake's own colour over it.
|
||||
std::vector<uint8_t> committed(second.indices.size(), 0);
|
||||
{
|
||||
const TriangleMesh mesh(second);
|
||||
TriangleSelector selector(mesh);
|
||||
const TriangleSelector::TriangleSplittingData carried = TriangleSelector::remap_painting(
|
||||
first, painted_first, second, Transform3d::Identity(), {});
|
||||
REQUIRE_FALSE(carried.bitstream.empty());
|
||||
selector.deserialize(carried, false);
|
||||
for (size_t i = 0; i < color2.size(); ++i)
|
||||
if (color2[i] != 0)
|
||||
selector.set_facet(int(i), EnforcerBlockerType(color2[i]));
|
||||
for (int state = 1; state <= int(palette.size()); ++state) {
|
||||
std::vector<int> src;
|
||||
if (selector.get_facets_strict(EnforcerBlockerType(state), &src).indices.empty())
|
||||
continue;
|
||||
for (const int s : src)
|
||||
if (s >= 0 && size_t(s) < committed.size() && committed[size_t(s)] == 0)
|
||||
committed[size_t(s)] = uint8_t(state);
|
||||
}
|
||||
}
|
||||
|
||||
// Just the triangles the first bake coloured, to ask "how far is this from colour that was really
|
||||
// laid down" of anything the commit painted.
|
||||
indexed_triangle_set coloured_first;
|
||||
coloured_first.vertices = first.vertices;
|
||||
for (size_t i = 0; i < color1.size(); ++i)
|
||||
if (color1[i] != 0)
|
||||
coloured_first.indices.push_back(first.indices[i]);
|
||||
const AABBTreeIndirect::Tree3f tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
coloured_first.vertices, coloured_first.indices);
|
||||
|
||||
// A triangle may carry colour the second bake did not produce only where the first bake's colour
|
||||
// actually was. Carrying the mask by position can widen that region by a triangle, so the margin
|
||||
// is the edge length the bake was told to refine to; landing anywhere else means the mask was read
|
||||
// against a mesh it no longer matches.
|
||||
size_t own = 0, carried_over = 0, stranded = 0;
|
||||
float worst = 0.f;
|
||||
for (size_t i = 0; i < second.indices.size(); ++i) {
|
||||
if (committed[i] == 0)
|
||||
continue;
|
||||
if (i < color2.size() && color2[i] != 0) {
|
||||
CHECK(committed[i] == color2[i]); // this bake's own colour wins where it painted
|
||||
++own;
|
||||
continue;
|
||||
}
|
||||
const stl_triangle_vertex_indices &t = second.indices[i];
|
||||
const Vec3f g = (second.vertices[size_t(t[0])] + second.vertices[size_t(t[1])] +
|
||||
second.vertices[size_t(t[2])]) / 3.f;
|
||||
size_t hit = 0;
|
||||
Vec3f foot;
|
||||
const float d = std::sqrt(std::max(0.f, AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
coloured_first.vertices, coloured_first.indices, tree, g, hit, foot)));
|
||||
worst = std::max(worst, d);
|
||||
if (d <= 2.f * refine)
|
||||
++carried_over;
|
||||
else
|
||||
++stranded;
|
||||
}
|
||||
INFO("committed: " << own << " from this bake, " << carried_over << " carried over, " << stranded
|
||||
<< " stranded, worst distance from the first bake's colour " << worst << " mm");
|
||||
CHECK(own > 0);
|
||||
CHECK(carried_over > 0); // the first bake's colour survived the second
|
||||
CHECK(stranded == 0); // and nothing was painted where neither bake put colour
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user