A new pilot, or one whose last ship isn't in the current mod, has no last
flown ship. create_default_player_ship() then shows a popup and returns an
error; release builds carry on with the mission's own player ship, but
debug builds asserted. Drop the assert so both builds behave the same.
When a destroyed subsystem was repaired, its submodels were only restored if the repair was done through a SEXP. A subsystem repaired by a support ship, by automatic repair, by a script, or through the debug console regained its hitpoints but not its submodels, so a ship could have a working turret with no model. Any -destroyed form also remained visible.
All of these now call restore_subsystem_submodels, which brings back the subsystem's submodels and hides the -destroyed submodel linked to a special-point subsystem. The -destroyed form of an ordinary submodel is hidden when the submodels are next replicated. The SEXP code uses the same function, with no change in its behavior.
Destruction outside do_subobj_destroyed_stuff now goes through a new blow_off_subsystem_submodels function, which do_subobj_destroyed_stuff also uses. It blows off both of the subsystem's submodels and shows any linked -destroyed submodel, unless the subsystem has "no disappear". This handles the cases where a subsystem appears destroyed without going through do_subobj_destroyed_stuff:
- the subsystem is destroyed at the start of the mission
- the subsystem was destroyed in the previous mission of a red-alert sequence
- the ship changes class while the subsystem's submodels are blown off
Previously the first two blew off only the first submodel, ignored "no disappear" for it, and never showed a linked -destroyed submodel; the third did nothing. For red alert, subsystems with no hitpoints at all are not treated as destroyed. For a class change, the visible state is carried over rather than inferred from hitpoints, so a subsystem set to zero without being blown up, or repaired without its submodels, keeps its appearance.
Red alert also restores the submodels of a subsystem that the mission file starts destroyed if the previous mission's data gives it hitpoints. Ship-copy-damage now shows or hides the -destroyed submodel linked to a special-point subsystem to match the copied state.
As before, the link between special-point subsystems and -destroyed submodels requires "$Link special-point subsystems to -destroyed submodels:" in game_settings.tbl.
The multiplayer hitpoint-update path is not changed. A client receives subsystem hitpoints separately from the notification that a subsystem was destroyed, so a hitpoint update sent just before the destruction could arrive after it and wrongly restore the submodels.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Neither editor had a teardown to match FSO's game_shutdown(), so the engine
data they set up was never freed. Add editor_free_engine_data() to free it,
and call it from a new fred_shutdown() in FRED and from qtFRED's shutdown(),
before the graphics system closes.
Also:
- Delete FRED's modeless editors if they are still open at exit.
- Make FRED's dialog tooltips and sexp tree image lists members, since
they were leaked or depended on OnDestroy() being called.
- Parent qtFRED's item models so they are deleted with their widgets.
- Enable the CRT leak report in qtFRED, which previously had none.
- In qtFRED, call shutdown() last in closeEvent(), since it destroys the
window.
- Make the preferences path and user SID strings function-level statics,
so MFC's leak dump no longer reports them as leaks.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Report leaks after static destruction via _CRTDBG_LEAK_CHECK_DF instead of calling _CrtDumpMemoryLeaks() at the end of main(), which incorrectly reported every global container as a memory leak.
Also fix the three remaining real leaks which were discovered after all the noise was cleared up:
- Mc_point_list was never freed, because its atexit cleanup was only registered by code that no longer runs. But the list was only a temporary array of pointers used while building the collision tree, so remove it and have model_collide_parse_bsp_defpoints() copy the vertices directly into the tree's point list.
- model_batch_buffer's Mem_alloc copy was never freed. But the copy is actually not necessary, so remove it and pass Submodel_matrices directly.
- Goal_target_names strings were only freed when the next mission was parsed, so the last mission's names leaked at exit. Hold them in SCP_vm_unique_ptr so they are freed with the vector.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Cockpit_active was latched inside ship_render_player_ship(), which only
runs when Viewer_obj is non-null. External, chase, warp-chase, topdown,
free-camera and dead views all null Viewer_obj, so the flag froze at its
last cockpit-view value and cockpitinactive HUD gauges vanished. It was
also read before being written each frame (by the show-ship check and the
shadow helpers), so those decisions lagged a frame on any transition.
Replace it with three functions computed on demand:
- ship_cockpit_enabled(sip): the ship has a cockpit model and cockpits
aren't disabled; used by the Show_ship_only_if_cockpits_enabled checks
- ship_render_player_cockpit(sip): the above, outside topdown view;
replaces three duplicated copies of that expression
- ship_render_player_cockpit_active(): whether the viewer's cockpit is
being rendered this frame; used by the HUD cockpit-view-choice checks
With the predicate correct, the VM_CHASE special cases added for #4804
are no longer needed and are removed. In chase view, cockpitinactive
gauges now also respect pop-up state, No_ets, and setRenderOverride,
which the early return in HudGauge::canRender() used to bypass.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
FRED and qtFRED no longer overwrite a hand-edited arrival cue on the wing
containing the player start. The cue is editable when the wing has multiple
waves (where it governs the arrival of subsequent waves) and read-only
otherwise. Waves and threshold are now editable for the single-player
player wing, while the arrival delay remains locked; the arrival location
is editable in both editors.
Both error checkers flag a single-wave player wing with a custom cue as a
potential issue, and the engine now warns and aborts the mission load,
rather than tripping an Assert, if the player start ship was not created
at mission start.
Also fix an unguarded read of Ships[Player_start_shipnum] in both editors.
Co-authored-by: Claude Fable 5 <[email protected]>
`support_ship_info::rearm_weapon_pool` was a dense per-weapon-class array where -1 = unlimited (the default), 0 = not rearmable, and >0 = remaining stockpile. Convert it to a sparse per-team map:
`std::array<SCP_map<int, int>, MAX_TVT_TEAMS> rearm_weapon_pool;`
`int rearm_pool_default() const; // -1, or 0 under rearm_pool_from_loadout`
Mission-file format and save output are unchanged. Also tightens the two weapon-class bounds checks in ship.cpp. This removes another `MAX_WEAPON_TYPES` sized structure on the way to lifting the weapon class limit.
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
With every fixed-size ship-class-indexed structure now converted to dynamic containers, we can finally remove the `MAX_SHIP_CLASSES` limit!
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
team_data was the last persistence-adjacent structure sized by MAX_SHIP_CLASSES: four parallel ship arrays plus four parallel weapon arrays (one slot per mission-file loadout line, holding a class or a sexp variable name, and a count or a sexp variable name) with manual num_*_choices counters, plus a dense per-class weapon_required flag array. Convert all of it to the sparse-container family:
ship_list / ship_list_variables / ship_count / ship_count_variables
-> SCP_vector<loadout_entry> ship_choices
weaponry_pool / weaponry_count / weaponry_pool_variable /
weaponry_amount_variable
-> SCP_vector<loadout_entry> weapon_choices
bool weapon_required[MAX_WEAPON_TYPES]
-> SCP_set<int> required_weapons
where loadout_entry is {class_index, count, class_variable, count_variable}, with empty variable strings meaning the value was given literally. num_ship_choices / num_weapon_choices become .size().
Intentional behavior fixes, all previously latent memory errors or
data bugs:
* is_ship_assignable stored an alt ship CLASS in a variable used as a
loadout list INDEX when default_to_this_class was false, then
swapped to ship_list[class] - garbage for any mission where the two
domains diverge. It now swaps to the alt class itself, per the
evident intent.
* The default-ship fallback read ship_list[0] even when a team had
zero ship choices; it now leaves default_ship at -1, and
ss_fixup_team_data guards its append of the default ship accordingly.
* FRED2's dumpstats indexed Ships[] with a ship class when printing
loadout entries; it now prints the entry's class or variable name
directly.
* Team parse now clears the previous mission's entries explicitly
rather than relying on overwrite-in-place.
This clears the largest remaining cluster of MAX_SHIP_CLASSES references on the way to lifting the ship class limit.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
- A parse flag to indicate when the mission is being parsed, and a new `ship_class_index()` accessor in the ship registry, both currently unused
- Make the ship registry more robust, especially in FRED: properly update it when ships are renamed or deleted
Following PR #7429, ships belonging to wings were hashed the same way as every other ship, instead of the original hackish format where the hash was juggled around the number. This was a mostly transparent upgrade, and ship names were automatically converted from the legacy format to the new format. However, missions that referenced ships *in subsequent waves* broke; the converter only handled ships in the first wave.
This PR adds compatibility checks to `eval_ship` and `ship_registry_get` to accommodate legacy names.
Convert the allowed-ships and allowed-weapons lists to sets, rather than naive vectors that stored a true/false for every class. Since these are ordered sets, iteration occurs in the same sequence as iteration of `Ship_info` or `Weapon_info`. Associated data structures such as `Granted_ships` and `Granted_weapons` are converted as well.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Convert ship/weapon loadout pools from naive per-class arrays to ordered maps that list loadout entries. Each entry represents a ship/weapon class and its count. An absent entry means the class is not in this mission's loadout. Since these are ordered maps, iteration of the map keys occurs in the same sequence as iteration of `Ship_info` or `Weapon_info`.
Includes several bugfixes:
- `csg_read_loadout` now clears both pools before reading. Previously the pools were only zeroed in `mission_campaign_init`
- `restore_wss_data`: non-transmitted classes are now absent rather than memset to 0
- a copy-paste bug where the weapon pool entries were bounds-checked against MAX_SHIP_CLASSES
- `wss_maybe_restore_loadout`: the pool write-back now preserves loadout membership instead of writing a count for every class
- `ss_dump_to_list`/`ss_swap_list_slot`: returning a slot ship whose class is not in the pool now makes it available instead of silently losing it. Unreachable in retail but reachable via script
- off-by-one errors in the Lua `Ship_Pool`/`Weapon_Pool` indexers
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Refactor `Ship_class_names`, `Iff_info_names`, and `Ai_class_names` to be populated once at parse time, as opposed to ad-hoc and possibly many times.
Also make `Ai_classes` and `Spawn_names` proper SCP_vectors.
* Shadow overhaul round 2: PCSS, dynamic cascades, cockpit/instanced shadows
Squashed snapshot of lafiel/shadow_overhaul_2's continuation work
(d3922aa87..010f9d2d9), which has no separate upstream PR of its own.
* Add Vulkan rendering backend
Squashed snapshot of the Vulkan backend as vendored into this branch
(ce2d1e85f..6ce539463, authored by Mara van der Laan with build/CI
fixes by Taylor Richards). This work has its own upstream PR #7553
(notimaginative:vulkan-pr-new) and is not otherwise original to true-hdr.
* Add true HDR output, RT shadows, and PCSS shadows for Vulkan
Squashed snapshot of true-hdr's own integration work: true HDR
framebuffer output, wiring the shadow overhaul into the Vulkan
backend (raytraced + cascaded shadow maps), logging refactors, log
filter UI, and CI/build fixes.
* refactor: update Vulkan logging calls to use `nprintf` for scoped logging
* fix: correct shadow cascade count in Vulkan logging calls
* fix: fall back to PATH-resolved ccache when CCACHE_PATH is stale
CI hardcodes /usr/local/bin/ccache, but macOS runners on Apple Silicon
hosts install Homebrew (and ccache) under /opt/homebrew instead,
causing all four Mac configs to fail with "No such file or directory".
* Fix vulkan texture readback to correctly handle transparent pipelines
* Put the pixel swizzles into a util file
* OOB safety check
* Fix fog handling
* Fix MSAA in Vulkan
* fix: enable SMAA for vulkan
* adapt the post-processing AA shaders to hdr
* fix clang-tidy warnings: concatenate nested namespaces, static methods
pixel_swizzle.{h,cpp}: collapse `namespace graphics { namespace util {`
into `namespace graphics::util {` (modernize-concat-nested-namespaces).
VulkanState.h/VulkanDraw.h: getClearColor()/setClearColor() only touch
gr_screen and other globals, no instance state, so make them static
(readability-convert-member-functions-to-static) and drop the
top-level const on getClearColor()'s return type
(readability-const-return-type).
* fix clang-tidy warning
* Extend raytraced shadows to all directional lights
RT shadows previously only shadowed the first directional light in the
scene, even though multi-sun missions push more than one directional
light into the engine. The TLAS raytraced shadows query against is
already light-agnostic, so this extends both the deferred lighting
pass and the forward MODEL shader to shadow every directional light
(capped by a new Max Raytraced Shadow Lights option, configurable via
the in-game Options menu or the Lab's Scene rendering options).
Cascaded shadow maps are untouched: they're built for a single light
only, so the deferred pass still limits itself to one shadowed
directional light whenever CSM (not raytracing) is the active method.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016DA6JHJ3twz2oHGQcZ1Udc
* refactor: improve shader flag handling and render target finalization in Vulkan backend
- Consolidate shader flag setup logic in VulkanPostProcessingLighting to simplify configuration.
- Fix dead field usage in VulkanDraw by ensuring correct image view selection for environment maps.
- Add mip chain regeneration in VulkanRenderer::endRenderTarget to ensure correct mip levels for cubemaps.
* fix: ensure correct mip chain initialization in cubemap rendering
- Add mip chain generation to VulkanDraw for cubemaps with multiple mip levels.
- Fix potential issue with uninitialized mip levels above level 0 in deferred-f.sdr sampling paths.
* fix: don't crash on startup when environment locale is unavailable
std::locale("") pulls in the process's environment locale categories
(LC_CTYPE, LC_MONETARY, LC_PAPER, etc.), and throws std::runtime_error
if any of them names a locale not installed on the host (e.g.
mismatched regional-format overrides). Since SCP_default_locale is
constructed during static initialization, before main() and before any
of our own exception handling exists, an uncaught throw here aborted
the whole process before the game even started. Fall back to the
classic "C" locale instead, which is sufficient since this locale is
only ever used for ASCII case-folding (SCP_toupper/tolower/isdigit).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
* `feat: add raytraced shadows for local lights and optimize TLAS management`
* Make the Commandline authoritative for -res and -vulkan / -opengl
* address BMagnu's comments on the original PR
* update prebuilt version hash to 62c89ef6
* refactor: standardize graphics API handling and document Vulkan limitations in qtFRED
- Updated `AboutDialogModel.cpp` to use `GraphicsAPI` enum for better clarity.
- Ensured qtFRED falls back to OpenGL when Vulkan is requested.
- Added detailed documentation in `qtfred/README.md` regarding Vulkan backend limitations and requirements.
* chore: include cmdline library in main and mission management modules
* docs: add known issue for blank render viewport under Wayland in README.md
* fix CI issues
* chore: add `GR_DEBUG_SCOPE` markers across Vulkan post-processing and rendering paths for improved debugging
* feat: improve Vulkan HDR pipeline with separate encode paths for SDR and HDR10
- Added an HDR10-specific output encode pass in `VulkanRenderer` and `VulkanPostProcessor` that applies PQ/BT.2020 transfer.
- Introduced an optimized SDR fallback path with direct blit support for devices that can skip shader draws.
- Updated `m_sceneLdrCompressed` to provide [0,1]-clamped proxies for HDR edge detection.
- Refactored `copyImageToImage` and added `blitImageToImage` to allow format conversions and optimized transfers.
- Replaced `hdr_mode` with explicit HDR10 flags and data structures for clearer state handling.
* refactor: encapsulate SMAA processing and refactor VulkanPostProcessor
- Moved SMAA-specific logic into `VulkanSMAA` class for better separation of concerns.
- Simplified `initSMAA`, `shutdownSMAA`, and `executeSMAA` logic by leveraging `VulkanLDR` and `PostProcessContext` abstractions.
- Improved initialization and cleanup code with clearer resource lifecycle management.
- Updated debug markers and messages to reference `VulkanSMAA`.
- Enhanced flexibility of fullscreen triangle rendering API with additional parameters (`sampleCount`, `bindGlobalSet`).
* Update Vulkan porting credits in AboutDialogModel
* fix CI issues again
* fix: correct Y-flip in viewport setup for Vulkan light volumes
- Adjusted viewport height to negative value to account for G-buffer render pass's negative Y-axis convention.
- Ensured proper alignment of point and tube light volumes with actual light positions.
* feat: add user-adjustable gamma correction to SDR and HDR10 output paths
- Added gamma correction to HDR10 path, applying user settings before PQ/BT.2020 encode.
- Replaced the SDR blit-only path with an encode pass that applies gamma correction, ensuring consistent behavior across devices.
- Refactored VulkanPostProcessor and associated shaders to support gamma slider adjustments for both SDR and HDR output.
* fix: ensure proper uniform and cull mode setup for Vulkan sphere rendering
- Added uniform buffer setup and default material uniforms in `vulkan_draw_sphere`.
- Disabled face culling in material definition for sphere rendering to align behavior with OpenGL.
* Ensure memory flushes after UBO updates and add explicit pipeline barrier for MoltenVK compatibility.
* fix: add clear pass for SMAA edge detection to prevent persistent artifacts
* feat: add Vulkan debug-utils messenger, opt-in sync validation, frame diagnostics
Groundwork for the renderer-review fix series (.agents/plans/
vulkan-renderer-review-fixes.md, Phase 0):
- Register a VK_EXT_debug_utils messenger (preferred) instead of only the
deprecated VK_EXT_debug_report callback, which is kept as a fallback for
loaders without debug-utils. The messenger config is also chained into
instance creation so vkCreateInstance-time messages are captured.
Validation errors/warnings now go to the main log via mprintf.
- New -gr_sync_validation cmdline flag (implies -gr_debug): chains
VkValidationFeaturesEXT with synchronization validation into instance
creation when VK_LAYER_KHRONOS_validation is available. This is the
regression net for the synchronization fixes in the following commits.
- Frame stats now report descriptor sets/writes allocated per frame and the
total pipeline count (descriptor-manager counters; DescriptorWriter::flush
moved out-of-line to report into the manager).
Co-Authored-By: Claude Fable 5 <[email protected]>
* refactor: unify Vulkan render-pass begins, clamp in-pass clears, add per-frame UBO ring
Phase 1 of the renderer-review fix series: shared helpers that the
correctness fixes build on.
- New VulkanRenderer::beginTrackedRenderPass(PassBeginDesc): every render-pass
begin on the frame command buffer now records vkCmdBeginRenderPass and
updates the state tracker (pass, color attachment count, sample count,
render area, viewport) in one place. Converted: setupFrame,
beginSceneRendering (both branches), resumeSceneRendering,
endSceneRendering, beginRenderTarget (viewport kept for gr_set_viewport),
resumeSwapChainPass, plus new resumeScenePassAfterCopy() shared by
copyEffectTexture/copySceneDepthForParticles and
rebeginSwapChainPassAfterReadback() shared by both readbackFramebuffer
exits (~200 lines of duplicated begin/clear/viewport blocks removed).
- The state tracker records the active render area; all four
vkCmdClearAttachments sites clamp their rects against it and skip empty
results. Previously gr_screen-sized clear rects could exceed a smaller
off-screen render target's render area, which is invalid API usage.
- New PerFrameUboRing utility (VulkanPerFrameUbo.h/.cpp): a persistently
mapped uniform buffer with one slot region per frame in flight and a
self-flushing alloc(). Used by the next commit to fix the cross-frame
UBO races.
Deliberate behavior deltas (all safe-direction): the helper always sets
colorAttachmentCount/sampleCount (some sites previously inherited stale
values), and resumeSwapChainPass/resumeSceneRendering now also reset the
standard flipped viewport instead of leaving the previous one.
Co-Authored-By: Claude Fable 5 <[email protected]>
* fix: eliminate Vulkan cross-frame UBO/buffer races, make gr_sync honest
Phase 2 of the renderer-review fix series: with two frames in flight, several
host-visible resources were rewritten by the CPU while the previous frame's
GPU work could still be reading them.
- The post-processing scratch UBO is now a PerFrameUboRing (per-frame-in-
flight slot regions, persistently mapped, self-flushing alloc). All the
per-pass map/unmap/cursor bookkeeping in bloom/LDR/fog/SMAA is deleted;
the cursor resets exactly once per frame via the new
VulkanPostProcessor::beginFrame() hook in setupFrame.
This also fixes two latent bugs: (a) fog consumed scratch slots mid-scene
and bloom's later same-frame cursor reset overwrote them before submission
whenever fog+bloom were both active; (b) volumetric_fog_data (288 bytes)
overflowed the 256-byte slots - the memcpy spilled into the neighboring
slot and the shader's bound range ended before the aspect/fov/noise
fields. Slots are now 512 bytes (static_assert at the fog call site) and
the per-frame budget is 32 slots.
- The dedicated tonemap, output-encode, and MSAA-resolve UBOs (each a single
instance rewritten per frame, i.e. racy; the MSAA one also never flushed)
are deleted entirely - those passes build their structs on the stack and
allocate from the scratch ring.
- Static-buffer rewrites orphan instead of racing: getVkBuffer() stamps a
last-used frame number, and a full-content updateBufferData() on a Static
buffer used within the last MAX_FRAMES_IN_FLIGHT frames defers the old
VkBuffer to the deletion queue and writes a fresh one. Offset writes
(GPU-heap appends to fresh regions) and PersistentMapping buffers (the
engine holds their mapped pointer and syncs via gr_sync) are exempt.
- gr_sync_wait now honors its contract: VulkanRenderFrame::waitForFinish
takes a timeout and reports expiry without consuming the fence;
VulkanRenderer::waitForFrame returns false for fences taken during the
still-recording frame (previously it waited on a stale or idle slot and
reported success) and resolves the frame->slot mapping correctly. This
makes UniformBufferManager's segment-fence throttling actually protective
on Vulkan for the first time.
Co-Authored-By: Claude Fable 5 <[email protected]>
* Refactor framebuffer and target lifecycle: centralized creation/resizing logic, improved resource management across Vulkan post-processing subsystems.
* Improve Vulkan cross-frame sync with explicit access masks and validation updates
- Unified Vulkan validation flag handling in setup, separating engine debug output from synchronization validation.
- Specified access masks and stage flags to address cross-frame WRITE_AFTER_WRITE vulnerabilities, notably in G-buffer, post-processing, and MSAA resolve paths.
- Adjusted subpass dependencies and image layout transitions to prevent read-after-write and write-after-write hazards, detected using `-gr_sync_validation`.
- Fixed uniform buffer and barrier transitions to ensure proper ordering in render passes across frames.
* Refactor Vulkan render-pass dependencies and sync stages for clarity and future testing
- Clarified dependency logic for cross-pass and cross-frame synchronization, ensuring explicit stage and access flag comments match observed behavior.
- Adjusted subpass dependencies to decouple render-pass compatibility definitions from dependency assumptions.
- Highlighted MoltenVK-specific issues and retained workarounds with notes for re-testing on macOS hardware.
- Updated synchronization barriers and validation notes to address historical inconsistencies across platforms.
* Remove transfer queue support; consolidate all operations into the graphics queue
- Removed transfer queue handling, simplifying buffer and queue logic to use the graphics queue for all transfer operations.
- Updated buffer sharing modes and removed related code paths and parameters for transfer queue families.
- Introduced exclusive buffer access, noting async transfer and queue ownership transfers are future work.
- Limited enabled device features to only those actively used by the renderer, reducing unnecessary feature requests (e.g., robustBufferAccess).
* Refactor frame advancement and descriptor management in VulkanRenderer
- Removed `endFrame()` from `VulkanDescriptorManager`, replacing it with `setCurrentFrame(frameIndex)` for explicit frame synchronization.
- Centralized frame index advancement within `VulkanRenderer` to ensure consistency across components.
- Invalidated cached descriptor set bindings at render-pass boundaries to handle raw recorder staleness correctly.
- Optimized pipeline descriptor rebind logic by removing redundant clears during pipeline changes.
* Refactor `PipelineConfig::hash` and Global descriptor set management for improved hashing and memoization efficiency
- Replaced inefficient shift-based hashing in `PipelineConfig::hash` with a robust `hashCombine` strategy to minimize bucket collisions.
- Introduced `maskBits` helper to streamline bitmask creation for boolean fields.
- Implemented memoization for Global (Set 0) descriptor sets, reducing redundant rebuilds across frames and improving performance.
- Added dirty state tracking (`m_globalSetDirty`) and invalidation logic for Global set updates following relevant input changes (e.g., TLAS or UBO modifications).
* Refactor Vulkan post-processing output logic: extracted shared `encodeToSwapChainPass` method to streamline HDR and SDR output paths.
* Optimize Vulkan descriptor set management with per-frame memoization
- Added memoization for Material (Set 1) and PerDraw (Set 2) descriptor sets to reuse cached sets across frames if inputs remain unchanged.
- Implemented precise input tracking for Material and PerDraw descriptor sets to detect and rebuild only when necessary.
- Reduced redundant Vulkan descriptor allocations and writes, improving performance on repeated or batched draw calls.
* Fix Vulkan access masks for G-buffer and scene pass transitions
- Added `eColorAttachmentRead` to access masks in multiple barriers to ensure proper synchronization for load operations (e.g., `loadOp=eLoad`) across render passes.
- Prevented `READ_AFTER_WRITE` synchronization validation issues in deferred and multi-pass rendering scenarios.
- Updated comments to clarify the reasoning behind the access mask changes and load dependencies.
* Refactor Vulkan resource management and indexed draw logic
- Consolidated destruction queue methods in `VulkanDeletionQueue` using a shared `queueHandle` template to reduce redundancy.
- Introduced `ModelDrawParams` structure and `computeModelDrawParams` function to unify indexed draw parameters for models and shadows.
- Simplified draw logic by eliminating repeated calculation of indexed draw parameters and improving code reuse.
* Add `releaseAnimationSlotRef` for ref-count handling of shared animation texture arrays
- Introduced `releaseAnimationSlotRef` to manage reference counting for shared animation texture-array slots, preventing premature destruction of shared resources.
- Updated `flushTextures` and related methods to integrate `releaseAnimationSlotRef` for shared slot cleanup.
- Refactored staging buffer creation into `createStagingBuffer` for 3D texture uploads and shared path consistency.
- Enhanced texture upload logic with improved tracking and logging for re-upload cases, aiding preloading gap analysis.
- Added instrumentation for on-demand mid-frame texture uploads in `FrameStats`.
* Refactor Vulkan resource and texture management, extend debug logging
- Updated smart pointer usage for consistency across Vulkan resource managers (`std::make_unique`).
- Removed redundant `cubeImageView` member and related cleanup logic; unified sampling view management.
- Added per-instance debug log counters (avoiding static state) for Vulkan texture uploads and bindings, improving visibility during initialization and run-time.
- Optimized `VulkanRenderer` property/feature getters by caching physical device properties and features during device creation.
- Improved memory region flushing logic for out-of-date offsets, aligning with Vulkan Memory Allocator behavior.
- Refactored render pass state tracking to reapply dynamic state reliably after mid-frame render passes.
- Enhanced validation and error handling for mismatched update texture formats.
* fix CI build warning
* Fix on-demand YUV texture locking and set alpha uniform for movie rendering in Vulkan
- Corrected bitmap lock type from `BMP_TEX_OTHER` to `BMP_AABITMAP` for YUV textures during on-demand loading.
- Updated movie rendering to bind alpha from the `MovieData` UBO (set 2, binding 4) for Vulkan, aligning behavior with OpenGL implementation.
* revert inadvertent changes introduced by a squash commit
* address ci issues
* remove wip stuff from code comments that make no sense to people who aren't me
* Remove unused LD_LIBRARY_PATH export and fix shader shadow map sampling.
* Refactor batched rendering uniform setup in Vulkan
- Removed unused fields from `genericData` uniform block, simplifying shader definitions.
- Updated Vulkan material uniform handling to align with OpenGL's batched rendering logic.
- Introduced `batched_data` struct for streamlined UBO updates, improving maintainability and clarity.
* Add `-hdr` command-line option for HDR nits configuration
- Introduced `-hdr` parameter to set paper white and peak luminance in nits, overriding in-game HDR settings.
- Added input validation with error feedback for incorrect format.
* Remove unused `MODEL_SDR_FLAG_SHADOW_MAP` macro from shader flags
* clean up unrelated changes
* Invalidate Vulkan state tracker bindings after external command buffer modifications
- Added `invalidateExternalBindings()` to `VulkanStateTracker` to handle stale pipelines, descriptors, and dynamic states caused by external Vulkan commands (e.g., ImGui renders mid-pass).
- Updated ImGui rendering flow to call `invalidateExternalBindings()` post-draw, ensuring consistent pipeline state for subsequent engine renders.
- Adjusted image usage flags to account for `eTransferSrc` in texture readbacks.
- Fixed barrier `dstAccessMask` and pipeline stage flags to ensure proper synchronization for image transitions.
* Add a few more generically useful lines to the log
* Simplify Qt deployment logic and improve handling for system-installed Qt on Linux
* add cmdline headers to make qtFred compile
* use VK_KHR_synchronization2 for sync and barrier purposes
* Simplify and clean up Qt deployment logic in CMake for qtfred
* update get_bitmap_from_texture to match the new API
* Enhance `-hdr` command-line option to force HDR output and support optional nits configuration with improved input validation and overrides.
* Improve Vulkan robustness, add device feature checks, and refine memory/logging.
- Added depth clamping support gated by device feature checks, with fallback where unsupported.
- Improved deferred lighting UBO overflow handling with user-accessible warnings and buffer size adjustments.
- Enhanced Vulkan logging for validation, allocation, and debugging:
- Automatic logging for descriptor pool exhaustion/fragmentation.
- Vertex format and resource destruction checks.
- Notifications for generic layout transitions and volumetric texture uploads.
- Consolidated shader logic to ensure Vulkan and OpenGL parity.
* Remove HDR10 encode shader and consolidate gamma correction logic for SDR and HDR10. Replace `Verify` assertions with `Assert`
* Add braces to conditional in object lighting logic for clarity
* Refine Vulkan memory allocation checks with `Verification` for improved error handling and messaging.
* Refine VulkanBuffer resize logic to preserve contents with CPU-side copy and add safeguards for non-coherent memory scenarios.
* Add detailed comments explaining descriptor array capacity constants in VulkanDescriptorManager
* Gate shadow support on depth clamp feature availability and clarify its necessity with improved comments.
---------
Co-authored-by: BMagnu <[email protected]>
Co-authored-by: Mara van der Laan <[email protected]>
Co-authored-by: Claude <[email protected]>
The defaulted moves copied the conditionally-owned name and its
ownership flag without clearing the source, so reallocation of
Builtin_messages (mods append custom types via #Message Types) freed
every strdup'd name out from under the surviving elements. Hand-write
the moves to reset the source and free the destination's old name.
Also fix copy-assignment, which leaked the old name and was unsafe
under self-assignment.
Co-Authored-By: Claude Fable 5 <[email protected]>
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
A sweep for the w_bank pattern (raw owning handles in types that are
copied, moved, or shifted by value) found four more instances:
p_object: the destructor frees dock_list, which suppresses the implicit
move operations, so Parse_objects growth copied elements -- safe only
because dock lists happen to be built after parsing finishes. Wrap
dock_list in the new util::reset_on_move<> (a handle whose move resets
the source) and explicitly default the rule of five (with a user-defined
move assignment that frees any existing dock list before the memberwise
transfer), so moves now transfer the dock list.
DecalDefinition: the defaulted moves copied the bitmap handles without
resetting the source, so destroying a moved-from element would
bm_release live handles -- safe only because DecalDefinitions grows
before loadBitmaps runs. Wrap the handles in reset_on_move<int, -1>.
cmission: FRED's remove_mission overwrote the removed slot's five
vm_strdup'd strings without freeing them (a leak on every removal) and
left the vacated slot aliasing the surviving copy (a double-free if the
slot is later reused without reassigning every string). Factor the
string cleanup out of mission_campaign_clear into
mission_campaign_free_mission_strings and call it before the overwrite,
nulling the vacated slot afterward.
Undo_stack: the destructor deletes the tracked items, but the implicit
shallow copy was used to transfer stacks into the undo system, kept safe
only by an immediately-following untrack() call. Delete the copy
operations, default the move constructor and implement move assignment
as clear-then-exchange, so an assigned-over stack deletes its own items
first and the source is guaranteed empty.
Co-Authored-By: Claude Fable 5 <[email protected]>
* long project will be long
* add shared foundation files
* unify some shared types across both editors
* sexptreemodel framework
* move opf functions to the model
* move shared functions into the model
* move variable/container stuff to the model
* sexptreeactions and ui interface
* wire the model into the ui
* move some shared logic to the model
* move more methods to the model
* move clipboard to model
* create new right click menu model
* begin connecting the new right click menu
* wire up the new right click menu in fred
* wire up the new right click menu in qtfred
* move help text and visuals to model
* move menu option enable disable to model
* wire up sexp_tree to the model
* rename sexp_tree files to sexp_tree_ui
* fix build errors
* fix warning
* remove all opf forwarders
* remove some forwarder methods
* connect qtfred events editor directly to model
* wite plan for unifying tree types
* replace legacy m_mode with treeflags
* fix qtfred campaign dialog
* fix qtfred ship/wing dialog trees
* build error
* documentation
* more documentation
* even more documentation
* move more opf functions to opf file
* clean up context menu function
* move default argument to opf file
* build errors
* fix qtfred dialogs and annoations
* new shared annotations model
* sexp_tree_ui to sexp_tree_view
* documentation pass
* document annotations model
* delete the plan
* move event annotations to the new model
* generic cleanup
* remove some old defines
* cleanup includes
* more cleanup
* parenthesis
* more parenthesis
* variable collisions
* too many parenthesis this time
* clang modernization
* variable names again
* more clang and assertions
* static cast
* clang again
* fix some bugs and issues
* Refactor sexp tree action duplication into SexpTreeActions
* fix a few more minor issues
* avoid clang false positive
* rebase tweaks
* fix annotations issue
* fix typo and two small bugs claude found
* fix event editor tree widget loading
* fix ship and wing sexp tree editors in qtfred
* fix qtfred briefing and debriefing trees
* fix a regression Claude found
* address feedback
* fix annotations for root items in qtfred by copying fred's method
* address feedback
* rescope variables
* clang
brief_stage owns its icons and lines buffers through raw pointers, so the
implicitly generated copy/move operations silently alias those buffers
across two stages. Delete them and provide a custom swap() that
exchanges members (keeping each slot with a distinct buffer), plus a
reset() that restores the default-constructed state.
Update brief_compact_stages, the only code that assigned brief_stage
objects: shift stages down by swapping (equivalent, since the removed
stage buffers were already freed and the tail is cleared afterward) and
clear the tail entries with reset().
Co-Authored-By: Claude Fable 5 <[email protected]>
MMessage's avi_info/wave_info unions hold strdup'd filenames in FRED
(indices in the game), but moves copied the pointers without nulling
the source, so every vector shift on Messages left moved-from elements
aliasing live allocations -- one careless edit away from a double-free.
Wrap each union in a MessageMediaRef struct whose moves null the source
and whose copies stay shallow (the event editor's backup pattern copies
then re-strdups). MMessage keeps its implicit copy and gains a correct
implicit move; access syntax at all use sites is unchanged.
Add a message_free_media_names() helper and use it at the FRED
delete/wipe sites. Two fixes ride along: messages_init() now frees
mission messages' media names before discarding them, closing a leak
that fired on every mission load in FRED; and the message editor's
OnDelete now uses the move-based array_remove_slot instead of a
copy-shift that left a stale aliasing tail.
No destructor: the active union member depends on Fred_running, and
shallow copies alias, so freeing remains explicit.
Co-authored-by: Claude Fable 5 <[email protected]>
* framework for support ship rearm pool and flags
* scripting api
* FRED support
* QtFRED support
* do not save non-player-allowed weapons
* tvt and mission versioning
* fix variable name
* minor fixes and clarity
* oops
* add qtfred help docs
* fixes and cleanup
* clang and readability
* address feedback
* address feedback
Ship_info is already an SCP_vector but MAX_SHIP_CLASSES (500) is held in
place by fixed-size structures throughout the engine and editors. This
converts the seven file-format-adjacent arrays whose persistence touches
the .csg campaign save and .plr pilot file formats:
campaign::ships_allowed [MAX_SHIP_CLASSES] -> SCP_vector<ubyte>
campaign::weapons_allowed [MAX_WEAPON_TYPES] -> SCP_vector<ubyte>
loadout_data::ship_pool [MAX_SHIP_CLASSES] -> SCP_vector<int>
loadout_data::weapon_pool [MAX_WEAPON_TYPES] -> SCP_vector<int>
scoring_struct::kills [MAX_SHIP_CLASSES] -> SCP_vector<int>
scoring_struct::m_kills [MAX_SHIP_CLASSES] -> SCP_vector<int>
scoring_struct::m_okKills [MAX_SHIP_CLASSES] -> SCP_vector<int>
The CSG and PLR file formats already store explicit counts and key by
ship/weapon names, so no on-disk format change is needed. Existing
read/write loops already iterated by ship_info_size() / weapon_info_size()
and just compile through against the new vector types.
Vectors are sized to ship_info_size() / weapon_info_size() in
scoring_struct::init(), mission_campaign_clear(), player_loadout_init(),
and the campaign-pool reset paths in csg.cpp and multi_campaign.cpp.
All of these run after weapon_init() / ship_init(), so the sizing
invariant holds. scoring_struct globals constructed at static-init time
start with empty vectors; first index access is always post-table-parse.
Multiplayer stats sync (multimsgs.cpp) now derives offsets and counts
from the vector size; added USHRT_MAX assertions on the sender side
since the wire protocol packs offset/count as USHORT. The FSTracker
sync (multi_fstracker.cpp) clamps vmt->num_ships down to the local
mod's kills.size() before transmission and guards the receive-side
write so a tracker payload with more ship slots than this mod has won't
overrun the vector.
The MAX_SHIP_CLASSES cap itself stays in place at ship.cpp:2327; this
is one prerequisite toward lifting it. The other ~25 fixed-size
ship-class-indexed structures (team_data, Ss_pool_teams, Wl_ships,
FRED2 editor arrays, qtFRED Editor::_ship_usage, etc.) remain to be
addressed in follow-up PRs.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
* change to new fog system
* Fix fog conversion issues
* remove unused old neb values
* Update scripting API
* temporary old-value round trip
* Rewrite nebula fog with four explicit parameters
Replace the legacy `+Fog Near Mult` / `+Fog Far Mult` model — which
depended on fixed engine constants — with four parameters that map
directly to renderer behavior:
+Fog 1000m Visibility fraction of light surviving 1000m of fog
+Fog Near Distance meters from camera where fog begins
+Fog Skybox Clip Distance max render distance for the skybox in fog
+Fog Clip Distance max render distance for the scene in fog
The deferred-lighting fog shader, material system, model renderer,
object sort, and the Lua mission API all consume the new parameters
directly. The legacy `Neb2_fog_near_mult` / `Neb2_fog_far_mult`
globals are removed.
Mission parsing accepts either the legacy keys or the modern keys.
Legacy values are converted to the modern set at parse time, and a
per-mission flag (`Neb2_fog_use_legacy_values`) records which keys to
write back on save. The flag is cleared only when something actually
changes, so opening and OK'ing an unmodified legacy mission preserves
its on-disk format. Saving uses `optional_string_fred` to preserve
user comments through edit cycles.
FRED2 and QtFRED background editors both expose the four parameters
in place of the old multipliers, with friendly labels and tooltips.
Both editors validate input (visibility clamped to [0, 1]; near
distance must be > 0; skybox and scene clip distances may be 0 to
disable fog at that range) and use a snapshot-and-compare at close
time so that a no-op open-then-OK never spuriously rewrites a legacy
mission in the modern format.
FRED2's background dialog is also reorganized so that "Display bg
bitmaps in nebula", "Override nebula fog palette", the RGB row, and
the four fog parameters all sit inside the Nebula group box; the
"Old Nebula" group box is restored below it with its Pattern, Color,
Pitch, Bank, and Heading controls back inside the frame. The dialog
now overrides PreTranslateMessage so that the new fog tooltips —
and the previously broken "Save sun/bitmap angles in correct format"
tooltip — actually fire.
QtFRED's BackgroundEditor.ui adds four QDoubleSpinBox controls in
the existing nebula form layout, with per-field decimals chosen to
avoid scientific notation, and per-field min/max declaring the
validation rules. The QtFRED help page is updated to describe the
new fields.
* fix tooltips
* restore forward-renderer fog
* Clang Tidy
* address feedback
* Refine nebula fog rewrite
- Rename Neb2_fog_use_legacy_values to Neb2_fog_save_legacy_values
to reflect that the flag governs save format, not runtime
behavior.
- Decide both the save flag and the legacy-to-modern conversion
purely from whether the file contains modern fog keys, dropping
the prior heuristic based on required_fso_version. A mission
without modern keys (legacy- or retail-format) converts on load
and saves back in legacy format; missions with modern keys are
saved as modern.
- Snapshot the four fog values as floats at editor open and use
fl_equal for the no-change comparison in both FRED2 and QtFRED,
replacing the FRED2 CString comparison and the QtFRED strict
inequality.
- Document the on-disk invariant in missionsave: when not saving
legacy keys, the four modern keys must always be written (never
gated on "only if non-default"), since the parser uses their
presence to decide whether to run the conversion on next load.
---------
Co-authored-by: Goober5000 <[email protected]>
* add wing display names
Adds display name capability for wings, matching the pattern and infrastructure of display names for ships. As in #7234, wing display names take precedence over ship display names, if both are provided; because if ship display names took precedence, a ship would have the same display name in every wave.
All the relevant display locations are handled, including the HUD, escort list, message box, and mission log. Lua API support is provided as well.
Adds full support for both FRED and QtFRED, including the always-save-display-names setting.
Addresses #7189 and the last request from #1730.
* add help text from #7234
Some code refactoring to allow mission titles to be strings of any length. Titles that are too long to actually display (based on string width) will be visibly truncated using `font::force_fit_string`, but the underlying title will not be affected. Multiplayer is still subject to the 31-character length limit, so titles sent over the network will still have characters truncated.
Also removes some redundant calls to `gr_get_string_size`, since `font::force_fit_string` returns the fitted string width.
* new qtfred error checker
* fix some issues
* clang
* more clang
* clean up and fixes
* missed a comment
* add help documentation
* surface auto corrected issues more clearly
* some cleanup
* use std::move() for local variables that are copied but not reused
Addresses ~140 Coverity "Variable copied when it could be moved"
defects across 74 files. Every std::move() targets a local variable
whose value is consumed by push_back, emplace, assignment, lambda
capture, or a function call and is never read again afterward.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
* clang: use std::make_shared
* clang: use std::make_unique
* address feedback
---------
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Three out-of-bounds/UB fixes for issue #6793:
- asteroid_create_all: skip field_debris_type[idx] == -1 entries when
building the DG_DEBRIS odds table and loading models; iterate only
over valid table slots during spawn. Matches the guard already present
in asteroid_frame.
- missionparse: bounds-check subtype before indexing colors[] in the
obsolete +Field Debris Type: parsing path.
- verify_asteroid_list: guard the asteroid_list[0] fallback access
against an empty list.
- asteroid_init: emit an mprintf when no asteroids are defined instead
of returning silently.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>