Skip to content

Commit 184cb98

Browse files
committed
Qt GUI overhaul: live reload, undo/redo, test suite, crash reporting
Headline features - Live Reload From Source (QFileSystemWatcher, captured SVTFCreateOptions + retune dialog; also watches parent directory for atomic-save editors). - External VTF change detection with clean/dirty divergence handling plus Shift+F5 force-reload. - Command palette (Ctrl+Shift+P) with subsequence fuzzy matching and persistent recency + use-count scoring. - Non-modal toast overlay; clickable variant with pointer-hover pause. - Undo/redo backed by QUndoStack: flags, minor version, start frame, bumpmap scale, reflectivity, and compute-reflectivity. Properties multi-field saves grouped as a single macro. - Equirectangular HDRI -> cubemap (Tools menu; CPU bilinear sampler; 256/512/1024/2048 face sizes). - Lit Normal + Mip Diff channel-view modes; per-mip PSNR stats in VTF Properties dialog. - Export presets (preset bar on Create dialog: Save As / Delete; 20+ fields persisted in QSettings). - Per-file view memory (zoom, fit, cubemap face); reopen-last-session + auto-fit-on-open options. - Extended image import: PNG/JPG/BMP/TIFF/WebP/GIF/PPM/PGM/PBM (Qt) plus TGA, HDR, PSD, PIC (stb_image), OpenEXR (tinyexr), QOI (qoi.h). Animated GIF/APNG auto-expand into Animated-VTF frames. - Crash reporting: async-signal-safe POSIX handler / SetUnhandledException filter on Windows, writes to <AppDataLocation>/crashes/ with backtrace. Next startup surfaces new reports via a clickable toast. VTFLib fixes and extensions - Kaiser/Gaussian/Point mipmap filter crash fixed: replaced the stbir_filter(MipmapFilter) cast (which produced out-of-range stbir enum values) with an explicit VTFFilterToStbirFilter mapping applied at all three stbir_resize call sites. - New MIPMAP_FILTER_NICE: Valve-style half-pixel box filter that forces STBIR_TYPE_UINT8_SRGB in mipmap generation regardless of the caller's bSRGB flag. Exposed via vtfcmd -mfilter nice and the Qt dialog. - CVTFFile::Load now detects byte-swapped headers (console VTFs, Xbox 360 / PS3) and future VTF 7.6+ / compressed-body variants, failing with specific errors instead of generic corruption. - Fix -Wswitch warning by handling RESIZE_COUNT sentinel. VTFCmd - New -exportpath <file> flag for shell thumbnailers that pass an exact destination path. Only honored without -extract-all-*. Test suite + CI - 14 CTest scenarios: three formats (RGBA8888/BGRA8888/RGB888), a -nomipmaps variant, eight mipmap filters (box/triangle/catrom/mitchell/ kaiser/point/gaussian/nice), an animated round-trip with per-frame diff, and a 6-face cubemap round-trip. Header magic + dimensions + flags validated; PNG output pixel-diffed via new imgdiff helper (PSNR >= 20 dB gate, fully-transparent pixels ignored). - New imgdiff test helper (stb_image-based). - New mkanimvtf test helper (vlImageCreateMultiple wrapper; --cube flag for 6-face env maps). - CI core job runs ctest on every push across Ubuntu/Windows/macOS. - CI qt-gui job runs a headless vtfeditqt --version / --help smoke test. - New release job: tag-triggered (v*), downloads all platform artifacts, packages them with versioned names (e.g. QTFEdit-3.5.0-linux-x86_64.zip), drafts a GitHub Release with CHANGELOG.md as the body. - vtfeditqt gained --version / --help that short-circuit before QApplication so the smoke step needs no platform plugin. Shell integration - Linux: packaging/linux/vtf.thumbnailer + vtf-mime.xml; cmake --install deploys them to share/thumbnailers and share/mime/packages. - Windows: packaging/windows/vtf-assoc.reg + uninstaller; registers .vtf/.vmt under HKCU (no admin). - macOS: Info.plist.in with CFBundleDocumentTypes + UTExportedTypeDeclarations; QFileOpenEvent filter in main.cpp. Version - Project version bumped 3.0.0 -> 3.5.0. main.cpp --version now reads QTFEDIT_VERSION from CMake via a compile definition so the string won't drift out of sync with the project version again. Cleanup - Removed unused applyExposure8bit and leadingSpacesCount in MainWindow. - Expanded .gitignore for macOS/Linux/Windows metadata, editor swap files, clang-tidy/clangd caches, CTest scratch, coverage/profiling artifacts, core dumps, alternate build directories. Docs - docs/parity.md: VTFEdit Reloaded parity remains 100%; new Beyond-Parity section covers every feature above. - docs/roadmap.md: forward roadmap covering big-ticket items (Strata compressed VTF read/write, Xbox 360 / PS3 console VTF read) plus shell-integration, Qt GUI quick/medium/ambitious tiers, and infrastructure work. - README.md: version header v3.0 -> v3.5, expanded feature bullets, Testing section, short Roadmap highlighting the two headline items with a link to docs/roadmap.md for the full catalogue, release flow. - CHANGELOG.md: full inventory of the 3.5.0 arc.
1 parent 8f5fca0 commit 184cb98

