Skip to content

Commit f0324be

Browse files
committed
Optional Vulkan preview and Linux packaging
Make the Vulkan 3D preview optional and improve Linux portable packaging. - Add meson option 'vulkan_preview' (auto/enable/disable). Detect Qt private RHI headers (qrhi.h, qshader.h) and set PAKFU_WITH_VULKAN_PREVIEW accordingly. Fail configuration if the feature is explicitly required but headers are missing. Conditionally add Vulkan source files and moc headers when enabled. - Emit PAKFU_WITH_VULKAN_PREVIEW in generated pakfu_config.h and guard preview renderer includes on that macro. - Provide lightweight QWidget stubs for bsp/model Vulkan widgets when Vulkan preview is disabled so the app can build & run with OpenGL-only preview. - Revamp scripts/package_linux.sh to produce a portable tarball from the deployed AppDir produced by linuxdeployqt, verify libQt6Core is bundled and the pakfu binary resolves Qt from the AppDir, require AppRun presence, and create a top-level pakfu launcher that runs AppRun. Fail packaging on missing Qt Core or unresolved dependencies. - Update README, docs/DEPENDENCIES.md, docs/RELEASES.md and CHANGELOG to document the portable Linux archive contents and the optional Vulkan preview behavior.
1 parent 08f0b92 commit f0324be

11 files changed

Lines changed: 231 additions & 41 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ All notable changes to PakFu are documented here.
1010
- Fix stale folder listings when navigating newly added folders in a new PAK tab by using canonical archive paths for navigation/listing.
1111
- Revise all open/save file and folder dialogs to use platform-native navigation by default, with persistent recent locations, filters, and sidebar bookmarks across sessions.
1212
- Fix potential GUI hangs when opening file dialogs on systems with slow/unreachable volumes by removing synchronous volume probing from dialog state restore.
13+
- Bundle the deployed Linux runtime into portable tarballs and fail packaging if Qt Core is missing from the deployed AppDir.
14+
- Allow distro builds without Qt private RHI headers by auto-disabling the Vulkan preview renderer and keeping the OpenGL renderer available.
1315

1416
## [0.1.18.1] - 2026-02-19
1517
### Other

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ Get PakFu from GitHub Releases:
5959
- macOS: `installer.pkg` (recommended) or `portable.zip`
6060
- Linux: `installer.AppImage` (recommended) or `portable.tar.gz`
6161

62+
The Linux portable archive is a deployed runtime tree with Qt and non-Qt
63+
runtime libraries bundled. After extracting it, run `./pakfu` from the extracted
64+
directory.
65+
6266
PakFu can also check for updates at runtime (GUI) and via CLI (`--check-updates`).
6367

6468
## Highlights
@@ -122,6 +126,11 @@ Notes:
122126
- Qt6 (Core, Gui, Network, Widgets, OpenGL, OpenGLWidgets, Multimedia, MultimediaWidgets)
123127
- Windows: `DbgHelp` (system library, used for crash reporting)
124128

129+
The Vulkan 3D preview renderer is optional at build time. If Qt private RHI
130+
headers are unavailable, Meson builds the OpenGL preview path instead; use
131+
`-Dvulkan_preview=enabled` to require Vulkan or `-Dvulkan_preview=disabled` to
132+
force the portable OpenGL-only build.
133+
125134
See `docs/DEPENDENCIES.md` for full dependency details and packaging toolchain requirements.
126135

127136
### Windows Helper Build

docs/DEPENDENCIES.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
- OpenGLWidgets (model preview: MDL/MD2/MD3/MDC/MD4/MDR/SKB/SKD/MDM/GLM/IQM/MD5/LWO/OBJ)
1414
- Multimedia (audio/video playback via available backend codecs; prefers FFmpeg when available)
1515
- MultimediaWidgets (video output: QVideoWidget)
16+
- Vulkan preview builds additionally need Qt's private RHI headers
17+
(`rhi/qrhi.h` and `rhi/qshader.h`, for example from `qt6-base-private-dev`
18+
on Debian-family distributions). When they are not available, Meson disables
19+
the Vulkan preview automatically and keeps the OpenGL preview renderer.
1620

1721
## Release Packaging Toolchain
1822
- Windows:
@@ -22,7 +26,7 @@
2226
- `macdeployqt` (Qt deployment)
2327
- `pkgbuild` (native `.pkg` installer generation)
2428
- Linux:
25-
- `linuxdeployqt` (downloaded in CI for AppImage packaging)
29+
- `linuxdeployqt` (downloaded in CI for AppImage and portable runtime packaging)
2630
- AppImage runtime tooling (`APPIMAGE_EXTRACT_AND_RUN` flow in CI)
2731

