Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions doc/Linux-render-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Linux startup and rendering compatibility

The packaged Linux launchers and `scripts/run-local-linux.sh` share
`resources/linux-launch-env.sh`. It supplies the distribution CA bundle when
available and not already configured, and replaces the POSIX C locale with
`en_US.UTF-8` because Flutter requires a browser language tag.

## Blank model canvas or NVIDIA EGL crashes

An optional compatibility mode uses Mesa/Zink over Vulkan, the OpenGL 2.1
rendering path, X11 (XWayland in a Wayland session), and disables WebKit's
DMA-BUF renderer. Install Mesa, its Zink driver, a working Vulkan driver, and
XWayland before enabling it. On Arch Linux the Mesa EGL vendor descriptor is
`/usr/share/glvnd/egl_vendor.d/50_mesa.json`; other distributions must provide
that descriptor as well for this mode.

For a packaged build:

```sh
ORCA_LINUX_RENDER_COMPAT=1 ./Snapmaker_Orca.AppImage
```

For a source build in the usual `build/` directory:

```sh
ORCA_LINUX_RENDER_COMPAT=1 ./scripts/run-local-linux.sh project.3mf
```

Leave the variable unset to retain the normal rendering path and the existing
automatic NVIDIA/Wayland Zink detection in the packaged launchers. Compatibility
mode is a workaround, not a root-cause fix for the modern OpenGL path. It may
reduce performance or disable features requiring newer OpenGL versions.

## Validation and limits

Observed with Snapmaker Orca 2.4.1 (`c0e6987`), Arch Linux, wxGTK3,
WebKitGTK 4.1, NVIDIA RTX 3090 / driver 610.57.04, and Mesa 26.2.2:

- WebKitWebProcess crashed with SIGSEGV inside `libnvidia-eglcore`.
- Default OpenGL contexts produced a blank model canvas with both Zink and
software llvmpipe, while the native toolbar remained visible.
- OpenGL 2.1 displayed model geometry and all seven plates of the same 3MF with
both llvmpipe and Zink. The Zink configuration was then checked using the normal
user profile.
- Model visibility was checked; sliced toolpaths, printing, other GPUs, and
other distributions were not validated.

The portable launcher changes accompany GTK popup lifetime fixes, no-result
WebKit script execution, correct validation of flat printer metadata, and
materialized Eigen casts in integer-coordinate line-radius queries.

## Focused regression tests

```sh
cmake -S tests/crash_regressions -B regression-build \
-DCMAKE_PREFIX_PATH=/path/to/dependency/prefix
cmake --build regression-build
ctest --test-dir regression-build --output-on-failure
```

This suite contains three metadata-validation cases and two line-radius cases.
The metadata cases are also registered with the main `tests/slic3rutils` suite.
Catch2 is fetched by the repository's existing CMake helper; a local Catch2
checkout may be passed as `FETCHCONTENT_SOURCE_DIR_CATCH2` for offline builds.

Two standalone GUI smoke programs in `tests/crash_regressions` exercise popup
destruction and WebKit native-object completion. They require a graphical
session and can be built with the dependency prefix's `wx-config` and system
WebKitGTK:

```sh
c++ -std=c++17 -Isrc $(wx-config --cxxflags) \
tests/crash_regressions/PopupLifecycle_smoke.cpp \
src/slic3r/GUI/Widgets/PopupWindow.cpp $(wx-config --libs) -o popup-smoke
./popup-smoke
c++ tests/crash_regressions/WebKitCompletion_smoke.cpp \
$(pkg-config --cflags --libs webkit2gtk-4.1) -o webkit-smoke
ORCA_LINUX_RENDER_COMPAT=1 sh -c \
'. ./resources/linux-launch-env.sh; exec ./webkit-smoke'
```