50 files changed

Lines changed: 14928 additions & 616 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ jobs:
3232
- name: Build
3333
run: cmake --build build --config Release
3434

35+
- name: Test
36+
working-directory: build
37+
run: ctest --output-on-failure -C Release
38+
3539
- name: Upload artifacts
3640
uses: actions/upload-artifact@v4
3741
with:
@@ -98,6 +102,31 @@ jobs:
98102
- name: Build
99103
run: cmake --build build-qt --config Release
100104

105+
- name: Smoke test (Linux/macOS)
106+
if: runner.os != 'Windows'
107+
shell: bash
108+
env:
109+
QT_QPA_PLATFORM: offscreen
110+
run: |
111+
if [ "$RUNNER_OS" = "macOS" ]; then
112+
./build-qt/VTFEdit/vtfeditqt.app/Contents/MacOS/vtfeditqt --version
113+
./build-qt/VTFEdit/vtfeditqt.app/Contents/MacOS/vtfeditqt --help
114+
else
115+
./build-qt/VTFEdit/vtfeditqt --version
116+
./build-qt/VTFEdit/vtfeditqt --help
117+
fi
118+
119+
- name: Smoke test (Windows)
120+
if: runner.os == 'Windows'
121+
shell: pwsh
122+
env:
123+
QT_QPA_PLATFORM: offscreen
124+
run: |
125+
& .\build-qt\VTFEdit\Release\vtfeditqt.exe --version
126+
if ($LASTEXITCODE -ne 0) { throw "vtfeditqt --version failed with $LASTEXITCODE" }
127+
& .\build-qt\VTFEdit\Release\vtfeditqt.exe --help
128+
if ($LASTEXITCODE -ne 0) { throw "vtfeditqt --help failed with $LASTEXITCODE" }
129+
101130
- name: Install (stage)
102131
run: cmake --install build-qt --config Release --prefix dist
103132

