Move surface_configure out of Global

This commit is contained in:
Andy Leiserson 2025-10-02 16:23:50 -07:00 committed by Teodor Tanasoaia
parent 21ea8f2979
commit 73d1334ba3
2 changed files with 276 additions and 267 deletions

View File

@ -1737,276 +1737,15 @@ impl Global {
device_id: DeviceId,
config: &wgt::SurfaceConfiguration<Vec<TextureFormat>>,
) -> Option<present::ConfigureSurfaceError> {
use present::ConfigureSurfaceError as E;
profiling::scope!("surface_configure");
let device = self.hub.devices.get(device_id);
let surface = self.surfaces.get(surface_id);
fn validate_surface_configuration(
config: &mut hal::SurfaceConfiguration,
caps: &hal::SurfaceCapabilities,
max_texture_dimension_2d: u32,
) -> Result<(), E> {
let width = config.extent.width;
let height = config.extent.height;
if width > max_texture_dimension_2d || height > max_texture_dimension_2d {
return Err(E::TooLarge {
width,
height,
max_texture_dimension_2d,
});
}
if !caps.present_modes.contains(&config.present_mode) {
// Automatic present mode checks.
//
// The "Automatic" modes are never supported by the backends.
let fallbacks = match config.present_mode {
wgt::PresentMode::AutoVsync => {
&[wgt::PresentMode::FifoRelaxed, wgt::PresentMode::Fifo][..]
}
// Always end in FIFO to make sure it's always supported
wgt::PresentMode::AutoNoVsync => &[
wgt::PresentMode::Immediate,
wgt::PresentMode::Mailbox,
wgt::PresentMode::Fifo,
][..],
_ => {
return Err(E::UnsupportedPresentMode {
requested: config.present_mode,
available: caps.present_modes.clone(),
});
}
};
let new_mode = fallbacks
.iter()
.copied()
.find(|fallback| caps.present_modes.contains(fallback))
.unwrap_or_else(|| {
unreachable!(
"Fallback system failed to choose present mode. \
This is a bug. Mode: {:?}, Options: {:?}",
config.present_mode, &caps.present_modes
);
});
api_log!(
"Automatically choosing presentation mode by rule {:?}. Chose {new_mode:?}",
config.present_mode
);
config.present_mode = new_mode;
}
if !caps.formats.contains(&config.format) {
return Err(E::UnsupportedFormat {
requested: config.format,
available: caps.formats.clone(),
});
}
if !caps
.composite_alpha_modes
.contains(&config.composite_alpha_mode)
{
let new_alpha_mode = 'alpha: {
// Automatic alpha mode checks.
let fallbacks = match config.composite_alpha_mode {
wgt::CompositeAlphaMode::Auto => &[
wgt::CompositeAlphaMode::Opaque,
wgt::CompositeAlphaMode::Inherit,
][..],
_ => {
return Err(E::UnsupportedAlphaMode {
requested: config.composite_alpha_mode,
available: caps.composite_alpha_modes.clone(),
});
}
};
for &fallback in fallbacks {
if caps.composite_alpha_modes.contains(&fallback) {
break 'alpha fallback;
}
}
unreachable!(
"Fallback system failed to choose alpha mode. This is a bug. \
AlphaMode: {:?}, Options: {:?}",
config.composite_alpha_mode, &caps.composite_alpha_modes
);
};
api_log!(
"Automatically choosing alpha mode by rule {:?}. Chose {new_alpha_mode:?}",
config.composite_alpha_mode
);
config.composite_alpha_mode = new_alpha_mode;
}
if !caps.usage.contains(config.usage) {
return Err(E::UnsupportedUsage {
requested: config.usage,
available: caps.usage,
});
}
if width == 0 || height == 0 {
return Err(E::ZeroArea);
}
Ok(())
#[cfg(feature = "trace")]
if let Some(ref mut trace) = *device.trace.lock() {
trace.add(trace::Action::ConfigureSurface(surface_id, config.clone()));
}
log::debug!("configuring surface with {config:?}");
let error = 'error: {
// User callbacks must not be called while we are holding locks.
let user_callbacks;
{
let device = self.hub.devices.get(device_id);
#[cfg(feature = "trace")]
if let Some(ref mut trace) = *device.trace.lock() {
trace.add(trace::Action::ConfigureSurface(surface_id, config.clone()));
}
if let Err(e) = device.check_is_valid() {
break 'error e.into();
}
let surface = self.surfaces.get(surface_id);
let caps = match surface.get_capabilities(&device.adapter) {
Ok(caps) => caps,
Err(_) => break 'error E::UnsupportedQueueFamily,
};
let mut hal_view_formats = Vec::new();
for format in config.view_formats.iter() {
if *format == config.format {
continue;
}
if !caps.formats.contains(&config.format) {
break 'error E::UnsupportedFormat {
requested: config.format,
available: caps.formats,
};
}
if config.format.remove_srgb_suffix() != format.remove_srgb_suffix() {
break 'error E::InvalidViewFormat(*format, config.format);
}
hal_view_formats.push(*format);
}
if !hal_view_formats.is_empty() {
if let Err(missing_flag) =
device.require_downlevel_flags(wgt::DownlevelFlags::SURFACE_VIEW_FORMATS)
{
break 'error E::MissingDownlevelFlags(missing_flag);
}
}
let maximum_frame_latency = config.desired_maximum_frame_latency.clamp(
*caps.maximum_frame_latency.start(),
*caps.maximum_frame_latency.end(),
);
let mut hal_config = hal::SurfaceConfiguration {
maximum_frame_latency,
present_mode: config.present_mode,
composite_alpha_mode: config.alpha_mode,
format: config.format,
extent: wgt::Extent3d {
width: config.width,
height: config.height,
depth_or_array_layers: 1,
},
usage: conv::map_texture_usage(
config.usage,
hal::FormatAspects::COLOR,
wgt::TextureFormatFeatureFlags::STORAGE_READ_ONLY
| wgt::TextureFormatFeatureFlags::STORAGE_WRITE_ONLY
| wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE,
),
view_formats: hal_view_formats,
};
if let Err(error) = validate_surface_configuration(
&mut hal_config,
&caps,
device.limits.max_texture_dimension_2d,
) {
break 'error error;
}
// Wait for all work to finish before configuring the surface.
let snatch_guard = device.snatchable_lock.read();
let fence = device.fence.read();
let maintain_result;
(user_callbacks, maintain_result) =
device.maintain(fence, wgt::PollType::wait_indefinitely(), snatch_guard);
match maintain_result {
// We're happy
Ok(wgt::PollStatus::QueueEmpty) => {}
Ok(wgt::PollStatus::WaitSucceeded) => {
// After the wait, the queue should be empty. It can only be non-empty
// if another thread is submitting at the same time.
break 'error E::GpuWaitTimeout;
}
Ok(wgt::PollStatus::Poll) => {
unreachable!("Cannot get a Poll result from a Wait action.")
}
Err(WaitIdleError::Timeout) if cfg!(target_arch = "wasm32") => {
// On wasm, you cannot actually successfully wait for the surface.
// However WebGL does not actually require you do this, so ignoring
// the failure is totally fine. See https://github.com/gfx-rs/wgpu/issues/7363
}
Err(e) => {
break 'error e.into();
}
}
// All textures must be destroyed before the surface can be re-configured.
if let Some(present) = surface.presentation.lock().take() {
if present.acquired_texture.is_some() {
break 'error E::PreviousOutputExists;
}
}
// TODO: Texture views may still be alive that point to the texture.
// this will allow the user to render to the surface texture, long after
// it has been removed.
//
// https://github.com/gfx-rs/wgpu/issues/4105
let surface_raw = surface.raw(device.backend()).unwrap();
match unsafe { surface_raw.configure(device.raw(), &hal_config) } {
Ok(()) => (),
Err(error) => {
break 'error match error {
hal::SurfaceError::Outdated | hal::SurfaceError::Lost => {
E::InvalidSurface
}
hal::SurfaceError::Device(error) => {
E::Device(device.handle_hal_error(error))
}
hal::SurfaceError::Other(message) => {
log::error!("surface configuration failed: {message}");
E::InvalidSurface
}
}
}
}
let mut presentation = surface.presentation.lock();
*presentation = Some(present::Presentation {
device,
config: config.clone(),
acquired_texture: None,
});
}
user_callbacks.fire();
return None;
};
Some(error)
device.configure_surface(&surface, config)
}
/// Check `device_id` for freeable resources and completed buffer mappings.