The WebKit smoke compares the unsupported native-object completion with a
discarded completion value. It exercises the API behavior, not the complete
application bridge or the driver's crash path.
4 changes: 2 additions & 2 deletions gitlab_pack_linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -477,9 +477,9 @@ pack_appimage() {
#!/bin/bash
DIR=\$(readlink -f "\$0" | xargs dirname)
export LD_LIBRARY_PATH="\$DIR/bin:\$LD_LIBRARY_PATH"
export LC_ALL=C
. "\$DIR/resources/linux-launch-env.sh"

if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ]; then
if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ] && [ "\$ORCA_LINUX_RENDER_COMPAT" != "1" ]; then
if command -v glxinfo >/dev/null 2>&1; then
RENDERER=\$(glxinfo | grep "OpenGL renderer string:" | sed 's/.*: //')
if echo "\$RENDERER" | grep -qi "NVIDIA"; then
Expand Down
4 changes: 2 additions & 2 deletions pack_linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,9 @@ pack_appimage() {
#!/bin/bash
DIR=\$(readlink -f "\$0" | xargs dirname)
export LD_LIBRARY_PATH="\$DIR/bin:\$LD_LIBRARY_PATH"
export LC_ALL=C
. "\$DIR/resources/linux-launch-env.sh"

if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ]; then
if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ] && [ "\$ORCA_LINUX_RENDER_COMPAT" != "1" ]; then
if command -v glxinfo >/dev/null 2>&1; then
RENDERER=\$(glxinfo | grep "OpenGL renderer string:" | sed 's/.*: //')
if echo "\$RENDERER" | grep -qi "NVIDIA"; then
Expand Down
24 changes: 24 additions & 0 deletions resources/linux-launch-env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh
# Shared environment for packaged Linux launchers and source builds.
# Keep user-provided certificate settings; source builds may use an OpenSSL
# prefix that does not contain the distribution's CA bundle.
if [ -z "${SSL_CERT_FILE:-}" ] && [ -f /etc/ssl/cert.pem ]; then
export SSL_CERT_FILE=/etc/ssl/cert.pem
fi

# Flutter expects a browser language tag rather than the POSIX C locale.
case "${LC_ALL:-${LC_MESSAGES:-${LANG:-C}}}" in
C|C.*|POSIX) export LC_ALL=en_US.UTF-8 ;;
esac

# Explicit fallback for blank model canvases or NVIDIA EGL crashes. This is
# intentionally opt-in: the OpenGL 2.1 path disables newer rendering features.
if [ "${ORCA_LINUX_RENDER_COMPAT:-0}" = 1 ]; then
export __GLX_VENDOR_LIBRARY_NAME=mesa
export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
export MESA_LOADER_DRIVER_OVERRIDE=zink
export GALLIUM_DRIVER=zink
export MESA_GL_VERSION_OVERRIDE=2.1
export GDK_BACKEND=x11
export WEBKIT_DISABLE_DMABUF_RENDERER=1
fi
7 changes: 7 additions & 0 deletions scripts/run-local-linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
set -eu

orca_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
. "$orca_root/resources/linux-launch-env.sh"
export DESKTOPINTEGRATION=false
exec "$orca_root/build/src/snapmaker-orca" "$@"
12 changes: 7 additions & 5 deletions src/libslic3r/AABBTreeLines.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ namespace AABBTreeLines {
{
Vec<LineType::Dim, typename LineType::Scalar> nearest_point;
const LineType& line = lines[primitive_index];
squared_distance = line_alg::distance_to_squared(line, origin.template cast<typename LineType::Scalar>(), &nearest_point);
squared_distance = line_alg::distance_to_squared<LineType>(
line, origin.template cast<typename LineType::Scalar>(), &nearest_point);
return nearest_point.template cast<ScalarType>();
}
};
Expand Down Expand Up @@ -351,10 +352,11 @@ namespace AABBTreeLines {
return dist;
}

std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius)
{
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
}
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius)
{
// Store a vector value, not Eigen's lazy cast expression, in the distancer.
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>().eval(), radius * radius);
}