@@ -245,4 +274,53 @@ jobs:
245274
if-no-files-found: error
246275
compression-level: 9
247276
path: |
248-
legacy/sln/vs2019/Build/**
277+
legacy/sln/vs2019/Build/**
278+
279+
release:
280+
name: Publish GitHub Release
281+
needs: [core, qt-gui, winforms-windows]
282+
if: startsWith(github.ref, 'refs/tags/v')
283+
runs-on: ubuntu-latest
284+
permissions:
285+
contents: write
286+
287+
steps:
288+
- uses: actions/checkout@v4
289+
290+
- name: Download all artifacts
291+
uses: actions/download-artifact@v4
292+
with:
293+
path: artifacts
294+
295+
- name: Package per-platform bundles
296+
shell: bash
297+
run: |
298+
set -euo pipefail
299+
cd artifacts
300+
version="${GITHUB_REF_NAME#v}"
301+
mkdir -p ../release-assets
302+
for d in */; do
303+
name="${d%/}"
304+
case "$name" in
305+
vtfeditqt-ubuntu-latest) out="QTFEdit-${version}-linux-x86_64" ;;
306+
vtfeditqt-windows-latest) out="QTFEdit-${version}-windows-x86_64" ;;
307+
vtfeditqt-macos-latest) out="QTFEdit-${version}-macos" ;;
308+
core-ubuntu-latest) out="VTFLib-VTFCmd-${version}-linux-x86_64" ;;
309+
core-windows-latest) out="VTFLib-VTFCmd-${version}-windows-x86_64" ;;
310+
core-macos-latest) out="VTFLib-VTFCmd-${version}-macos" ;;
311+
vtfedit-winforms-windows-latest) out="VTFEdit-Reloaded-${version}-winforms-windows-x86_64" ;;
312+
*) out="${name}-${version}" ;;
313+
esac
314+
(cd "$d" && zip -r "../../release-assets/${out}.zip" .)
315+
done
316+
echo "---"
317+
ls -la ../release-assets
318+
319+
- name: Publish release (draft)
320+
uses: softprops/action-gh-release@v2
321+
with:
322+
files: release-assets/*.zip
323+
body_path: CHANGELOG.md
324+
draft: true
325+
fail_on_unmatched_files: true
326+
generate_release_notes: false

.gitignore

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,5 +337,111 @@ ASALocalRun/
337337
# NVidia Nsight GPU debugger configuration file
338338
*.nvuser
339339

340-
# MFractors (Xamarin productivity tool) working folder
340+
# MFractors (Xamarin productivity tool) working folder
341341
.mfractor/
342+
343+
# -------------------------------------------------------------------
344+
# QTFEdit project additions (cross-platform, editors, tooling, CTest)
345+
# -------------------------------------------------------------------
346+
347+
# macOS metadata
348+
.DS_Store
349+
.AppleDouble
350+
.LSOverride
351+
._*
352+
.Spotlight-V100
353+
.Trashes
354+
.TemporaryItems
355+
.VolumeIcon.icns
356+
.fseventsd/
357+
.com.apple.timemachine.donotpresent
358+
Icon?
359+
# Catch nested .DS_Store files too (e.g. legacy/.DS_Store, thirdparty/.DS_Store).
360+
**/.DS_Store
361+
362+
# Linux
363+
*~
364+
.fuse_hidden*
365+
.Trash-*
366+
.nfs*
367+
.directory
368+
369+
# Windows
370+
Thumbs.db
371+
Thumbs.db:encryptable
372+
ehthumbs.db
373+
ehthumbs_vista.db
374+
Desktop.ini
375+
$RECYCLE.BIN/
376+
377+
# Editor swap/backup
378+
*.swp
379+
*.swo
380+
*.swn
381+
.*.sw?
382+
*.orig
383+
*.rej
384+
*.bak
385+
386+
# Clang/clangd/ccls tooling caches (override the `!*.[Cc]ache/` rule above)
387+
.cache/
388+
.clangd/
389+
.ccls-cache/
390+
compile_flags.txt
391+
392+
# Qt Creator per-user project state
393+
CMakeLists.txt.user
394+
CMakeLists.txt.user.*
395+
*.qmake.stash
396+
*.pro.user
397+
*.pro.user.*
398+
399+
# VS Code / Xcode / Sublime / other editors
400+
.vscode/
401+
!.vscode/extensions.json
402+
!.vscode/settings.shared.json
403+
xcuserdata/
404+
*.xcworkspace/
405+
*.xcuserstate
406+
*.sublime-workspace
407+
*.sublime-project
408+
409+
# CTest scratch + artifacts that CMake drops into the source tree if ctest is
410+
# ever invoked from the wrong directory (build/ is preferred).
411+
/Testing/
412+
**/Testing/
413+
CTestTestfile.cmake
414+
DartConfiguration.tcl
415+
416+
# Coverage / profiling
417+
*.gcda
418+
*.gcno
419+
*.gcov
420+
coverage/
421+
*.profdata
422+
*.profraw
423+
perf.data
424+
perf.data.old
425+
426+
# Core dumps
427+
core
428+
core.*
429+
vgcore.*
430+
431+
# Accidental Python venvs
432+
venv/
433+
.venv/
434+
env/
435+
__pycache__/
436+
437+
# Accidental app bundles inside the source tree (real builds land in build/)
438+
*.app/
439+
440+
# Crash-report outputs written by the tool during dev runs inside the tree
441+
crashes/
442+
443+
# Build-tree fallback: anything under build/, build-qt/ etc. is already covered
444+
# by the [Bb]uild/ pattern above; this is just defensive for alternate names.
445+
out/
446+
cmake-build-*/
447+