View File

@ -24,6 +24,7 @@ use wgt::{
#[cfg(feature = "trace")]
use crate::device::trace;
use crate::{
api_log,
binding_model::{self, BindGroup, BindGroupLayout, BindGroupLayoutEntryError},
command, conv,
device::{
@ -39,6 +40,7 @@ use crate::{
lock::{rank, Mutex, RwLock},
pipeline,
pool::ResourcePool,
present,
resource::{
self, Buffer, ExternalTexture, Fallible, Labeled, ParentDevice, QuerySet,
RawResourceAccess, Sampler, StagingBuffer, Texture, TextureView,
@ -4693,6 +4695,274 @@ impl Device {
Ok(query_set)
}
pub fn configure_surface(
self: &Arc<Self>,
surface: &crate::instance::Surface,
config: &wgt::SurfaceConfiguration<Vec<TextureFormat>>,
) -> Option<present::ConfigureSurfaceError> {
use present::ConfigureSurfaceError as E;
profiling::scope!("surface_configure");
fn validate_surface_configuration(
config: &mut hal::SurfaceConfiguration,
caps: &hal::SurfaceCapabilities,
max_texture_dimension_2d: u32,
) -> Result<(), E> {
let width = config.extent.width;
let height = config.extent.height;
if width > max_texture_dimension_2d || height > max_texture_dimension_2d {
return Err(E::TooLarge {
width,
height,
max_texture_dimension_2d,
});
}
if !caps.present_modes.contains(&config.present_mode) {
// Automatic present mode checks.
//
// The "Automatic" modes are never supported by the backends.
let fallbacks = match config.present_mode {
wgt::PresentMode::AutoVsync => {
&[wgt::PresentMode::FifoRelaxed, wgt::PresentMode::Fifo][..]
}
// Always end in FIFO to make sure it's always supported
wgt::PresentMode::AutoNoVsync => &[
wgt::PresentMode::Immediate,
wgt::PresentMode::Mailbox,
wgt::PresentMode::Fifo,
][..],
_ => {
return Err(E::UnsupportedPresentMode {
requested: config.present_mode,
available: caps.present_modes.clone(),
});
}
};
let new_mode = fallbacks
.iter()
.copied()
.find(|fallback| caps.present_modes.contains(fallback))
.unwrap_or_else(|| {
unreachable!(
"Fallback system failed to choose present mode. \
This is a bug. Mode: {:?}, Options: {:?}",
config.present_mode, &caps.present_modes
);
});
api_log!(
"Automatically choosing presentation mode by rule {:?}. Chose {new_mode:?}",
config.present_mode
);
config.present_mode = new_mode;
}
if !caps.formats.contains(&config.format) {
return Err(E::UnsupportedFormat {
requested: config.format,
available: caps.formats.clone(),
});
}
if !caps
.composite_alpha_modes
.contains(&config.composite_alpha_mode)
{
let new_alpha_mode = 'alpha: {
// Automatic alpha mode checks.
let fallbacks = match config.composite_alpha_mode {
wgt::CompositeAlphaMode::Auto => &[
wgt::CompositeAlphaMode::Opaque,
wgt::CompositeAlphaMode::Inherit,
][..],
_ => {
return Err(E::UnsupportedAlphaMode {
requested: config.composite_alpha_mode,
available: caps.composite_alpha_modes.clone(),
});
}
};
for &fallback in fallbacks {
if caps.composite_alpha_modes.contains(&fallback) {
break 'alpha fallback;
}
}
unreachable!(
"Fallback system failed to choose alpha mode. This is a bug. \
AlphaMode: {:?}, Options: {:?}",
config.composite_alpha_mode, &caps.composite_alpha_modes
);
};
api_log!(
"Automatically choosing alpha mode by rule {:?}. Chose {new_alpha_mode:?}",
config.composite_alpha_mode
);
config.composite_alpha_mode = new_alpha_mode;
}
if !caps.usage.contains(config.usage) {
return Err(E::UnsupportedUsage {
requested: config.usage,
available: caps.usage,
});
}
if width == 0 || height == 0 {
return Err(E::ZeroArea);
}
Ok(())
}
log::debug!("configuring surface with {config:?}");
let error = 'error: {
// User callbacks must not be called while we are holding locks.
let user_callbacks;
{
if let Err(e) = self.check_is_valid() {
break 'error e.into();
}
let caps = match surface.get_capabilities(&self.adapter) {
Ok(caps) => caps,
Err(_) => break 'error E::UnsupportedQueueFamily,
};
let mut hal_view_formats = Vec::new();
for format in config.view_formats.iter() {
if *format == config.format {
continue;
}
if !caps.formats.contains(&config.format) {
break 'error E::UnsupportedFormat {
requested: config.format,
available: caps.formats,
};
}
if config.format.remove_srgb_suffix() != format.remove_srgb_suffix() {
break 'error E::InvalidViewFormat(*format, config.format);
}
hal_view_formats.push(*format);
}
if !hal_view_formats.is_empty() {
if let Err(missing_flag) =
self.require_downlevel_flags(wgt::DownlevelFlags::SURFACE_VIEW_FORMATS)
{
break 'error E::MissingDownlevelFlags(missing_flag);
}
}
let maximum_frame_latency = config.desired_maximum_frame_latency.clamp(
*caps.maximum_frame_latency.start(),
*caps.maximum_frame_latency.end(),
);
let mut hal_config = hal::SurfaceConfiguration {
maximum_frame_latency,
present_mode: config.present_mode,
composite_alpha_mode: config.alpha_mode,
format: config.format,
extent: wgt::Extent3d {
width: config.width,
height: config.height,
depth_or_array_layers: 1,
},
usage: conv::map_texture_usage(
config.usage,
hal::FormatAspects::COLOR,
wgt::TextureFormatFeatureFlags::STORAGE_READ_ONLY
| wgt::TextureFormatFeatureFlags::STORAGE_WRITE_ONLY
| wgt::TextureFormatFeatureFlags::STORAGE_READ_WRITE,
),
view_formats: hal_view_formats,
};
if let Err(error) = validate_surface_configuration(
&mut hal_config,
&caps,
self.limits.max_texture_dimension_2d,
) {
break 'error error;
}
// Wait for all work to finish before configuring the surface.
let snatch_guard = self.snatchable_lock.read();
let fence = self.fence.read();
let maintain_result;
(user_callbacks, maintain_result) =
self.maintain(fence, wgt::PollType::wait_indefinitely(), snatch_guard);
match maintain_result {
// We're happy
Ok(wgt::PollStatus::QueueEmpty) => {}
Ok(wgt::PollStatus::WaitSucceeded) => {
// After the wait, the queue should be empty. It can only be non-empty
// if another thread is submitting at the same time.
break 'error E::GpuWaitTimeout;
}
Ok(wgt::PollStatus::Poll) => {
unreachable!("Cannot get a Poll result from a Wait action.")
}
Err(WaitIdleError::Timeout) if cfg!(target_arch = "wasm32") => {
// On wasm, you cannot actually successfully wait for the surface.
// However WebGL does not actually require you do this, so ignoring
// the failure is totally fine. See https://github.com/gfx-rs/wgpu/issues/7363
}
Err(e) => {
break 'error e.into();
}
}
// All textures must be destroyed before the surface can be re-configured.
if let Some(present) = surface.presentation.lock().take() {
if present.acquired_texture.is_some() {
break 'error E::PreviousOutputExists;
}
}
// TODO: Texture views may still be alive that point to the texture.
// this will allow the user to render to the surface texture, long after
// it has been removed.
//
// https://github.com/gfx-rs/wgpu/issues/4105
let surface_raw = surface.raw(self.backend()).unwrap();
match unsafe { surface_raw.configure(self.raw(), &hal_config) } {
Ok(()) => (),
Err(error) => {
break 'error match error {
hal::SurfaceError::Outdated | hal::SurfaceError::Lost => {
E::InvalidSurface
}
hal::SurfaceError::Device(error) => {
E::Device(self.handle_hal_error(error))
}
hal::SurfaceError::Other(message) => {
log::error!("surface configuration failed: {message}");
E::InvalidSurface
}
}
}
}
let mut presentation = surface.presentation.lock();
*presentation = Some(present::Presentation {
device: Arc::clone(self),
config: config.clone(),
acquired_texture: None,
});
}
user_callbacks.fire();
return None;
};
Some(error)
}
fn lose(&self, message: &str) {
// Follow the steps at https://gpuweb.github.io/gpuweb/#lose-the-device.