template <bool sorted>
std::vector<std::pair<Vec<2, Scalar>, size_t>> intersections_with_line(const LineType& line) const
Expand Down
6 changes: 3 additions & 3 deletions src/slic3r/GUI/HttpServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ boost::asio::ip::port_type HttpServer::find_available_port(boost::asio::ip::port
// 尝试从起始端口开始查找可用端口
for (boost::asio::ip::port_type p = start_port; p < start_port + 1000; ++p) {
if (is_port_available(p)) {
BOOST_LOG_TRIVIAL(error) << "use new port for start server:"<<p;
BOOST_LOG_TRIVIAL(info) << "Starting local resource server on port " << p;
return p;
}
}
Expand Down Expand Up @@ -769,7 +769,7 @@ void HttpServer::stop_health_check()
std::lock_guard<std::mutex> lock(m_health_check_mutex);

if (!m_health_check_enabled) {
BOOST_LOG_TRIVIAL(error) << "Health check is not running";
BOOST_LOG_TRIVIAL(debug) << "Health check is already stopped";
return;
}

Expand Down Expand Up @@ -1176,4 +1176,4 @@ void HttpServer::ResponseFile::write_response(std::stringstream& ssOut)
ssOut << fileContent; // 响应体(长度必须与Content-Length一致)
}

}} // namespace Slic3r::GUI
}} // namespace Slic3r::GUI
4 changes: 4 additions & 0 deletions src/slic3r/GUI/PartPlate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,10 @@ void PartPlate::calc_exclude_triangles(const ExPolygon &poly)
{
m_exclude_triangles.reset();

// A single 0x0 point denotes no excluded area in printer profiles.
if (poly.contour.points.size() < 3)
return;

if (!init_model_from_poly(m_exclude_triangles, poly, GROUND_Z))
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to create exclude triangles\n";
}
Expand Down
9 changes: 7 additions & 2 deletions src/slic3r/GUI/SliceModePopup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,20 @@ SliceModePopup::SliceModePopup(wxWindow *parent)
Bind(wxEVT_LEFT_UP, &SliceModePopup::on_left_up, this);
Bind(wxEVT_TIMER, &SliceModePopup::on_timer, this);
#ifdef __WXGTK__
wxGetTopLevelParent(parent)->Bind(wxEVT_ACTIVATE, &SliceModePopup::on_top_window_activate, this);
m_activation_source = wxGetTopLevelParent(parent);
if (m_activation_source)
m_activation_source->Bind(wxEVT_ACTIVATE, &SliceModePopup::on_top_window_activate, this);
#endif
update_metrics();
}

SliceModePopup::~SliceModePopup()
{
#ifdef __WXGTK__
wxGetTopLevelParent(GetParent())->Unbind(wxEVT_ACTIVATE, &SliceModePopup::on_top_window_activate, this);
// DestroyChildren() can detach the popup before its destructor runs.
// Unbind from the original emitter, if it is still alive.
if (m_activation_source)
m_activation_source->Unbind(wxEVT_ACTIVATE, &SliceModePopup::on_top_window_activate, this);
#endif
}

Expand Down
2 changes: 2 additions & 0 deletions src/slic3r/GUI/SliceModePopup.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <wx/popupwin.h>
#include <wx/timer.h>
#include <wx/weakref.h>

#include <vector>

Expand Down Expand Up @@ -41,6 +42,7 @@ class SliceModePopup : public wxPopupWindow
void on_timer(wxTimerEvent &evt);
void update_metrics();
#ifdef __WXGTK__
wxWeakRef<wxWindow> m_activation_source;
// The popup is an override-redirect window that stays on top of everything,
// so hide it when the application loses activation (mirrors Widgets/PopupWindow).
void on_top_window_activate(wxActivateEvent &evt);
Expand Down
7 changes: 5 additions & 2 deletions src/slic3r/GUI/Widgets/PopupWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@ bool PopupWindow::Create(wxWindow *parent, int style)
if (!wxPopupTransientWindow::Create(parent, style))
return false;
#ifdef __WXGTK__
GetTopParent(parent)->Bind(wxEVT_ACTIVATE, &PopupWindow::topWindowActiavate, this);
m_activation_source = GetTopParent(parent);
if (m_activation_source)
m_activation_source->Bind(wxEVT_ACTIVATE, &PopupWindow::topWindowActiavate, this);
#endif
return true;
}

PopupWindow::~PopupWindow()
{
#ifdef __WXGTK__
GetTopParent(this)->Unbind(wxEVT_ACTIVATE, &PopupWindow::topWindowActiavate, this);
if (m_activation_source)
m_activation_source->Unbind(wxEVT_ACTIVATE, &PopupWindow::topWindowActiavate, this);
#endif
}

Expand Down
2 changes: 2 additions & 0 deletions src/slic3r/GUI/Widgets/PopupWindow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define slic3r_GUI_PopupWindow_hpp_

#include <wx/popupwin.h>
#include <wx/weakref.h>

class PopupWindow : public wxPopupTransientWindow
{
Expand All @@ -17,6 +18,7 @@ class PopupWindow : public wxPopupTransientWindow

private:
#ifdef __WXGTK__
wxWeakRef<wxWindow> m_activation_source;
void topWindowActiavate(wxActivateEvent &event);
#endif
};
Expand Down
36 changes: 31 additions & 5 deletions src/slic3r/GUI/Widgets/WebView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <wx/msw/webview_edge.h>
#elif defined(__WXMAC__)
#include <wx/osx/webview_webkit.h>
#elif defined(__WXGTK__)
#include <wx/gtk/webview_webkit.h>
#endif
#include <wx/uri.h>
#if defined(__WIN32__) || defined(__WXMAC__)
Expand All @@ -30,7 +32,6 @@
#elif defined __linux__
#include <gtk/gtk.h>
#define WEBKIT_API
struct WebKitWebView;
struct WebKitJavascriptResult;
extern "C" {
WEBKIT_API void
Expand Down Expand Up @@ -291,6 +292,22 @@ class WebViewWebKit : public wxWebViewWebKit
wxString m_queuedUrl;
};