2832
## Planned / Optional

docs/RELEASES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ Required assets per platform:
4343
Current updater behavior prefers installer assets and falls back to portable
4444
archives when needed.
4545

46+
Linux portable archives are produced from the same deployed AppDir used for the
47+
AppImage, so Qt and other runtime libraries are bundled in both Linux assets.
48+
The archive includes a top-level `pakfu` launcher for extracted-directory use.
49+
4650
The release pipeline also emits:
4751
- `pakfu-<version>-release-manifest.json` (checksums + distribution metadata)
4852
- `CHANGELOG-<version>.md`

meson_options.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,10 @@ option(
1212
value: 'stable',
1313
description: 'Update channel selection.'
1414
)
15+
16+
option(
17+
'vulkan_preview',
18+
type: 'feature',
19+
value: 'auto',
20+
description: 'Build the Vulkan 3D preview renderer when Qt private RHI headers are available.'
21+
)

scripts/package_linux.sh

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,6 @@ icon_file="${app_dir}/usr/share/icons/hicolor/256x256/apps/pakfu-app.png"
5252
linuxdeployqt_tool="${out_dir}/linuxdeployqt-${linuxdeploy_arch}.AppImage"
5353

5454
rm -rf "${portable_dir}" "${portable_archive}" "${installer_appimage}" "${app_dir}"
55-
mkdir -p "${portable_dir}"
56-
cp "${binary}" "${portable_dir}/pakfu"
57-
chmod +x "${portable_dir}/pakfu"
58-
if [[ -d "${root_dir}/assets" ]]; then
59-
cp -R "${root_dir}/assets" "${portable_dir}/assets"
60-
fi
61-
tar -czf "${portable_archive}" -C "${out_dir}" "$(basename "${portable_dir}")"
62-
echo "Packaged portable archive: ${portable_archive}"
63-
6455
mkdir -p "$(dirname "${desktop_file}")" "$(dirname "${icon_file}")" "${app_dir}/usr/bin" "${app_dir}/usr/share/pakfu"
6556
cp "${binary}" "${app_dir}/usr/bin/pakfu"
6657
chmod +x "${app_dir}/usr/bin/pakfu"
@@ -93,7 +84,7 @@ if [[ ! -f "${linuxdeployqt_tool}" ]]; then
9384
chmod +x "${linuxdeployqt_tool}"
9485
fi
9586

96-
find "${out_dir}" . -maxdepth 1 -type f -name "*.AppImage" ! -name "$(basename "${linuxdeployqt_tool}")" -delete
87+
find "${out_dir}" -maxdepth 1 -type f -name "*.AppImage" ! -name "$(basename "${linuxdeployqt_tool}")" -delete
9788

