mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-05 18:18:49 +00:00
* #1590: build effect kernels on first use instead of with the GPU context GpuContext::new built every compute pipeline up front: the compositor's own kernels and ~208 effect kernels. In Chrome on Windows that kept the GPU process busy past its watchdog, so the device was lost ~60 s after every load (#1590); on DirectX 12 with FXC the window took over a minute to appear (#613). The compositor's kernels are still built up front and required. Effect kernels are built the first time a dispatch asks for one, inside the same error scopes, so one a compiler rejects still renders its effects on the CPU. unavailable_kernels lists the failures so far; build_all_kernels builds the rest. RX 6900 XT, Windows: Vulkan context 0.27 s, all effect kernels 0.9 s, GPU tests 77 s -> 26 s; on #1571's DX12/FXC, context ~66 s -> 1.96 s, tests 1306 s -> 252 s, same results. Co-Authored-By: Claude <noreply@anthropic.com> * #1590: compile each kernel family as a shader module of its own Building effect kernels on first use was not enough in Chrome: the page and its frame workers each create a device, every one compiled its required kernels against the single ~460K-character module, and Dawn compiles the module again for each pipeline, so the GPU process still passed its watchdog and the device was lost ~64 s after load (RX 6900 XT, Chrome 155). Each kernel family (FAMILIES) is now common.wgsl + kernels.wgsl + its own file and the files it borrows functions from (at most ~120K characters), built with the family's first kernel; the compositor's kernels, sky and classic3d use small modules of their own. Chrome 155 on Windows / RX 6900 XT, release web build, default flags: main loses the device at ~64 s (60.7 s with ?nogpuworkers); with this, two 100 s runs keep it, and both frame workers get the GPU. Native Vulkan tests unchanged (212 passed, the 3 failures that main has, #1605). Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: 00xJ0 <56056764+00xJ0@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
This commit is contained in:
4 files changed
+223
-105
No files matched your search
+201
-101
@@ -24,9 +24,10 @@ const ENTRIES: &[&str] = &[
|
||||
"fill",
|
||||
];
|
||||
|
||||
/// Effect entry points in `kernels.wgsl`. Like the `fx_*` kernels, one that fails to build
|
||||
/// (a driver's shader compiler rejecting it, #613) leaves its effects to the CPU instead of
|
||||
/// turning the whole compositor off.
|
||||
/// Effect entry points in `kernels.wgsl`. Like the `fx_*` kernels, they are built on first use
|
||||
/// (building all ~200 up front kept Chrome's GPU process busy past its watchdog, #1590, and
|
||||
/// FXC took over a minute, #613), and one that fails to build (a driver's shader compiler
|
||||
/// rejecting it) leaves its effects to the CPU instead of turning the whole compositor off.
|
||||
const EFFECT_ENTRIES: &[&str] = &["box_h", "box_v", "directional", "glow_bright", "glow_combine", "shadow_make", "shadow_combine", "pointwise"];
|
||||
|
||||
/// Entry points that also bind group 1 (four read-only storage buffers; see
|
||||
@@ -34,6 +35,79 @@ const EFFECT_ENTRIES: &[&str] = &["box_h", "box_v", "directional", "glow_bright"
|
||||
/// can't unroll `classic3d`'s loops, #613) leaves its work to the CPU.
|
||||
const EXT_ENTRIES: &[&str] = &["classic3d"];
|
||||
|
||||
/// What every kernel module starts with: the shared helpers and bindings, and `kernels.wgsl`
|
||||
/// (the compositor's kernels, which declare `out` and helpers the effect files use).
|
||||
const BASE_WGSL: [&str; 2] = [include_str!("shaders/common.wgsl"), include_str!("shaders/kernels.wgsl")];
|
||||
|
||||
/// The family whose module also gets the adapter's median implementation
|
||||
/// ([`crate::fx_noise::MedianKernel::source`]), chosen when the context is created.
|
||||
const MEDIAN_FAMILY: &str = "fx_noise";
|
||||
|
||||
/// Kernel families: each is compiled as a module of its own, `BASE_WGSL` plus its file and the
|
||||
/// files it borrows functions from, built the first time one of its kernels is. A pipeline then
|
||||
/// compiles at most ~120K characters of WGSL instead of all ~460K: on WebGPU each pipeline
|
||||
/// compiles its module again, and the page's and its workers' devices doing that for every
|
||||
/// kernel kept Chrome's GPU process busy past its watchdog (#1590). Family 0 is the base module.
|
||||
const FAMILIES: &[(&str, &[&str], &[&str])] = &[
|
||||
("kernels", &[], EFFECT_ENTRIES),
|
||||
("fx_color", &[include_str!("shaders/fx_lut.wgsl"), include_str!("shaders/fx_color.wgsl")], crate::fx_color::KERNELS),
|
||||
("fx_distort", &[include_str!("shaders/fx_distort.wgsl")], crate::fx_distort::KERNELS),
|
||||
("fx_generate", &[include_str!("shaders/fx_generate.wgsl")], crate::fx_generate::KERNELS),
|
||||
("fx_key", &[include_str!("shaders/fx_distort.wgsl"), include_str!("shaders/fx_key.wgsl")], crate::fx_key::KERNELS),
|
||||
("fx_stylize", &[include_str!("shaders/fx_stylize.wgsl")], crate::fx_stylize::KERNELS),
|
||||
(MEDIAN_FAMILY, &[include_str!("shaders/fx_noise.wgsl")], crate::fx_noise::KERNELS),
|
||||
("fx_tone", &[include_str!("shaders/fx_tone.wgsl")], crate::fx_tone::KERNELS),
|
||||
("fx_warp", &[include_str!("shaders/fx_warp.wgsl")], crate::fx_warp::KERNELS),
|
||||
("fx_extra", &[include_str!("shaders/fx_generate.wgsl"), include_str!("shaders/fx_extra.wgsl")], crate::fx_extra::KERNELS),
|
||||
("fx_depth", &[include_str!("shaders/fx_depth.wgsl")], crate::fx_depth::KERNELS),
|
||||
("fx_lut", &[include_str!("shaders/fx_lut.wgsl")], crate::fx_lut::KERNELS),
|
||||
("fx_sim", &[include_str!("shaders/fx_sim.wgsl")], crate::fx_sim::KERNELS),
|
||||
(
|
||||
"fx_particles",
|
||||
&[include_str!("shaders/fx_stylize.wgsl"), include_str!("shaders/fx_sim.wgsl"), include_str!("shaders/fx_particles.wgsl")],
|
||||
crate::fx_particles::KERNELS,
|
||||
),
|
||||
("fx_vr", &[include_str!("shaders/fx_vr.wgsl")], crate::fx_vr::KERNELS),
|
||||
("fx_light", &[include_str!("shaders/fx_light.wgsl")], crate::fx_light::KERNELS),
|
||||
("fx_transition", &[include_str!("shaders/fx_transition.wgsl")], crate::fx_transition::KERNELS),
|
||||
("fx_text", &[include_str!("shaders/fx_text.wgsl")], crate::fx_text::KERNELS),
|
||||
("fx_time", &[include_str!("shaders/fx_time.wgsl")], crate::fx_time::KERNELS),
|
||||
("fx_pixel2", &[include_str!("shaders/fx_stylize.wgsl"), include_str!("shaders/fx_pixel2.wgsl")], crate::fx_pixel2::KERNELS),
|
||||
("fx_gen2", &[include_str!("shaders/fx_gen2.wgsl")], crate::fx_gen2::KERNELS),
|
||||
("inpaint", &[include_str!("shaders/inpaint.wgsl")], crate::inpaint::KERNELS),
|
||||
];
|
||||
|
||||
/// Module `label`: `BASE_WGSL` followed by `files`.
|
||||
fn kernel_module(device: &wgpu::Device, label: &str, files: &[&str]) -> Result<wgpu::ShaderModule, String> {
|
||||
let src: String = BASE_WGSL.iter().chain(files).copied().collect();
|
||||
init_resource(device, &format!("{label} shader module"), || {
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some(label), source: wgpu::ShaderSource::Wgsl(src.into()) })
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute pipeline `entry` of `module`; a shader compiler rejecting it (FXC, #613) is an `Err`.
|
||||
fn build_pipeline(
|
||||
device: &wgpu::Device,
|
||||
module: &wgpu::ShaderModule,
|
||||
layout: &wgpu::PipelineLayout,
|
||||
entry: &'static str,
|
||||
) -> Result<wgpu::ComputePipeline, String> {
|
||||
init_resource(device, entry, || {
|
||||
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
label: Some(entry),
|
||||
layout: Some(layout),
|
||||
module,
|
||||
entry_point: Some(entry),
|
||||
compilation_options: Default::default(),
|
||||
cache: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Effect kernels by entry point: their family (index in `FAMILIES`) and, once asked for, the
|
||||
/// built pipeline or why it didn't build.
|
||||
type EffectPipelines = HashMap<&'static str, (usize, std::sync::OnceLock<Result<wgpu::ComputePipeline, String>>)>;
|
||||
|
||||
/// Pixel format of every working texture: premultiplied RGBA, 32-bit float (32 bpc headroom;
|
||||
/// 8/16 bpc are emulated by clamping and quantising, as on the CPU).
|
||||
pub const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba32Float;
|
||||
@@ -601,9 +675,16 @@ pub struct GpuContext {
|
||||
pub(crate) median_kernel: crate::fx_noise::MedianKernel,
|
||||
bgl: wgpu::BindGroupLayout,
|
||||
bgl_ext: wgpu::BindGroupLayout,
|
||||
/// Each of `FAMILIES`' modules, built with its first kernel; and their group 0 layout.
|
||||
modules: Vec<std::sync::OnceLock<Result<wgpu::ShaderModule, String>>>,
|
||||
layout: wgpu::PipelineLayout,
|
||||
/// The compositor's own kernels and Classic 3D (built up front).
|
||||
pipelines: HashMap<&'static str, wgpu::ComputePipeline>,
|
||||
/// Effect kernels that failed to build, and why (their effects render on the CPU).
|
||||
/// Kernels built up front that failed to build, and why (Classic 3D renders on the CPU).
|
||||
unavailable: Vec<(&'static str, String)>,
|
||||
/// Effect kernels, built on first dispatch (`GpuContext::pipeline`); one that doesn't build
|
||||
/// leaves its effects to the CPU.
|
||||
effect_pipelines: EffectPipelines,
|
||||
display_bgl: wgpu::BindGroupLayout,
|
||||
display: wgpu::ComputePipeline,
|
||||
/// The display texture's next mip level from the one above (`display.wgsl` `mip`).
|
||||
@@ -778,40 +859,7 @@ impl GpuContext {
|
||||
if let Some(why) = &adv3d_unsupported {
|
||||
log::info!("gpu {name}: Advanced 3D renders on the CPU ({why})");
|
||||
}
|
||||
let src = [
|
||||
include_str!("shaders/common.wgsl"),
|
||||
include_str!("shaders/kernels.wgsl"),
|
||||
include_str!("shaders/classic3d.wgsl"),
|
||||
include_str!("shaders/fx_color.wgsl"),
|
||||
include_str!("shaders/fx_distort.wgsl"),
|
||||
include_str!("shaders/fx_generate.wgsl"),
|
||||
include_str!("shaders/fx_key.wgsl"),
|
||||
include_str!("shaders/fx_stylize.wgsl"),
|
||||
include_str!("shaders/fx_noise.wgsl"),
|
||||
median_kernel.source(),
|
||||
include_str!("shaders/fx_tone.wgsl"),
|
||||
include_str!("shaders/fx_warp.wgsl"),
|
||||
include_str!("shaders/fx_extra.wgsl"),
|
||||
include_str!("shaders/fx_depth.wgsl"),
|
||||
include_str!("shaders/fx_lut.wgsl"),
|
||||
include_str!("shaders/fx_sim.wgsl"),
|
||||
include_str!("shaders/fx_particles.wgsl"),
|
||||
include_str!("shaders/fx_vr.wgsl"),
|
||||
include_str!("shaders/fx_light.wgsl"),
|
||||
include_str!("shaders/fx_transition.wgsl"),
|
||||
include_str!("shaders/fx_text.wgsl"),
|
||||
include_str!("shaders/fx_time.wgsl"),
|
||||
include_str!("shaders/fx_pixel2.wgsl"),
|
||||
include_str!("shaders/fx_gen2.wgsl"),
|
||||
include_str!("shaders/inpaint.wgsl"),
|
||||
include_str!("shaders/sky.wgsl"),
|
||||
]
|
||||
.concat();
|
||||
#[cfg(test)]
|
||||
let src = src + crate::tests::MATH_PROBE_SHADER;
|
||||
let module = init_resource(&device, "kernels shader module", || {
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("effectcraft kernels"), source: wgpu::ShaderSource::Wgsl(src.into()) })
|
||||
})?;
|
||||
let module = kernel_module(&device, "effectcraft kernels", &[])?;
|
||||
let bgl = init_resource(&device, "kernels bind-group layout", || {
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("effectcraft kernels"),
|
||||
@@ -864,25 +912,21 @@ impl GpuContext {
|
||||
immediate_size: 0,
|
||||
})
|
||||
})?;
|
||||
let build = |e: &'static str, layout: &wgpu::PipelineLayout| {
|
||||
init_resource(&device, e, || {
|
||||
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
label: Some(e),
|
||||
layout: Some(layout),
|
||||
module: &module,
|
||||
entry_point: Some(e),
|
||||
compilation_options: Default::default(),
|
||||
cache: None,
|
||||
})
|
||||
})
|
||||
};
|
||||
// The compositor's own kernels are required; an effect kernel that doesn't build is
|
||||
// left out, and its effects render on the CPU (see `Enc::take_missing`). So is a
|
||||
// missing Classic 3D kernel: its 3D runs render on the CPU.
|
||||
let mut pipelines = ENTRIES.iter().chain(crate::adv3d::SKY_KERNELS).map(|e| Ok((*e, build(e, &layout)?))).collect::<Result<HashMap<_, _>, String>>()?;
|
||||
// The compositor's own kernels are required and built now. A Classic 3D kernel that
|
||||
// doesn't build is left out (its 3D runs render on the CPU), and effect kernels are built
|
||||
// on first use (`GpuContext::pipeline`); one that doesn't build leaves its effects to the
|
||||
// CPU (see `Enc::take_missing`).
|
||||
let sky = kernel_module(&device, "effectcraft sky", &[include_str!("shaders/sky.wgsl")])?;
|
||||
let mut pipelines = ENTRIES
|
||||
.iter()
|
||||
.map(|e| (e, &module))
|
||||
.chain(crate::adv3d::SKY_KERNELS.iter().map(|e| (e, &sky)))
|
||||
.map(|(e, module)| Ok((*e, build_pipeline(&device, module, &layout, e)?)))
|
||||
.collect::<Result<HashMap<_, _>, String>>()?;
|
||||
let mut unavailable = vec![];
|
||||
let classic3d = kernel_module(&device, "effectcraft classic3d", &[include_str!("shaders/classic3d.wgsl")]);
|
||||
for e in EXT_ENTRIES {
|
||||
match build(e, &layout_ext) {
|
||||
match classic3d.as_ref().map_err(Clone::clone).and_then(|m| build_pipeline(&device, m, &layout_ext, e)) {
|
||||
Ok(p) => {
|
||||
pipelines.insert(*e, p);
|
||||
}
|
||||
@@ -893,41 +937,14 @@ impl GpuContext {
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pipelines.insert("math_probe", build("math_probe", &layout)?);
|
||||
for e in EFFECT_ENTRIES
|
||||
.iter()
|
||||
.chain(crate::fx_color::KERNELS)
|
||||
.chain(crate::fx_distort::KERNELS)
|
||||
.chain(crate::fx_generate::KERNELS)
|
||||
.chain(crate::fx_key::KERNELS)
|
||||
.chain(crate::fx_stylize::KERNELS)
|
||||
.chain(crate::fx_noise::KERNELS)
|
||||
.chain(crate::fx_tone::KERNELS)
|
||||
.chain(crate::fx_warp::KERNELS)
|
||||
.chain(crate::fx_extra::KERNELS)
|
||||
.chain(crate::fx_depth::KERNELS)
|
||||
.chain(crate::fx_lut::KERNELS)
|
||||
.chain(crate::fx_sim::KERNELS)
|
||||
.chain(crate::fx_particles::KERNELS)
|
||||
.chain(crate::fx_vr::KERNELS)
|
||||
.chain(crate::fx_light::KERNELS)
|
||||
.chain(crate::fx_transition::KERNELS)
|
||||
.chain(crate::fx_text::KERNELS)
|
||||
.chain(crate::fx_time::KERNELS)
|
||||
.chain(crate::fx_pixel2::KERNELS)
|
||||
.chain(crate::fx_gen2::KERNELS)
|
||||
.chain(crate::inpaint::KERNELS)
|
||||
{
|
||||
match build(e, &layout) {
|
||||
Ok(p) => {
|
||||
pipelines.insert(*e, p);
|
||||
}
|
||||
Err(why) => {
|
||||
log::warn!("gpu {name}: kernel {e} unavailable, its effects render on the CPU: {why}");
|
||||
unavailable.push((*e, why));
|
||||
}
|
||||
}
|
||||
// The numerical probe (tests) calls `fx_stylize.wgsl` helpers.
|
||||
let probe = kernel_module(&device, "effectcraft math probe", &[include_str!("shaders/fx_stylize.wgsl"), crate::tests::MATH_PROBE_SHADER])?;
|
||||
pipelines.insert("math_probe", build_pipeline(&device, &probe, &layout, "math_probe")?);
|
||||
}
|
||||
let effect_pipelines =
|
||||
FAMILIES.iter().enumerate().flat_map(|(family, (_, _, kernels))| kernels.iter().map(move |e| (*e, (family, std::sync::OnceLock::new())))).collect();
|
||||
let modules = std::iter::once(std::sync::OnceLock::from(Ok(module))).chain(FAMILIES.iter().skip(1).map(|_| std::sync::OnceLock::new())).collect();
|
||||
let dmodule = init_resource(&device, "display shader module", || {
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("effectcraft display"),
|
||||
@@ -988,8 +1005,11 @@ impl GpuContext {
|
||||
median_kernel,
|
||||
bgl,
|
||||
bgl_ext,
|
||||
modules,
|
||||
layout,
|
||||
pipelines,
|
||||
unavailable,
|
||||
effect_pipelines,
|
||||
display_bgl,
|
||||
display,
|
||||
display_mip,
|
||||
@@ -1300,21 +1320,101 @@ impl GpuContext {
|
||||
|
||||
/// Readbacks (GPU → CPU) are possible: waited for natively, or deferred (a browser
|
||||
/// worker, [`crate::deferred`]). Impossible on the browser's main thread.
|
||||
/// Effect kernels the device couldn't build, with why: their effects render on the CPU.
|
||||
pub fn unavailable_kernels(&self) -> &[(&'static str, String)] {
|
||||
&self.unavailable
|
||||
/// Kernels the device couldn't build so far, with why: their effects render on the CPU.
|
||||
/// Effect kernels are built on first use, so one not used yet isn't listed.
|
||||
pub fn unavailable_kernels(&self) -> Vec<(&'static str, String)> {
|
||||
let mut out: Vec<_> = self
|
||||
.unavailable
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.effect_pipelines.iter().filter_map(|(e, (_, cell))| match cell.get() {
|
||||
Some(Err(why)) => Some((*e, why.clone())),
|
||||
_ => None,
|
||||
}))
|
||||
.collect();
|
||||
out.sort_unstable_by_key(|(e, _)| *e);
|
||||
out
|
||||
}
|
||||
|
||||
/// Whether kernel `entry` was built (an effect or Classic 3D kernel can be missing, #613).
|
||||
/// Whether kernel `entry` is available (an effect or Classic 3D kernel can be missing,
|
||||
/// #613); an effect kernel not built yet is built now.
|
||||
pub(crate) fn has_kernel(&self, entry: &str) -> bool {
|
||||
self.pipelines.contains_key(entry)
|
||||
self.pipeline(entry).is_some()
|
||||
}
|
||||
|
||||
/// Kernel `entry`'s pipeline: one built up front, or an effect kernel, built the first time
|
||||
/// it is asked for. `None` when the device doesn't have it.
|
||||
fn pipeline(&self, entry: &str) -> Option<&wgpu::ComputePipeline> {
|
||||
if let Some(p) = self.pipelines.get(entry) {
|
||||
return Some(p);
|
||||
}
|
||||
let Some((e, (family, cell))) = self.effect_pipelines.get_key_value(entry) else {
|
||||
if !self.unavailable.iter().any(|(e, _)| *e == entry) {
|
||||
log::error!("gpu: no kernel {entry}");
|
||||
}
|
||||
return None;
|
||||
};
|
||||
let built = cell.get_or_init(|| {
|
||||
let built = self.family_module(*family).and_then(|module| build_pipeline(&self.device, module, &self.layout, e));
|
||||
if let Err(why) = &built {
|
||||
log::warn!("gpu {}: kernel {e} unavailable, its effects render on the CPU: {why}", self.name);
|
||||
}
|
||||
built
|
||||
});
|
||||
built.as_ref().ok()
|
||||
}
|
||||
|
||||
/// Family `family`'s module (`FAMILIES`), built the first time it is asked for.
|
||||
fn family_module(&self, family: usize) -> Result<&wgpu::ShaderModule, String> {
|
||||
let (Some((label, files, _)), Some(cell)) = (FAMILIES.get(family), self.modules.get(family)) else {
|
||||
return Err(format!("no kernel family {family}"));
|
||||
};
|
||||
cell.get_or_init(|| {
|
||||
let median = [self.median_kernel.source()];
|
||||
let extra: &[&str] = if *label == MEDIAN_FAMILY { &median } else { &[] };
|
||||
let files: Vec<&str> = files.iter().chain(extra).copied().collect();
|
||||
kernel_module(&self.device, label, &files)
|
||||
})
|
||||
.as_ref()
|
||||
.map_err(Clone::clone)
|
||||
}
|
||||
|
||||
/// Build every effect kernel not built yet (tests; and a host that would rather pay the
|
||||
/// compile time up front than on first use).
|
||||
pub fn build_all_kernels(&self) {
|
||||
for e in self.effect_pipelines.keys() {
|
||||
let _ = self.pipeline(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// How many effect kernels have been built (tests).
|
||||
#[cfg(test)]
|
||||
pub(crate) fn effect_pipelines_built(&self) -> usize {
|
||||
self.effect_pipelines.values().filter(|(_, cell)| matches!(cell.get(), Some(Ok(_)))).count()
|
||||
}
|
||||
|
||||
/// Leave kernel `entry` out of `pipelines`/`effect_pipelines`, as if it had failed to build.
|
||||
#[cfg(test)]
|
||||
fn drop_from(
|
||||
pipelines: &mut HashMap<&'static str, wgpu::ComputePipeline>,
|
||||
unavailable: &mut Vec<(&'static str, String)>,
|
||||
effects: &mut EffectPipelines,
|
||||
entry: &'static str,
|
||||
) {
|
||||
let why = "dropped by a test".to_string();
|
||||
pipelines.remove(entry);
|
||||
match effects.get(entry).map(|(f, _)| *f) {
|
||||
Some(family) => {
|
||||
effects.insert(entry, (family, std::sync::OnceLock::from(Err(why))));
|
||||
}
|
||||
None => unavailable.push((entry, why)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Leave kernel `entry` out, as if it had failed to build (tests of the CPU fallback).
|
||||
#[cfg(test)]
|
||||
pub(crate) fn drop_kernel(&mut self, entry: &'static str) {
|
||||
self.pipelines.remove(entry);
|
||||
self.unavailable.push((entry, "dropped by a test".into()));
|
||||
Self::drop_from(&mut self.pipelines, &mut self.unavailable, &mut self.effect_pipelines, entry);
|
||||
}
|
||||
|
||||
/// A copy of this context sharing the same [`wgpu::Device`] and [`wgpu::Queue`] with
|
||||
@@ -1323,9 +1423,9 @@ impl GpuContext {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn without_kernel(&self, entry: &'static str) -> GpuContext {
|
||||
let mut pipelines = self.pipelines.clone();
|
||||
pipelines.remove(entry);
|
||||
let mut unavailable = self.unavailable.clone();
|
||||
unavailable.push((entry, "dropped by a test".into()));
|
||||
let mut effect_pipelines = self.effect_pipelines.clone();
|
||||
Self::drop_from(&mut pipelines, &mut unavailable, &mut effect_pipelines, entry);
|
||||
GpuContext {
|
||||
device: self.device.clone(),
|
||||
queue: self.queue.clone(),
|
||||
@@ -1334,8 +1434,11 @@ impl GpuContext {
|
||||
median_kernel: self.median_kernel,
|
||||
bgl: self.bgl.clone(),
|
||||
bgl_ext: self.bgl_ext.clone(),
|
||||
modules: self.modules.clone(),
|
||||
layout: self.layout.clone(),
|
||||
pipelines,
|
||||
unavailable,
|
||||
effect_pipelines,
|
||||
display_bgl: self.display_bgl.clone(),
|
||||
display: self.display.clone(),
|
||||
display_mip: self.display_mip.clone(),
|
||||
@@ -1766,10 +1869,7 @@ impl<'g> Enc<'g> {
|
||||
) {
|
||||
let KernelImages { src, aux, out } = images;
|
||||
let g = self.g;
|
||||
let Some(pipe) = g.pipelines.get(entry) else {
|
||||
if !g.unavailable.iter().any(|(e, _)| *e == entry) {
|
||||
log::error!("gpu: no kernel {entry}");
|
||||
}
|
||||
let Some(pipe) = g.pipeline(entry) else {
|
||||
self.missing = true;
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -422,7 +422,7 @@ impl Accelerator for Gpu {
|
||||
// Readback is synchronous-only, and a kernel that failed to build would make every stage
|
||||
// fall back to the CPU *after* paying the upload and a failed dispatch (and the fill
|
||||
// would still report the GPU path). Neither is a backend at all.
|
||||
if !self.ctx.can_wait() || self.ctx.unavailable_kernels().iter().any(|(e, _)| crate::inpaint::KERNELS.contains(e)) {
|
||||
if !self.ctx.can_wait() || !crate::inpaint::KERNELS.iter().all(|e| self.ctx.has_kernel(e)) {
|
||||
return None;
|
||||
}
|
||||
Some(self as &dyn effectcraft_raster::inpaint::FillBackend)
|
||||
|
||||
+20
-2
@@ -137,7 +137,7 @@ fn numerical_effects_remain_resident_on_vulkan() {
|
||||
return;
|
||||
}
|
||||
for entry in ["fxs_point", "fxt_point", "ftr_grid", "fp2_hextile"] {
|
||||
assert!(!g.ctx.unavailable_kernels().iter().any(|(name, _)| *name == entry), "{entry}: {:?}", g.ctx.unavailable_kernels());
|
||||
assert!(g.ctx.has_kernel(entry), "{entry}: {:?}", g.ctx.unavailable_kernels());
|
||||
}
|
||||
for (id, vals) in [
|
||||
("ec.stylize.posterize", vec![("level", n(4.0))]),
|
||||
@@ -1107,7 +1107,7 @@ fn effects_whose_kernels_failed_render_on_the_cpu() {
|
||||
ctx.drop_kernel("pointwise");
|
||||
ctx.drop_kernel("box_h");
|
||||
let names: Vec<&str> = ctx.unavailable_kernels().iter().map(|(e, _)| *e).collect();
|
||||
assert_eq!(names, ["pointwise", "box_h"]);
|
||||
assert_eq!(names, ["box_h", "pointwise"]);
|
||||
let g = Gpu::from_context(ctx);
|
||||
for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {
|
||||
let mut s = Scene::new(depth);
|
||||
@@ -1127,6 +1127,24 @@ fn effects_whose_kernels_failed_render_on_the_cpu() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #1590: effect kernels are built on first use, not with the context (building all of them
|
||||
/// up front kept Chrome's GPU process busy past its watchdog). Once asked for, every one of
|
||||
/// them builds on this device: a kernel no test dispatches still can't break unnoticed.
|
||||
#[test]
|
||||
fn effect_kernels_build_on_first_use() {
|
||||
if gpu().is_none() {
|
||||
return;
|
||||
}
|
||||
let Ok(ctx) = pollster::block_on(crate::context::GpuContext::request()) else { return };
|
||||
let built = |ctx: &crate::context::GpuContext| ctx.effect_pipelines_built();
|
||||
assert_eq!(built(&ctx), 0, "no effect kernel is built with the context");
|
||||
let started = std::time::Instant::now();
|
||||
ctx.build_all_kernels();
|
||||
eprintln!("{}: built {} effect kernels in {:.2?}", ctx.name, built(&ctx), started.elapsed());
|
||||
assert_eq!(ctx.unavailable_kernels(), vec![], "every effect kernel builds on {}", ctx.name);
|
||||
assert!(ctx.has_kernel("classic3d") && ctx.has_kernel("math_probe"), "the up-front kernels build on {}", ctx.name);
|
||||
}
|
||||
|
||||
/// WGSL cannot read the CPU's constants, so Glow's bright pass repeats them as literals; this
|
||||
/// ties them to `effects::GLOW_GAIN` / `GLOW_KNEE` (runs without a GPU).
|
||||
#[test]
|
||||
|
||||
@@ -44,7 +44,7 @@ fn rect_hole(w: u32, h: u32, x0: u32, y0: u32, s: u32) -> Vec<bool> {
|
||||
#[test]
|
||||
fn the_inpaint_kernels_are_available() {
|
||||
let Some(g) = gpu() else { return };
|
||||
let bad: Vec<&str> = g.ctx.unavailable_kernels().iter().filter(|(e, _)| crate::inpaint::KERNELS.contains(e)).map(|(e, _)| *e).collect();
|
||||
let bad: Vec<&str> = crate::inpaint::KERNELS.iter().copied().filter(|e| !g.ctx.has_kernel(e)).collect();
|
||||
assert!(bad.is_empty(), "inpaint kernels did not build on {}: {bad:?}", g.ctx.name);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user