#elif defined(__WXGTK__)

class WebViewWebKit : public wxWebViewWebKit
{
public:
bool RunScript(const wxString &javascript, wxString *output = nullptr) const override
{
if (output)
return wxWebViewWebKit::RunScript(javascript, output);

// Bridge setup scripts can return native WebKit objects. There is no
// result to serialize when the caller only wants to execute a script.
return WebView::RunScript(const_cast<WebViewWebKit *>(this), javascript);
}
};

#endif

class FakeWebView : public wxWebView
Expand Down Expand Up @@ -377,6 +394,8 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
wxWebView* webView = new WebViewEdge;
#elif defined(__WXOSX__)
wxWebView *webView = new WebViewWebKit(url2);
#elif defined(__WXGTK__)
wxWebView *webView = new WebViewWebKit;
#else
auto webView = wxWebView::New();
#endif
Expand Down Expand Up @@ -491,6 +510,8 @@ void WebView::LoadUrl(wxWebView * webView, wxString const &url)

bool WebView::RunScript(wxWebView *webView, wxString const &javascript)
{
if (!webView || !webView->GetNativeBackend())
return false;
if (Slic3r::GUI::wxGetApp().app_config->get("internal_developer_mode") == "true"
&& javascript.find("studio_userlogin") == wxString::npos)
wxLogMessage("Running JavaScript:\n%s\n", javascript);
Expand All @@ -507,14 +528,19 @@ bool WebView::RunScript(wxWebView *webView, wxString const &javascript)
return true;
#else
WebKitWebView *wkWebView = (WebKitWebView *) webView->GetNativeBackend();
// Discard native message-handler objects that WebKit cannot return
// to the UI process when the caller does not request a result.
const wxString script_without_result = javascript + "\n;void 0;";
webkit_web_view_run_javascript(
wkWebView, javascript.utf8_str(), NULL,
wkWebView, script_without_result.utf8_str(), NULL,
[](GObject *wkWebView, GAsyncResult *res, void *) {
GError * error = NULL;
auto result = webkit_web_view_run_javascript_finish((WebKitWebView*)wkWebView, res, &error);
if (!result)
g_error_free (error);
else
if (error) {
wxLogWarning("Error running JavaScript: %s", wxString::FromUTF8(error->message));
g_error_free(error);
}
if (result)
webkit_javascript_result_unref (result);
}, NULL);
return true;
Expand Down
11 changes: 11 additions & 0 deletions src/slic3r/Utils/PresetUpdater.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "PresetUpdater.hpp"
#include "PrinterMetadataValidation.hpp"

#include <algorithm>
#include <boost/filesystem/operations.hpp>
Expand Down Expand Up @@ -117,6 +118,8 @@ void copy_file_fix(const fs::path &source, const fs::path &target)

struct Update
{
enum class DirectoryKind { PresetBundle, PrinterMetadata };
DirectoryKind directory_kind { DirectoryKind::PresetBundle };
fs::path source;
fs::path target;

Expand Down Expand Up @@ -167,6 +170,13 @@ struct Update
{
if (is_directory) {
auto validate_staging = [this](const fs::path &staging) -> bool {
if (directory_kind == DirectoryKind::PrinterMetadata) {
if (!validate_printer_metadata_directory(staging)) {
BOOST_LOG_TRIVIAL(error) << "Printer metadata staging is missing version.txt or model JSON files";
return false;
}
return true;
}
if (vendor == "flutter_web") {
if (!fs::exists(staging / "version.json")) {
BOOST_LOG_TRIVIAL(error) << "Web resource staging is missing version.json";
Expand Down Expand Up @@ -1702,6 +1712,7 @@ Updates PresetUpdater::priv::get_printer_config_updates(bool update) const
} catch (...) {}
}
updates.updates.emplace_back(std::move(resc_folder), std::move(config_folder), version, "bbl", change_log, version.comment, false, true);
updates.updates.back().directory_kind = Update::DirectoryKind::PrinterMetadata;
return updates;
}

Expand Down
Loading