9889
disabled_plugins=()
9990
restore_disabled_plugins() {
@@ -120,16 +111,55 @@ if [[ -n "${qt_plugins_dir}" && -d "${qt_plugins_dir}" ]]; then
120111
done < <(find "${qt_plugins_dir}" -type f -name "*.so" | sort)
121112
fi
122113

123-
export VERSION="${version}"
124-
APPIMAGE_EXTRACT_AND_RUN=1 "${linuxdeployqt_tool}" \
125-
"${desktop_file}" \
126-
-qmake="${qmake_bin}" \
127-
-unsupported-allow-new-glibc \
128-
-no-copy-copyright-files \
129-
-bundle-non-qt-libs \
130-
-appimage
114+
(
115+
cd "${out_dir}"
116+
export VERSION="${version}"
117+
APPIMAGE_EXTRACT_AND_RUN=1 "./$(basename "${linuxdeployqt_tool}")" \
118+
"PakFu.AppDir/usr/share/applications/pakfu.desktop" \
119+
-qmake="${qmake_bin}" \
120+
-unsupported-allow-new-glibc \
121+
-no-copy-copyright-files \
122+
-bundle-non-qt-libs \
123+
-appimage
124+
)
125+
126+
if [[ ! -e "${app_dir}/usr/lib/libQt6Core.so.6" ]]; then
127+
echo "linuxdeployqt did not bundle libQt6Core.so.6 into ${app_dir}." >&2
128+
exit 1
129+
fi
130+
131+
pakfu_ldd="$(ldd "${app_dir}/usr/bin/pakfu" 2>/dev/null || true)"
132+
if printf "%s\n" "${pakfu_ldd}" | grep -q "not found"; then
133+
echo "Packaged pakfu binary still has unresolved runtime dependencies:" >&2
134+
printf "%s\n" "${pakfu_ldd}" >&2
135+
exit 1
136+
fi
137+
138+
app_dir_abs="$(cd "${app_dir}" && pwd)"
139+
if ! printf "%s\n" "${pakfu_ldd}" | grep -F "libQt6Core.so.6 => ${app_dir_abs}/" >/dev/null; then
140+
echo "Packaged pakfu binary is not resolving Qt Core from the deployed AppDir." >&2
141+
printf "%s\n" "${pakfu_ldd}" >&2
142+
exit 1
143+
fi
144+
145+
if [[ ! -x "${app_dir}/AppRun" ]]; then
146+
echo "linuxdeployqt did not create an executable AppRun launcher in ${app_dir}." >&2
147+
exit 1
148+
fi
149+
150+
mkdir -p "${portable_dir}"
151+
cp -a "${app_dir}/." "${portable_dir}/"
152+
cat > "${portable_dir}/pakfu" <<'LAUNCHER'
153+
#!/usr/bin/env sh
154+
set -eu
155+
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
156+
exec "$HERE/AppRun" "$@"
157+
LAUNCHER
158+
chmod +x "${portable_dir}/pakfu"
159+
tar -czf "${portable_archive}" -C "${out_dir}" "$(basename "${portable_dir}")"
160+
echo "Packaged portable archive: ${portable_archive}"
131161

132-
generated_appimage="$(find "${out_dir}" . -maxdepth 1 -type f -name "*.AppImage" ! -name "$(basename "${linuxdeployqt_tool}")" | head -n1)"
162+
generated_appimage="$(find "${out_dir}" -maxdepth 1 -type f -name "*.AppImage" ! -name "$(basename "${linuxdeployqt_tool}")" | sort | head -n1)"
133163
if [[ -z "${generated_appimage}" ]]; then
134164
echo "linuxdeployqt did not produce an AppImage." >&2
135165
exit 1

src/meson.build

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@ conf.set('PAKFU_UPDATE_CHANNEL', get_option('update_channel'))
55

66
cpp_compiler = meson.get_compiler('cpp')
77

8-
configure_file(
9-
input: 'pakfu_config.h.in',
10-
output: 'pakfu_config.h',
11-
configuration: conf,
12-
)
13-
148
miniz_lib = static_library(
159
'pakfu_miniz',
1610
files(
@@ -30,8 +24,10 @@ qt_libs = qt6_dep.get_variable(configtool: 'QT_INSTALL_LIBS', default_value: '')
3024
qt_version = qt6_dep.get_variable(configtool: 'QT_VERSION', default_value: '')
3125

3226
fs = import('fs')
27+
vulkan_preview_opt = get_option('vulkan_preview')
3328
qt_rhi_includes = []
3429
qt_rhi_candidates = []
30+
qt_rhi_headers_found = false
3531
if qt_headers != '' and qt_version != ''
3632
qt_rhi_candidates += join_paths(qt_headers, 'QtGui', qt_version, 'QtGui')
3733
qt_rhi_candidates += join_paths(qt_headers, 'QtGui.framework', 'Headers', qt_version, 'QtGui')
@@ -50,12 +46,32 @@ if qt_libs != ''
5046
endif
5147

5248
foreach path : qt_rhi_candidates
53-
if fs.exists(join_paths(path, 'rhi', 'qshader.h'))
49+
if fs.exists(join_paths(path, 'rhi', 'qrhi.h')) and fs.exists(join_paths(path, 'rhi', 'qshader.h'))
50+
qt_rhi_headers_found = true
5451
qt_rhi_includes += include_directories(path)
5552
break
5653
endif
5754
endforeach
5855

56+
if vulkan_preview_opt.enabled() and not qt_rhi_headers_found
57+
error('Vulkan preview requires Qt private RHI headers (rhi/qrhi.h and rhi/qshader.h). Install the Qt private development headers or configure with -Dvulkan_preview=disabled.')
58+
endif
59+
60+
vulkan_preview_enabled = qt_rhi_headers_found and not vulkan_preview_opt.disabled()
61+
conf.set('PAKFU_WITH_VULKAN_PREVIEW', vulkan_preview_enabled ? 1 : 0)
62+
63+
if vulkan_preview_enabled
64+
message('Vulkan preview renderer enabled.')
65+
else
66+
message('Vulkan preview renderer disabled; OpenGL preview remains available.')
67+
endif
68+
69+
configure_file(
70+
input: 'pakfu_config.h.in',
71+
output: 'pakfu_config.h',
72+
configuration: conf,
73+
)
74+
5975
pakfu_includes = [include_directories('.')] + qt_rhi_includes
6076

6177
sources = files(
@@ -111,9 +127,7 @@ sources = files(
111127
'ui/cinematic_player_widget.cpp',
112128
'ui/video_player_widget.cpp',
113129
'ui/bsp_preview_widget.cpp',
114-
'ui/bsp_preview_vulkan_widget.cpp',
115130
'ui/model_viewer_widget.cpp',
116-
'ui/model_viewer_vulkan_widget.cpp',
117131
'ui/shader_viewer_widget.cpp',
118132
'ui/preview_pane.cpp',
119133
'ui/pak_tab.cpp',
@@ -124,23 +138,40 @@ sources = files(
124138
'update/update_service.cpp',
125139
)
126140

141+
if vulkan_preview_enabled
142+
sources += files(
143+
'ui/bsp_preview_vulkan_widget.cpp',
144+
'ui/model_viewer_vulkan_widget.cpp',
145+
)
146+
endif
147+
127148
rcc_sources = qt6.preprocess(
128149
qresources: ['resources.qrc'],
129150
)
130151

131-
moc_sources = qt6.preprocess(
132-
moc_headers: [
133-
'update/update_service.h',
134-
'ui/splash_screen.h',
135-
'ui/breadcrumb_bar.h',
136-
'ui/cinematic_player_widget.h',
137-
'ui/video_player_widget.h',
152+
moc_headers = [
153+
'update/update_service.h',
154+
'ui/splash_screen.h',
155+
'ui/breadcrumb_bar.h',
156+
'ui/cinematic_player_widget.h',
157+
'ui/video_player_widget.h',
158+
'ui/preview_pane.h',
159+
'ui/pak_tab.h',
160+
'ui/preferences_tab.h',
161+
]
162+
moc_extra_arguments = []
163+
164+
if vulkan_preview_enabled
165+
moc_headers += [
138166
'ui/bsp_preview_vulkan_widget.h',
139167
'ui/model_viewer_vulkan_widget.h',
140-
'ui/preview_pane.h',
141-
'ui/pak_tab.h',
142-
'ui/preferences_tab.h',
143-
],
168+
]
169+
moc_extra_arguments += ['-DPAKFU_WITH_VULKAN_PREVIEW=1']
170+
endif
171+
172+
moc_sources = qt6.preprocess(
173+
moc_headers: moc_headers,
174+
moc_extra_arguments: moc_extra_arguments,
144175
)
145176
sources += rcc_sources
146177
sources += moc_sources

src/pakfu_config.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
#define PAKFU_VERSION "@PAKFU_VERSION@"
44
#define PAKFU_GITHUB_REPO "@PAKFU_GITHUB_REPO@"
55
#define PAKFU_UPDATE_CHANNEL "@PAKFU_UPDATE_CHANNEL@"
6+
#define PAKFU_WITH_VULKAN_PREVIEW @PAKFU_WITH_VULKAN_PREVIEW@

src/ui/bsp_preview_vulkan_widget.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#pragma once
22

3+
#include "pakfu_config.h"
4+
5+
#if PAKFU_WITH_VULKAN_PREVIEW
6+
37
#include <QtWidgets/qrhiwidget.h>
48
#include <QColor>
59
#include <QElapsedTimer>
@@ -212,3 +216,33 @@ class BspPreviewVulkanWidget final : public QRhiWidget {
212216
bool pipeline_dirty_ = true;
213217
bool uniform_dirty_ = true;
214218
};
219+
220+
#else
221+
222+
#include <QColor>
223+
#include <QHash>
224+
#include <QImage>
225+
#include <QString>
226+
#include <QWidget>
227+
228+
#include "formats/bsp_preview.h"
229+
#include "ui/preview_3d_options.h"
230+
231+
class BspPreviewVulkanWidget final : public QWidget {
232+
public:
233+
explicit BspPreviewVulkanWidget(QWidget* parent = nullptr) : QWidget(parent) {}
234+
~BspPreviewVulkanWidget() override = default;
235+
236+
void set_mesh(BspMesh, QHash<QString, QImage>) {}
237+
void set_lightmap_enabled(bool) {}
238+
void set_grid_mode(PreviewGridMode) {}
239+
void set_background_mode(PreviewBackgroundMode, const QColor&) {}
240+
void set_wireframe_enabled(bool) {}
241+
void set_textured_enabled(bool) {}
242+
void set_fov_degrees(int) {}
243+
[[nodiscard]] PreviewCameraState camera_state() const { return {}; }
244+
void set_camera_state(const PreviewCameraState&) {}
245+
void clear() {}
246+
};
247+
248+
#endif

0 commit comments

Comments
 (0)