CHANGELOG.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Changelog
2+
3+
## 3.5.0 — 2026-04-24
4+
5+
### Qt GUI (QTFEdit)
6+
7+
- **Live Reload From Source** (Tools menu, opt-in): when a VTF was created by importing an image, `QFileSystemWatcher` re-encodes and overwrites the VTF in place whenever the source changes. Captures the original `SVTFCreateOptions`, texture type, and alpha-format choice. Directory watching catches editors that atomic-save (write-tmp + rename). `Tools → Retune Live Source Options…` re-runs the Create dialog mid-session.
8+
- **External VTF change detection** (always on): the open `.vtf` is watched on disk. Clean buffers auto-reload silently (preserving frame/face/slice/mip selection); dirty buffers get a status-bar warning. A 2-second self-save window suppresses feedback loops.
9+
- **Discard Changes & Reload** (Shift+F5).
10+
- **Command palette** (Ctrl+Shift+P) — fuzzy subsequence matching with start-of-string / word-boundary / consecutive-char bonuses; persistent recency + use-count scoring (3-day exp-decay half-life).
11+
- **Lit Normal** channel-view mode — Lambertian shading over RG-as-normal with a fixed directional light.
12+
- **Mip Diff** channel-view mode — heatmap of `|current mip − upscale(next-coarser mip)|`.
13+
- **Mip quality stats** in the VTF Properties dialog — per-mip PSNR column companion to Mip Diff.
14+
- **Non-modal Toast** overlay for info / warn / error status messages, supersedes most `QMessageBox::information` calls; repositions on window resize; supports a clickable variant with pointer-hover pause.
15+
- **Undo / redo** (Ctrl+Z / Ctrl+Shift+Z) backed by `QUndoStack`. Covers VTF flags, minor version, start frame, bumpmap scale, reflectivity, and computed-reflectivity. Properties-dialog multi-field saves grouped as a single macro. Stack cleared on file close / open.
16+
- **Equirectangular HDRI → cubemap** (`Tools → Create Cubemap From HDRI…`) — CPU bilinear sampler into a 6-face Env-Map VTF at 256 / 512 / 1024 / 2048 per face.
17+
- **Export presets** in the Create VTF dialog (preset bar with combo + Save As… + Delete). Persists 20+ fields: format, mipmap filter, version, resize config, sRGB/gamma, full flag mask.
18+
- **Per-file memory**: zoom level, fit-to-window state, and cubemap face are remembered per VTF.
19+
- **Reopen last session on startup** (opt-in).
20+
- **Auto-fit preview on open** (opt-in).
21+
- **Extended image import**: PNG/JPG/BMP/TIFF/WebP/GIF/PPM/PGM/PBM via Qt; **TGA** / Radiance **HDR** / Photoshop **PSD** / Softimage **PIC** via bundled `stb_image`; **OpenEXR** via tinyexr; **QOI** via `qoi.h`. Animated **GIF** / **APNG** auto-expand into Animated-VTF frames.
22+
- **Crash reporting** — async-signal-safe POSIX handler (or `SetUnhandledExceptionFilter` on Windows) writes a minimal report with backtrace to `<AppDataLocation>/crashes/crash-<epochms>.txt`. On next startup, any new reports surface as a **clickable** non-modal toast that opens the folder via `QDesktopServices`.
23+
- **Qt GUI `--version` / `--help`** flags short-circuit before `QApplication` so the binary can be smoke-tested headlessly without a platform plugin.
24+
- **macOS file associations**: `CFBundleDocumentTypes` + `UTExportedTypeDeclarations` in a custom `Info.plist.in`; `main.cpp` installs a `QFileOpenEvent` filter for Finder "Open With".
25+
26+
### VTFLib
27+
28+
- **Kaiser / Gaussian / Point / etc. mipmap filter crash fixed**: `stbir_filter(MipmapFilter)` was a plain C-style cast across two incompatible enums. Out-of-range values past `MIPMAP_FILTER_MITCHELL` tripped an internal `stbir__perform_build` assertion at mipmap-generation time. Replaced with a `VTFFilterToStbirFilter` helper that explicitly maps each VTFLib filter to the closest stbir equivalent (MITCHELL for the windowed-sinc family, TRIANGLE for Gaussian/Bessel, etc.). Applied to all three `stbir_resize` call sites.
29+
- **`MIPMAP_FILTER_NICE`** — new enum value for a Valve-style half-pixel box filter that forces `STBIR_TYPE_UINT8_SRGB` in the mipmap generation path regardless of the caller's `bSRGB` flag. Exposed via `vtfcmd -mfilter nice` and the Qt Create dialog's combo (**"Nice (sRGB-aware box)"**).
30+
- **Clearer errors for unsupported VTF variants**`CVTFFile::Load` now detects byte-swapped headers (big-endian console VTFs — Xbox 360 / PS3) and future Strata minor versions, and fails with a specific error pointing at `docs/roadmap.md`.
31+
- Fixed `-Wswitch` warning in the resize-method switch by handling the `RESIZE_COUNT` sentinel explicitly.
32+
33+
### VTFCmd
34+
35+
- **`-exportpath <file>`**: single-output path flag for tools (like shell thumbnailers) that need a specific destination file. Only honoured without `-extract-all-*` to prevent overwriting the same file N times.
36+
37+
### Testing + CI
38+
39+
- **CTest round-trip suite**: 14 scenarios — three formats (RGBA8888 / BGRA8888 / RGB888), a `-nomipmaps` variant, eight mipmap-filter variants (box / triangle / catrom / mitchell / kaiser / point / gaussian / nice), an animated round-trip with per-frame diff, and a 6-face cubemap round-trip. Each test validates VTF header fields (magic, width/height, Frames field for animated, `TEXTUREFLAGS_ENVMAP` for cubemap) and pixel-diffs the decoded PNG via the bundled `imgdiff` helper (PSNR ≥ 20 dB gate, fully-transparent pixels ignored).
40+
- New **`imgdiff`** test helper (stb_image-based pixel diff).
41+
- New **`mkanimvtf`** test helper — builds multi-frame or cubemap VTFs via `vlImageCreateMultiple` (vtfcmd's `-file` flag can't do that directly). `--cube` flag for 6-face env maps.
42+
- **CI**: `core` job runs `ctest` on every push (Ubuntu / Windows / macOS). `qt-gui` job runs `vtfeditqt --version` + `--help` as a headless smoke test on all three platforms.
43+
44+
### Shell integration
45+
46+
- **Linux** (freedesktop): `packaging/linux/vtf.thumbnailer` + `vtf-mime.xml`. `cmake --install` deploys them to `share/thumbnailers` and `share/mime/packages`. `.vtf` files get thumbnails in Nautilus / Nemo / Thunar / Caja / PCManFM.
47+
- **Windows**: `packaging/windows/vtf-assoc.reg` + uninstaller. Registers `.vtf` / `.vmt` with QTFEdit ProgIDs under `HKEY_CURRENT_USER` (no admin).
48+
- **macOS**: file associations already shipped via `Info.plist.in`; Finder thumbnails still need a Quick Look app extension (not shipped).
49+
50+
### Cleanup
51+
52+
- Removed unused `applyExposure8bit` and `leadingSpacesCount` functions from `MainWindow.cpp`.
53+
54+
### Docs
55+
56+
- `docs/parity.md` — VTFEdit Reloaded parity checklist now with a "Beyond Parity (Qt-only enhancements)" section covering every item above.
57+
- `docs/roadmap.md` — detailed roadmap covering the big-ticket additions (Strata compressed VTF, console VTF read for Xbox 360 / PS3), shell integration follow-ups, Qt GUI quick / medium / ambitious items, and infrastructure work.
58+
- `README.md` — expanded feature bullets; added Testing, Roadmap (Pending), and packaging sections; points at both `docs/*.md`.
59+
- `packaging/linux/README.md` and `packaging/windows/README.md` — step-by-step install/uninstall.

