Automatically choose texture format based on adapter (#224)

* Automatically choose texture format based on adapter

* Use compatible_surface properly
This commit is contained in:
Max Ammann 2022-12-14 10:13:05 +01:00 committed by GitHub
parent 516d642079
commit 4fcd142e0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 168 additions and 185 deletions

View File

@ -37,23 +37,18 @@ impl Node for CopySurfaceBufferNode {
Head::Headless(buffered_texture) => {
let size = surface.size();
command_encoder.copy_texture_to_buffer(
buffered_texture.texture.as_image_copy(),
buffered_texture.copy_texture(),
wgpu::ImageCopyBuffer {
buffer: &buffered_texture.output_buffer,
buffer: &buffered_texture.buffer(),
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(
std::num::NonZeroU32::new(
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
)
.unwrap(), // TODO: remove unwrap
),
bytes_per_row: Some(buffered_texture.bytes_per_row()),
rows_per_image: None,
},
},
wgpu::Extent3d {
width: size.width() as u32,
height: size.height() as u32,
width: size.width(),
height: size.height(),
depth_or_array_layers: 1,
},
);

View File

@ -12,6 +12,7 @@ use crate::{
InitializationResult, InitializedRenderer, RendererBuilder, UninitializedRenderer,
},
create_default_render_graph,
error::RenderError,
graph::RenderGraphError,
register_default_render_stages,
},
@ -30,7 +31,7 @@ pub enum MapError {
#[error("initializing render graph failed")]
RenderGraphInit(RenderGraphError),
#[error("initializing device failed")]
DeviceInit,
DeviceInit(RenderError),
}
pub enum MapContextState {
@ -92,7 +93,7 @@ where
.build()
.initialize_renderer::<E::MapWindowConfig>(&self.window)
.await
.map_err(|e| MapError::DeviceInit)?;
.map_err(|e| MapError::DeviceInit(e))?;
let window_size = self.window.size();

View File

@ -1,22 +1,15 @@
use std::fmt;
use thiserror::Error;
use crate::render::graph::RenderGraphError;
#[derive(Debug)]
#[derive(Error, Debug)]
pub enum RenderError {
Surface(wgpu::SurfaceError),
Graph(RenderGraphError),
Device(wgpu::RequestDeviceError),
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RenderError::Surface(e) => write!(f, "{}", e),
RenderError::Graph(e) => write!(f, "{:?}", e),
RenderError::Device(e) => write!(f, "{}", e),
}
}
#[error("error in surface")]
Surface(#[from] wgpu::SurfaceError),
#[error("error in render graph")]
Graph(#[from] RenderGraphError),
#[error("error while requesting device")]
RequestDevice(#[from] wgpu::RequestDeviceError),
}
impl RenderError {
@ -30,21 +23,3 @@ impl RenderError {
}
}
}
impl From<RenderGraphError> for RenderError {
fn from(e: RenderGraphError) -> Self {
RenderError::Graph(e)
}
}
impl From<wgpu::SurfaceError> for RenderError {
fn from(e: wgpu::SurfaceError) -> Self {
RenderError::Surface(e)
}
}
impl From<wgpu::RequestDeviceError> for RenderError {
fn from(e: wgpu::RequestDeviceError) -> Self {
RenderError::Device(e)
}
}

View File

@ -57,6 +57,7 @@ pub use stages::register_default_render_stages;
use crate::{
render::{
error::RenderError,
graph::{EmptyNode, RenderGraph, RenderGraphError},
main_pass::{MainPassDriverNode, MainPassNode},
},
@ -142,7 +143,7 @@ pub struct Renderer {
pub instance: wgpu::Instance,
pub device: Arc<wgpu::Device>, // TODO: Arc is needed for headless rendering. Is there a simpler solution?
pub queue: wgpu::Queue,
pub adapter_info: wgpu::AdapterInfo,
pub adapter: wgpu::Adapter,
pub wgpu_settings: WgpuSettings,
pub settings: RendererSettings,
@ -157,30 +158,27 @@ impl Renderer {
window: &MW,
wgpu_settings: WgpuSettings,
settings: RendererSettings,
) -> Result<Self, wgpu::RequestDeviceError>
) -> Result<Self, RenderError>
where
MW: MapWindow + HeadedMapWindow,
{
let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all()));
let surface = Surface::from_window(&instance, window, &settings);
let surface: wgpu::Surface = unsafe { instance.create_surface(window.raw()) };
let compatible_surface = match &surface.head() {
Head::Headed(window_head) => Some(window_head.surface()),
Head::Headless(_) => None,
};
let (device, queue, adapter_info) = Self::request_device(
let (adapter, device, queue) = Self::request_device(
&instance,
&wgpu_settings,
&wgpu::RequestAdapterOptions {
power_preference: wgpu_settings.power_preference,
force_fallback_adapter: false,
compatible_surface,
compatible_surface: Some(&surface),
},
)
.await?;
let surface = Surface::from_surface(surface, &adapter, window, &settings);
match surface.head() {
Head::Headed(window) => window.configure(&device),
Head::Headless(_) => {}
@ -190,7 +188,7 @@ impl Renderer {
instance,
device: Arc::new(device),
queue,
adapter_info,
adapter,
wgpu_settings,
settings,
state: RenderState::new(surface),
@ -201,13 +199,13 @@ impl Renderer {
window: &MW,
wgpu_settings: WgpuSettings,
settings: RendererSettings,
) -> Result<Self, wgpu::RequestDeviceError>
) -> Result<Self, RenderError>
where
MW: MapWindow,
{
let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all()));
let (device, queue, adapter_info) = Self::request_device(
let (adapter, device, queue) = Self::request_device(
&instance,
&wgpu_settings,
&wgpu::RequestAdapterOptions {
@ -224,7 +222,7 @@ impl Renderer {
instance,
device: Arc::new(device),
queue,
adapter_info,
adapter,
wgpu_settings,
settings,
state: RenderState::new(surface),
@ -240,7 +238,7 @@ impl Renderer {
instance: &wgpu::Instance,
settings: &WgpuSettings,
request_adapter_options: &wgpu::RequestAdapterOptions<'_>,
) -> Result<(wgpu::Device, wgpu::Queue, wgpu::AdapterInfo), wgpu::RequestDeviceError> {
) -> Result<(wgpu::Adapter, wgpu::Device, wgpu::Queue), wgpu::RequestDeviceError> {
let adapter = instance
.request_adapter(request_adapter_options)
.await
@ -384,7 +382,7 @@ impl Renderer {
trace_path,
)
.await?;
Ok((device, queue, adapter_info))
Ok((adapter, device, queue))
}
pub fn instance(&self) -> &wgpu::Instance {

View File

@ -1,9 +1,9 @@
//! Utilities for handling surfaces which can be either headless or headed. A headed surface has
//! a handle to a window. A headless surface renders to a texture.
use std::{mem::size_of, sync::Arc};
use std::{mem::size_of, num::NonZeroU32, sync::Arc};
use wgpu::CompositeAlphaMode;
use log::debug;
use crate::{
render::{eventually::HasChanged, resource::texture::TextureView, settings::RendererSettings},
@ -11,41 +11,54 @@ use crate::{
};
pub struct BufferDimensions {
pub width: usize,
pub height: usize,
pub unpadded_bytes_per_row: usize,
pub padded_bytes_per_row: usize,
pub width: u32,
pub height: u32,
pub unpadded_bytes_per_row: NonZeroU32,
pub padded_bytes_per_row: NonZeroU32,
}
impl BufferDimensions {
fn new(width: usize, height: usize) -> Self {
let bytes_per_pixel = size_of::<u32>();
let unpadded_bytes_per_row = width * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
fn new(size: WindowSize) -> Self {
let bytes_per_pixel = size_of::<u32>() as u32;
let unpadded_bytes_per_row = size.width() * bytes_per_pixel;
let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
Self {
width,
height,
unpadded_bytes_per_row,
padded_bytes_per_row,
width: size.width(),
height: size.height(),
unpadded_bytes_per_row: NonZeroU32::new(unpadded_bytes_per_row)
.expect("can not be zero"), // expect is fine because this can never happen
padded_bytes_per_row: NonZeroU32::new(padded_bytes_per_row).expect("can not be zero"),
}
}
}
pub struct WindowHead {
surface: wgpu::Surface,
surface_config: wgpu::SurfaceConfiguration,
size: WindowSize,
format: wgpu::TextureFormat,
present_mode: wgpu::PresentMode,
}
impl WindowHead {
pub fn resize_and_configure(&mut self, width: u32, height: u32, device: &wgpu::Device) {
self.surface_config.height = width;
self.surface_config.width = height;
self.surface.configure(device, &self.surface_config);
self.size = WindowSize::new(width, height).unwrap();
self.configure(device);
}
pub fn configure(&self, device: &wgpu::Device) {
self.surface.configure(device, &self.surface_config);
let surface_config = wgpu::SurfaceConfiguration {
alpha_mode: wgpu::CompositeAlphaMode::Auto,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: self.format,
width: self.size.width(),
height: self.size.height(),
present_mode: self.present_mode,
};
self.surface.configure(device, &surface_config);
}
pub fn recreate_surface<MW>(&mut self, window: &MW, instance: &wgpu::Instance)
@ -54,15 +67,17 @@ impl WindowHead {
{
self.surface = unsafe { instance.create_surface(window.raw()) };
}
pub fn surface(&self) -> &wgpu::Surface {
&self.surface
}
}
pub struct BufferedTextureHead {
pub texture: wgpu::Texture,
pub output_buffer: wgpu::Buffer,
pub buffer_dimensions: BufferDimensions,
texture: wgpu::Texture,
texture_format: wgpu::TextureFormat,
output_buffer: wgpu::Buffer,
buffer_dimensions: BufferDimensions,
}
#[cfg(feature = "headless")]
@ -95,17 +110,33 @@ impl BufferedTextureHead {
let mut png_writer = png_encoder
.write_header()
.unwrap() // TODO: Remove unwrap
.into_stream_writer_with_size(self.buffer_dimensions.unpadded_bytes_per_row)
.into_stream_writer_with_size(
self.buffer_dimensions.unpadded_bytes_per_row.get() as usize
)
.unwrap(); // TODO: Remove unwrap
// from the padded_buffer we write just the unpadded bytes into the image
for chunk in padded_buffer.chunks(self.buffer_dimensions.padded_bytes_per_row) {
for chunk in
padded_buffer.chunks(self.buffer_dimensions.padded_bytes_per_row.get() as usize)
{
png_writer
.write_all(&chunk[..self.buffer_dimensions.unpadded_bytes_per_row])
.write_all(&chunk[..self.buffer_dimensions.unpadded_bytes_per_row.get() as usize])
.unwrap(); // TODO: Remove unwrap
}
png_writer.finish().unwrap(); // TODO: Remove unwrap
}
pub fn copy_texture(&self) -> wgpu::ImageCopyTexture<'_> {
self.texture.as_image_copy()
}
pub fn buffer(&self) -> &wgpu::Buffer {
&self.output_buffer
}
pub fn bytes_per_row(&self) -> NonZeroU32 {
self.buffer_dimensions.padded_bytes_per_row
}
}
pub enum Head {
@ -119,8 +150,9 @@ pub struct Surface {
}
impl Surface {
pub fn from_window<MW>(
instance: &wgpu::Instance,
pub fn from_surface<MW>(
surface: wgpu::Surface,
adapter: &wgpu::Adapter,
window: &MW,
settings: &RendererSettings,
) -> Self
@ -128,22 +160,24 @@ impl Surface {
MW: MapWindow + HeadedMapWindow,
{
let size = window.size();
let surface_config = wgpu::SurfaceConfiguration {
alpha_mode: CompositeAlphaMode::Auto,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: settings.texture_format,
width: size.width(),
height: size.height(),
present_mode: settings.present_mode,
};
let surface = unsafe { instance.create_surface(window.raw()) };
debug!(
"supported formats by adapter: {:?}",
surface.get_supported_formats(adapter)
);
let format = settings
.texture_format
.or_else(|| surface.get_supported_formats(adapter).first().cloned())
.unwrap_or(wgpu::TextureFormat::Rgba8Unorm);
Self {
size,
head: Head::Headed(WindowHead {
surface,
surface_config,
size,
format,
present_mode: settings.present_mode,
}),
}
}
@ -159,17 +193,22 @@ impl Surface {
// So we calculate padded_bytes_per_row by rounding unpadded_bytes_per_row
// up to the next multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT.
// https://en.wikipedia.org/wiki/Data_structure_alignment#Computing_padding
let buffer_dimensions =
BufferDimensions::new(size.width() as usize, size.height() as usize);
let buffer_dimensions = BufferDimensions::new(size);
// The output buffer lets us retrieve the data as an array
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("BufferedTextureHead buffer"),
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
size: (buffer_dimensions.padded_bytes_per_row.get() * buffer_dimensions.height) as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let texture = device.create_texture(&wgpu::TextureDescriptor {
// FIXME: Is this a sane default?
let format = settings
.texture_format
.unwrap_or(wgpu::TextureFormat::Rgba8Unorm);
let texture_descriptor = wgpu::TextureDescriptor {
label: Some("Surface texture"),
size: wgpu::Extent3d {
width: size.width(),
@ -179,20 +218,29 @@ impl Surface {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: settings.texture_format,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
});
};
let texture = device.create_texture(&texture_descriptor);
Self {
size,
head: Head::Headless(Arc::new(BufferedTextureHead {
texture,
texture_format: format,
output_buffer,
buffer_dimensions,
})),
}
}
pub fn surface_format(&self) -> wgpu::TextureFormat {
match &self.head {
Head::Headed(headed) => headed.format,
Head::Headless(headless) => headless.texture_format,
}
}
#[tracing::instrument(name = "create_view", skip_all)]
pub fn create_view(&self, device: &wgpu::Device) -> TextureView {
match &self.head {
@ -261,9 +309,10 @@ impl Surface {
}
impl HasChanged for WindowHead {
/// Tuple of width and height
type Criteria = (u32, u32);
fn has_changed(&self, criteria: &Self::Criteria) -> bool {
self.surface_config.width != criteria.0 || self.surface_config.height != criteria.1
self.size.width() != criteria.0 || self.size.height() != criteria.1
}
}

View File

@ -103,7 +103,8 @@ impl Default for Msaa {
#[derive(Clone, Copy)]
pub struct RendererSettings {
pub msaa: Msaa,
pub texture_format: TextureFormat,
/// Explicitly set a texture format or let the renderer automatically choose one
pub texture_format: Option<TextureFormat>,
pub depth_texture_format: TextureFormat,
/// Present mode for surfaces if a surface is used.
pub present_mode: PresentMode,
@ -113,26 +114,7 @@ impl Default for RendererSettings {
fn default() -> Self {
Self {
msaa: Msaa::default(),
// WebGPU
#[cfg(all(target_arch = "wasm32", not(feature = "web-webgl")))]
texture_format: wgpu::TextureFormat::Bgra8Unorm,
// WebGL
#[cfg(all(target_arch = "wasm32", feature = "web-webgl"))]
texture_format: wgpu::TextureFormat::Rgba8UnormSrgb,
// Vulkan Android
#[cfg(target_os = "android")]
texture_format: wgpu::TextureFormat::Rgba8Unorm,
/// MacOS and iOS (Metal).
#[cfg(any(target_os = "macos", target_os = "ios"))]
texture_format: wgpu::TextureFormat::Bgra8UnormSrgb,
/// For Vulkan/OpenGL
#[cfg(not(any(
target_os = "android",
target_os = "macos",
any(target_os = "macos", target_os = "ios"),
target_arch = "wasm32"
)))]
texture_format: TextureFormat::Bgra8UnormSrgb,
texture_format: None,
depth_texture_format: TextureFormat::Depth24PlusStencil8,
present_mode: PresentMode::AutoVsync,

View File

@ -63,7 +63,7 @@ impl Stage for ResourceStage {
Some(Texture::new(
Some("multisampling texture"),
device,
settings.texture_format,
surface.surface_format(),
size.width(),
size.height(),
settings.msaa,
@ -96,7 +96,7 @@ impl Stage for ResourceStage {
state.tile_pipeline.initialize(|| {
let tile_shader = shaders::TileShader {
format: settings.texture_format,
format: surface.surface_format(),
};
let pipeline = TilePipeline::new(
@ -120,7 +120,7 @@ impl Stage for ResourceStage {
state.mask_pipeline.initialize(|| {
let mask_shader = shaders::TileMaskShader {
format: settings.texture_format,
format: surface.surface_format(),
draw_colors: false,
};

View File

@ -1,5 +1,7 @@
//! Utilities for the window system.
use std::num::NonZeroU32;
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
@ -31,23 +33,31 @@ pub trait MapWindowConfig: 'static {
/// Window size with a width and an height in pixels.
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct WindowSize {
width: u32,
height: u32,
width: NonZeroU32,
height: NonZeroU32,
}
impl WindowSize {
pub fn new(width: u32, height: u32) -> Option<Self> {
if width == 0 || height == 0 {
return None;
}
Some(Self { width, height })
Some(Self {
width: NonZeroU32::new(width)?,
height: NonZeroU32::new(height)?,
})
}
pub fn width(&self) -> u32 {
self.width.get()
}
pub fn width_non_zero(&self) -> NonZeroU32 {
self.width
}
pub fn height(&self) -> u32 {
self.height.get()
}
pub fn height_non_zero(&self) -> NonZeroU32 {
self.height
}
}

View File

@ -2,11 +2,10 @@
use std::{
borrow::Cow,
error::Error,
fmt::{Display, Formatter},
};
use js_sys::{Error as JSError, TypeError};
use js_sys::TypeError;
use maplibre::io::apc::{CallError, ProcedureError};
use thiserror::Error;
use wasm_bindgen::{JsCast, JsValue};
@ -34,7 +33,7 @@ impl From<JsValue> for WebError {
.as_string() else { return WebError::InvalidMessage; };
WebError::TypeError(message.into())
} else if let Some(error) = value.dyn_ref::<JSError>() {
} else if let Some(error) = value.dyn_ref::<js_sys::Error>() {
let Some(message) = error
.message()
.as_string() else { return WebError::InvalidMessage; };
@ -48,41 +47,25 @@ impl From<JsValue> for WebError {
/// Wraps several unrelated errors and implements Into<JSValue>. This should be used in Rust
/// functions called from JS-land as return error type.
#[derive(Debug)]
pub enum WrappedError {
ProcedureError(ProcedureError),
CallError(CallError),
WebError(WebError),
#[derive(Error, Debug)]
pub enum JSError {
ProcedureError(#[from] ProcedureError),
CallError(#[from] CallError),
WebError(#[from] WebError),
}
impl Display for WrappedError {
impl Display for JSError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Error from Rust: {:?}", self)
match self {
JSError::ProcedureError(inner) => inner.fmt(f),
JSError::CallError(inner) => inner.fmt(f),
JSError::WebError(inner) => inner.fmt(f),
}
}
}
impl Error for WrappedError {}
impl From<WrappedError> for JsValue {
fn from(val: WrappedError) -> Self {
impl From<JSError> for JsValue {
fn from(val: JSError) -> Self {
JsValue::from_str(&val.to_string())
}
}
impl From<CallError> for WrappedError {
fn from(e: CallError) -> Self {
WrappedError::CallError(e)
}
}
impl From<ProcedureError> for WrappedError {
fn from(e: ProcedureError) -> Self {
WrappedError::ProcedureError(e)
}
}
impl From<WebError> for WrappedError {
fn from(e: WebError) -> Self {
WrappedError::WebError(e)
}
}

View File

@ -11,7 +11,7 @@ use maplibre::{
use maplibre_winit::{WinitEnvironment, WinitMapWindowConfig};
use wasm_bindgen::prelude::*;
use crate::{error::WrappedError, platform::http_client::WHATWGFetchHttpClient};
use crate::{error::JSError, platform::http_client::WHATWGFetchHttpClient};
mod error;
mod platform;
@ -65,7 +65,7 @@ type CurrentEnvironment = WinitEnvironment<
pub type MapType = Map<CurrentEnvironment>;
#[wasm_bindgen]
pub async fn run_maplibre(new_worker: js_sys::Function) -> Result<(), WrappedError> {
pub async fn run_maplibre(new_worker: js_sys::Function) -> Result<(), JSError> {
let mut kernel_builder = KernelBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(WHATWGFetchHttpClient::new());

View File

@ -2,11 +2,11 @@ use maplibre::io::apc::CallError;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use crate::{platform::multithreaded::pool::Work, WrappedError};
use crate::{platform::multithreaded::pool::Work, JSError};
/// Entry point invoked by the worker.
#[wasm_bindgen]
pub async fn multithreaded_worker_entry(ptr: u32) -> Result<(), WrappedError> {
pub async fn multithreaded_worker_entry(ptr: u32) -> Result<(), JSError> {
let work = unsafe { Box::from_raw(ptr as *mut Work) };
JsFuture::from(work.execute())
.await

View File

@ -103,15 +103,8 @@ impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
}
}
type NewWorker = Box<dyn Fn() -> Result<Worker, WebError>>;
pub type ReceivedType = RefCell<Vec<Message<UsedTransferables>>>;
#[derive(Error, Debug)]
pub enum PassingAPCError {
#[error("creating a worker failed")]
Worker,
}
pub struct PassingAsyncProcedureCall {
workers: Vec<Worker>,

View File

@ -12,7 +12,7 @@ use thiserror::Error;
use wasm_bindgen::{prelude::*, JsCast};
use crate::{
error::WrappedError,
error::JSError,
platform::singlethreaded::{
apc::{MessageTag, ReceivedType},
transferables::FlatBufferTransferable,
@ -23,10 +23,7 @@ use crate::{
/// Entry point invoked by the worker.
#[wasm_bindgen]
pub async fn singlethreaded_worker_entry(
procedure_ptr: u32,
input: String,
) -> Result<(), WrappedError> {
pub async fn singlethreaded_worker_entry(procedure_ptr: u32, input: String) -> Result<(), JSError> {
let procedure: AsyncProcedure<UsedContext> = unsafe { mem::transmute(procedure_ptr) };
let input =
@ -50,7 +47,7 @@ pub struct DeserializeMessage;
pub unsafe fn singlethreaded_main_entry(
received_ptr: *const ReceivedType,
in_transfer: js_sys::Array,
) -> Result<(), WrappedError> {
) -> Result<(), JSError> {
let tag = in_transfer
.get(0)
.as_f64()