This commit is contained in:
Brandon Thomas committed 2026-10-09 09:04:06 -04:00
commit 7e350eccb8
6 files changed
+69 -1

No files matched your search

+13 -1
View File
@@ -83,11 +83,13 @@ impl eframe::App for App {
/// The window's renderer (egui-wgpu): eframe's defaults, with LightCraft's backend choice — DX12
/// alone on Windows unless `LIGHTCRAFT_GPU_BACKEND` / `WGPU_BACKEND` say otherwise (issue #136:
/// with Vulkan in the set, wgpu loads the Vulkan driver even when it then picks DX12).
/// with Vulkan in the set, wgpu loads the Vulkan driver even when it then picks DX12) — and DX12
/// shaders compiled with FXC, never a stray `dxcompiler.dll` (issue #471).
fn window_wgpu_options() -> eframe::egui_wgpu::WgpuConfiguration {
let mut c = eframe::egui_wgpu::WgpuConfiguration::default();
if let eframe::egui_wgpu::WgpuSetup::CreateNew(n) = &mut c.wgpu_setup {
n.instance_descriptor.backends = lightcraft_engine::gpu::backend::window_backends();
n.instance_descriptor.backend_options = lightcraft_engine::gpu::backend::backend_options();
}
c
}
@@ -905,6 +907,16 @@ mod tests {
assert!(!b.is_empty());
}
/// Issue #471: the window's DX12 shaders compile with FXC — wgpu's default loaded any
/// `dxcompiler.dll` on the search path, and one without `dxil.dll` kept the window from opening.
#[test]
fn window_compiles_dx12_shaders_with_fxc() {
let eframe::egui_wgpu::WgpuSetup::CreateNew(n) = window_wgpu_options().wgpu_setup else { panic!("expected CreateNew") };
if std::env::var_os("WGPU_DX12_COMPILER").is_none() {
assert!(matches!(n.instance_descriptor.backend_options.dx12.shader_compiler, eframe::wgpu::Dx12Compiler::Fxc));
}
}
/// Issue #234: Windows opens links with the URL protocol handler (the default browser), not
/// File Explorer, and passes the whole map link, `?`, `&` and `#` included, as one argument.
#[test]
+38
View File
@@ -121,6 +121,30 @@ pub fn window_backends() -> Backends {
resolve(choice, default, wgpu::Instance::enabled_backend_features()).unwrap_or(default)
}
/// The DX12 shader compiler: FXC (`d3dcompiler_47.dll`, part of Windows) unless
/// `WGPU_DX12_COMPILER` (`wgpu`, wgpu's own variable: `fxc`, `dxc`, `auto`…) names another.
///
/// Issue #471: wgpu's default (`Auto`) loads whichever `dxcompiler.dll` the DLL search path finds
/// first — LightCraft ships none, so it is some other program's (an SDK's, a folder on `PATH`). A
/// copy without its `dxil.dll` beside it warns that the DXIL is unsigned; wgpu takes the warning for
/// a compile error, its own validation pipelines fail, the device is lost and the window never
/// opens. FXC is always there and compiles everything LightCraft's shaders need.
pub fn dx12_compiler(env: Option<&str>) -> wgpu::Dx12Compiler {
let Some(v) = env else { return wgpu::Dx12Compiler::Fxc };
v.parse().unwrap_or_else(|e| {
log::warn!("gpu: WGPU_DX12_COMPILER={v} ignored: {e}");
wgpu::Dx12Compiler::Fxc
})
}
/// wgpu's backend options for every instance LightCraft creates — the window's and the compute
/// devices': wgpu's environment variables, with the DX12 compiler from [`dx12_compiler`].
pub fn backend_options() -> wgpu::BackendOptions {
let mut o = wgpu::BackendOptions::from_env_or_default();
o.dx12.shader_compiler = dx12_compiler(std::env::var("WGPU_DX12_COMPILER").ok().as_deref());
o
}
/// `LIGHTCRAFT_GPU_BACKEND=off`.
pub(crate) fn env_off() -> bool {
env_choice() == BackendChoice::Off
@@ -211,6 +235,20 @@ mod tests {
assert!(default_window_backends("linux").contains(Backends::VULKAN | Backends::GL));
}
/// Issue #471: DX12 shaders compile with FXC, never a `dxcompiler.dll` found on the search path,
/// unless `WGPU_DX12_COMPILER` asks for it.
#[test]
fn dx12_compiles_with_fxc_unless_asked() {
assert!(matches!(dx12_compiler(None), wgpu::Dx12Compiler::Fxc));
assert!(matches!(dx12_compiler(Some("bogus")), wgpu::Dx12Compiler::Fxc));
assert!(matches!(dx12_compiler(Some("FXC")), wgpu::Dx12Compiler::Fxc));
assert!(matches!(dx12_compiler(Some("dxc")), wgpu::Dx12Compiler::DynamicDxc { .. }));
assert!(matches!(dx12_compiler(Some("auto")), wgpu::Dx12Compiler::Auto));
if std::env::var_os("WGPU_DX12_COMPILER").is_none() {
assert!(matches!(backend_options().dx12.shader_compiler, wgpu::Dx12Compiler::Fxc));
}
}
#[test]
fn resolves_against_the_build() {
let compiled = Backends::DX12 | Backends::VULKAN;
+2
View File
@@ -282,6 +282,8 @@ impl Gpu {
let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
// only these drivers are loaded (`crate::backend`: never Vulkan on Windows unless asked)
desc.backends = backends;
// DX12 shaders compile with FXC (issue #471)
desc.backend_options = crate::backend::backend_options();
let instance = wgpu::Instance::new(desc);
let adapter = pollster::block_on(
instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, ..Default::default() }),
+2
View File
@@ -76,6 +76,8 @@ fn create_device() -> Result<Dev, String> {
fn make_device(backends: wgpu::Backends) -> Result<Dev, String> {
let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
desc.backends = backends;
// DX12 shaders compile with FXC (issue #471)
desc.backend_options = crate::backend::backend_options();
let instance = wgpu::Instance::new(desc);
let wanted = std::env::var("LIGHTCRAFT_GPU_ADAPTER").ok().map(|s| s.trim().to_lowercase()).filter(|s| !s.is_empty());
let adapter = match &wanted {
+11
View File
@@ -44,6 +44,17 @@ Overrides (read once at launch):
the compute device always used Vulkan + DX12 (+ Metal).
- `LIGHTCRAFT_GPU=0`: GPU rendering off for the process (the window is unaffected).
**DX12 shader compiler (issue #471).** Every instance (window and compute) compiles DX12 shaders with
FXC (`d3dcompiler_47.dll`, part of Windows; `lightcraft_gpu::backend::backend_options`). wgpu's
default, `Auto`, loads whichever `dxcompiler.dll` the DLL search path finds first. LightCraft ships
none, so that copy belongs to another program (a Windows SDK, a folder on `PATH`). An older copy without
`dxil.dll` beside it (e.g. DXC 1.7) warns that the DXIL is unsigned, wgpu treats the warning as a
compile error, its indirect-validation pipelines fail, the device is lost, and the window never opened
(`WGPU error: Parent device is lost`). `WGPU_DX12_COMPILER=dxc | auto | fxc` (wgpu's variable) still
chooses; with `dxc`, put `dxcompiler.dll` *and* `dxil.dll` (DXC ≥ 1.8.2502) next to `lightcraft.exe`.
FXC builds the compute kernels somewhat slower than DXC (≈ 9 s vs 7.5 s for the GPU test suite on an
RTX 4080), off the startup path.
**Off the startup path.** The compute device is created on a background thread once the window is
up (`gpu::warm_up` from the first frame's settings), and never while GPU rendering is off: with
Settings ▸ Performance ▸ *Use the GPU for rendering* unchecked (applied before the window opens),
+3
View File
@@ -151,6 +151,9 @@
ignored before) chooses the graphics backend; `off` renders on the CPU. The GPU now starts after the window
is up, and only when Settings ▸ Performance ▸ Use the GPU for rendering is on; if LightCraft ever dies while
starting the GPU, the next launch starts with GPU rendering off and says how to turn it back on.
- The Windows app no longer quits at launch with "Parent device is lost" when another program's `dxcompiler.dll` is
on the DLL search path without its `dxil.dll` (issue #471): DirectX 12 shaders now always compile with the
compiler built into Windows (FXC). `WGPU_DX12_COMPILER=dxc` uses a `dxcompiler.dll` instead.
- Exports and renders never write over a photo's original (issue #93): exporting into the photo's own folder with
the same name and "Overwrite" (or Export with Previous repeating it), an exact output path from the control
channel or MCP, a merge preview path or `lightcraft-cli render IMG.jpg -o IMG.jpg` is refused with a clear