CMakeLists.txt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,35 @@ endif()
1313

1414
# QTFEdit (Qt) is the primary GUI going forward. The legacy WinForms GUI and
1515
# Visual Studio solutions live under `legacy/` and are frozen.
16-
project(QTFEdit VERSION 3.0.0 LANGUAGES C CXX)
16+
project(QTFEdit VERSION 3.5.0 LANGUAGES C CXX)
1717

1818
option(VTFLIB_USE_COMPRESSONATOR "Link against AMD Compressonator if available" ON)
1919
option(BUILD_VTFCMD "Build VTFCmd CLI" ON)
2020
option(BUILD_VTFEDIT_QT "Build cross-platform Qt GUI (QTFEdit)" OFF)
2121

22+
enable_testing()
23+
2224
add_subdirectory(VTFLib)
2325
if(BUILD_VTFCMD)
2426
add_subdirectory(VTFCmd)
2527
endif()
2628
if(BUILD_VTFEDIT_QT)
2729
add_subdirectory(VTFEdit)
2830
endif()
31+
32+
# Linux shell integration: ship a freedesktop thumbnailer + MIME-type XML when installing.
33+
# These are harmless on other platforms because `UNIX AND NOT APPLE` gates the install rule.
34+
if(UNIX AND NOT APPLE)
35+
install(FILES
36+
${CMAKE_CURRENT_SOURCE_DIR}/packaging/linux/vtf.thumbnailer
37+
DESTINATION share/thumbnailers
38+
COMPONENT shell_integration
39+
OPTIONAL
40+
)
41+
install(FILES
42+
${CMAKE_CURRENT_SOURCE_DIR}/packaging/linux/vtf-mime.xml
43+
DESTINATION share/mime/packages
44+
COMPONENT shell_integration
45+
OPTIONAL
46+
)
47+
endif()

0 commit comments

Comments
 (0)