diff --git a/CHANGELOG.md b/CHANGELOG.md index e16759d4ee..1d7250ac96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,12 @@ Bottom level categories: - Update Naga to 9eb3a1dc (2023-10-12), which includes support for WGSL constant expressions. By @jimblandy in [#4233](https://github.com/gfx-rs/wgpu/pull/4233) +#### Support desktop OpenGL via WGL on Windows + +Added creating of full OpenGL contexts to the GLES backend using WGL to support older devices. + +By @Zoxc in [#4248](https://github.com/gfx-rs/wgpu/pull/4248) + #### Pass timestamp queries Addition of `TimestampWrites` to compute and render passes to allow profiling. diff --git a/Cargo.lock b/Cargo.lock index 741b091e98..d0369556fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1233,7 +1233,7 @@ dependencies = [ "glutin_egl_sys", "glutin_gles2_sys", "glutin_glx_sys", - "glutin_wgl_sys", + "glutin_wgl_sys 0.1.5", "libloading 0.7.4", "log", "objc", @@ -1286,6 +1286,15 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef89398e90033fc6bc65e9bd42fd29bbbfd483bda5b56dc5562f455550618165" +dependencies = [ + "gl_generator", +] + [[package]] name = "gpu-alloc" version = "0.6.0" @@ -3353,6 +3362,7 @@ dependencies = [ "env_logger", "glow", "glutin", + "glutin_wgl_sys 0.4.0", "gpu-alloc", "gpu-allocator", "gpu-descriptor", @@ -3365,6 +3375,7 @@ dependencies = [ "metal", "naga", "objc", + "once_cell", "parking_lot", "profiling", "range-alloc", diff --git a/README.md b/README.md index b721db2d47..564136aa7a 100644 --- a/README.md +++ b/README.md @@ -70,14 +70,14 @@ We have a [wiki](https://github.com/gfx-rs/wgpu/wiki) that serves as a knowledge ## Supported Platforms -| API | Windows | Linux & Android | macOS & iOS | Web (wasm) | -| --------- | ------------------------------ | ------------------ | ------------------------- | ------------------------- | -| Vulkan | :white_check_mark: | :white_check_mark: | :ok: (vulkan-portability) | | -| Metal | | | :white_check_mark: | | -| DX12 | :white_check_mark: (W10+ only) | | | | -| DX11 | :hammer_and_wrench: | | | | -| GLES3 | :ok: (angle) | :ok: | :ok: (angle; macOS only) | :ok: (WebGL2 Only) | -| WebGPU | | | | :white_check_mark: | +| API | Windows | Linux & Android | macOS & iOS | Web (wasm) | +| ----------- | ------------------------------ | ------------------ | ------------------------- | ------------------------- | +| Vulkan | :white_check_mark: | :white_check_mark: | :ok: (vulkan-portability) | | +| Metal | | | :white_check_mark: | | +| DX12 | :white_check_mark: (W10+ only) | | | | +| DX11 | :hammer_and_wrench: | | | | +| OpenGL | :ok: (Desktop GL 3.3+) | :ok: (GL ES 3.0+) | :ok: (angle; GL ES 3.0+) | :ok: (WebGL2) | +| WebGPU | | | | :white_check_mark: | :white_check_mark: = First Class Support — :ok: = Best Effort Support — :hammer_and_wrench: = Unsupported, but support in progress @@ -148,6 +148,7 @@ We have multiple methods of testing, each of which tests different qualities abo | DX11/Windows 10 | :construction: | — | using WARP | | Metal/MacOS | :heavy_check_mark: | — | using hardware runner | | Vulkan/Linux | :heavy_check_mark: | - | using swiftshader | +| GL/Windows | | — | | | GLES/Linux | :heavy_check_mark: | — | using llvmpipe | | WebGL/Chrome | :heavy_check_mark: | — | using swiftshader | diff --git a/tests/tests/multi-instance.rs b/tests/tests/multi-instance.rs new file mode 100644 index 0000000000..087fac7137 --- /dev/null +++ b/tests/tests/multi-instance.rs @@ -0,0 +1,33 @@ +#![cfg(not(target_arch = "wasm32"))] + +async fn get() -> wgpu::Adapter { + let adapter = { + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all), + ..Default::default() + }); + instance + .request_adapter(&wgpu::RequestAdapterOptions::default()) + .await + .unwrap() + }; + + log::info!("Selected adapter: {:?}", adapter.get_info()); + + adapter +} + +#[test] +fn multi_instance() { + { + env_logger::init(); + + // Sequential instances. + for _ in 0..3 { + pollster::block_on(get()); + } + + // Concurrent instances + let _instances: Vec<_> = (0..3).map(|_| pollster::block_on(get())).collect(); + } +} diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 5593e2ffeb..fb8e068659 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -36,7 +36,7 @@ targets = [ default = ["link"] metal = ["naga/msl-out", "block"] vulkan = ["naga/spv-out", "ash", "gpu-alloc", "gpu-descriptor", "libloading", "smallvec"] -gles = ["naga/glsl-out", "glow", "khronos-egl", "libloading"] +gles = ["naga/glsl-out", "glow", "glutin_wgl_sys", "khronos-egl", "libloading"] dx11 = ["naga/hlsl-out", "d3d12", "libloading", "winapi/d3d11", "winapi/std", "winapi/d3d11_1", "winapi/d3d11_2", "winapi/d3d11sdklayers", "winapi/dxgi1_6"] dx12 = ["naga/hlsl-out", "d3d12", "bit-set", "libloading", "range-alloc", "winapi/std", "winapi/winbase", "winapi/d3d12", "winapi/d3d12shader", "winapi/d3d12sdklayers", "winapi/dxgi1_6"] # TODO: This is a separate feature until Mozilla okays windows-rs, see https://github.com/gfx-rs/wgpu/issues/3207 for the tracking issue. @@ -59,6 +59,7 @@ parking_lot = ">=0.11,<0.13" profiling = { version = "1", default-features = false } raw-window-handle = "0.5" thiserror = "1" +once_cell = "1.18.0" # backends common arrayvec = "0.7" @@ -95,6 +96,8 @@ bit-set = { version = "0.5", optional = true } range-alloc = { version = "0.1", optional = true } gpu-allocator = { version = "0.23", default_features = false, features = ["d3d12", "public-winapi"], optional = true } hassle-rs = { version = "0.10", optional = true } +# backend: Gles +glutin_wgl_sys = { version = "0.4", optional = true } winapi = { version = "0.3", features = ["profileapi", "libloaderapi", "windef", "winuser", "dcomp"] } d3d12 = { version = "0.7", features = ["libloading"], optional = true } diff --git a/wgpu-hal/examples/raw-gles.rs b/wgpu-hal/examples/raw-gles.rs index ec0212960a..455c555e85 100644 --- a/wgpu-hal/examples/raw-gles.rs +++ b/wgpu-hal/examples/raw-gles.rs @@ -10,7 +10,7 @@ extern crate wgpu_hal as hal; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(windows, target_arch = "wasm32")))] fn main() { env_logger::init(); println!("Initializing external GL context"); @@ -116,10 +116,10 @@ fn main() { fill_screen(&exposed, 640, 400); } -#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] +#[cfg(any(windows, all(target_arch = "wasm32", not(target_os = "emscripten"))))] fn main() {} -#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] +#[cfg(any(not(any(windows, target_arch = "wasm32")), target_os = "emscripten"))] fn fill_screen(exposed: &hal::ExposedAdapter, width: u32, height: u32) { use hal::{Adapter as _, CommandEncoder as _, Device as _, Queue as _}; diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index c9682dce09..68bc7fe492 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -10,18 +10,6 @@ const GL_UNMASKED_VENDOR_WEBGL: u32 = 0x9245; const GL_UNMASKED_RENDERER_WEBGL: u32 = 0x9246; impl super::Adapter { - /// According to the OpenGL specification, the version information is - /// expected to follow the following syntax: - /// - /// ~~~bnf - /// ::= - /// ::= - /// ::= - /// ::= - /// ::= "." ["." ] - /// ::= [" " ] - /// ~~~ - /// /// Note that this function is intentionally lenient in regards to parsing, /// and will try to recover at least the first two version numbers without /// resulting in an `Err`. @@ -59,6 +47,35 @@ impl super::Adapter { None => false, }; + Self::parse_full_version(src).map(|(major, minor)| { + ( + // Return WebGL 2.0 version as OpenGL ES 3.0 + if is_webgl && !is_glsl { + major + 1 + } else { + major + }, + minor, + ) + }) + } + + /// According to the OpenGL specification, the version information is + /// expected to follow the following syntax: + /// + /// ~~~bnf + /// ::= + /// ::= + /// ::= + /// ::= + /// ::= "." ["." ] + /// ::= [" " ] + /// ~~~ + /// + /// Note that this function is intentionally lenient in regards to parsing, + /// and will try to recover at least the first two version numbers without + /// resulting in an `Err`. + pub(super) fn parse_full_version(src: &str) -> Result<(u8, u8), crate::InstanceError> { let (version, _vendor_info) = match src.find(' ') { Some(i) => (&src[..i], src[i + 1..].to_string()), None => (src, String::new()), @@ -78,15 +95,7 @@ impl super::Adapter { }); match (major, minor) { - (Some(major), Some(minor)) => Ok(( - // Return WebGL 2.0 version as OpenGL ES 3.0 - if is_webgl && !is_glsl { - major + 1 - } else { - major - }, - minor, - )), + (Some(major), Some(minor)) => Ok((major, minor)), _ => Err(crate::InstanceError::new(format!( "unable to extract OpenGL version from {version:?}" ))), @@ -212,29 +221,75 @@ impl super::Adapter { log::info!("Renderer: {}", renderer); log::info!("Version: {}", version); - log::debug!("Extensions: {:#?}", extensions); + let full_ver = Self::parse_full_version(&version).ok(); + let es_ver = full_ver + .is_none() + .then_some(()) + .and_then(|_| Self::parse_version(&version).ok()); - let ver = Self::parse_version(&version).ok()?; - if ver < (3, 0) { - log::warn!( - "Returned GLES context is {}.{}, when 3.0+ was requested", - ver.0, - ver.1 - ); + if es_ver.is_none() && full_ver.is_none() { + log::warn!("Unable to parse OpenGL version"); return None; } - let supports_storage = ver >= (3, 1); - let supports_work_group_params = ver >= (3, 1); + if let Some(es_ver) = es_ver { + if es_ver < (3, 0) { + log::warn!( + "Returned GLES context is {}.{}, when 3.0+ was requested", + es_ver.0, + es_ver.1 + ); + return None; + } + } + + if let Some(full_ver) = full_ver { + if full_ver < (3, 3) { + log::warn!( + "Returned GL context is {}.{}, when 3.3+ is needed", + full_ver.0, + full_ver.1 + ); + return None; + } + } + + let supported = |(req_es_major, req_es_minor), (req_full_major, req_full_minor)| { + let es_supported = es_ver + .map(|es_ver| es_ver >= (req_es_major, req_es_minor)) + .unwrap_or_default(); + + let full_supported = full_ver + .map(|full_ver| full_ver >= (req_full_major, req_full_minor)) + .unwrap_or_default(); + + es_supported || full_supported + }; + + let supports_storage = + supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_shader_storage_buffer_object"); + let supports_compute = + supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_compute_shader"); + let supports_work_group_params = supports_compute; let shading_language_version = { let sl_version = unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) }; log::info!("SL version: {}", &sl_version); - let (sl_major, sl_minor) = Self::parse_version(&sl_version).ok()?; - let value = sl_major as u16 * 100 + sl_minor as u16 * 10; - naga::back::glsl::Version::Embedded { - version: value, - is_webgl: cfg!(target_arch = "wasm32"), + if full_ver.is_some() { + let (sl_major, sl_minor) = Self::parse_full_version(&sl_version).ok()?; + let mut value = sl_major as u16 * 100 + sl_minor as u16 * 10; + // Naga doesn't think it supports GL 460+, so we cap it at 450 + if value > 450 { + value = 450; + } + naga::back::glsl::Version::Desktop(value) + } else { + let (sl_major, sl_minor) = Self::parse_version(&sl_version).ok()?; + let value = sl_major as u16 * 100 + sl_minor as u16 * 10; + naga::back::glsl::Version::Embedded { + version: value, + is_webgl: cfg!(target_arch = "wasm32"), + } } }; @@ -242,7 +297,19 @@ impl super::Adapter { let is_angle = renderer.contains("ANGLE"); let vertex_shader_storage_blocks = if supports_storage { - (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_SHADER_STORAGE_BLOCKS) } as u32) + let value = + (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_SHADER_STORAGE_BLOCKS) } as u32); + + if value == 0 && extensions.contains("GL_ARB_shader_storage_buffer_object") { + // The driver for AMD Radeon HD 5870 returns zero here, so assume the value matches the compute shader storage block count. + // Windows doesn't recognize `GL_MAX_VERTEX_ATTRIB_STRIDE`. + let new = (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_SHADER_STORAGE_BLOCKS) } + as u32); + log::warn!("Max vertex shader storage blocks is zero, but GL_ARB_shader_storage_buffer_object is specified. Assuming the compute value {new}"); + new + } else { + value + } } else { 0 }; @@ -295,18 +362,21 @@ impl super::Adapter { | wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES | wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES | wgt::DownlevelFlags::COMPARISON_SAMPLERS; - downlevel_flags.set(wgt::DownlevelFlags::COMPUTE_SHADERS, ver >= (3, 1)); + downlevel_flags.set(wgt::DownlevelFlags::COMPUTE_SHADERS, supports_compute); downlevel_flags.set( wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE, max_storage_block_size != 0, ); - downlevel_flags.set(wgt::DownlevelFlags::INDIRECT_EXECUTION, ver >= (3, 1)); + downlevel_flags.set( + wgt::DownlevelFlags::INDIRECT_EXECUTION, + supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_multi_draw_indirect"), + ); //TODO: we can actually support positive `base_vertex` in the same way // as we emulate the `start_instance`. But we can't deal with negatives... - downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, ver >= (3, 2)); + downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, supported((3, 2), (3, 2))); downlevel_flags.set( wgt::DownlevelFlags::INDEPENDENT_BLEND, - ver >= (3, 2) || extensions.contains("GL_EXT_draw_buffers_indexed"), + supported((3, 2), (4, 0)) || extensions.contains("GL_EXT_draw_buffers_indexed"), ); downlevel_flags.set( wgt::DownlevelFlags::VERTEX_STORAGE, @@ -339,7 +409,7 @@ impl super::Adapter { ); downlevel_flags.set( wgt::DownlevelFlags::MULTISAMPLED_SHADING, - ver >= (3, 2) || extensions.contains("OES_sample_variables"), + supported((3, 2), (4, 0)) || extensions.contains("OES_sample_variables"), ); let mut features = wgt::Features::empty() @@ -369,9 +439,14 @@ impl super::Adapter { ); features.set( wgt::Features::SHADER_PRIMITIVE_INDEX, - ver >= (3, 2) || extensions.contains("OES_geometry_shader"), + supported((3, 2), (3, 2)) + || extensions.contains("OES_geometry_shader") + || extensions.contains("GL_ARB_geometry_shader4"), + ); + features.set( + wgt::Features::SHADER_EARLY_DEPTH_TEST, + supported((3, 1), (4, 2)) || extensions.contains("GL_ARB_shader_image_load_store"), ); - features.set(wgt::Features::SHADER_EARLY_DEPTH_TEST, ver >= (3, 1)); features.set(wgt::Features::SHADER_UNUSED_VERTEX_OUTPUT, true); let gles_bcn_exts = [ "GL_EXT_texture_compression_s3tc_srgb", @@ -443,16 +518,19 @@ impl super::Adapter { ); private_caps.set( super::PrivateCapabilities::SHADER_BINDING_LAYOUT, - ver >= (3, 1), + supports_compute, ); private_caps.set( super::PrivateCapabilities::SHADER_TEXTURE_SHADOW_LOD, extensions.contains("GL_EXT_texture_shadow_lod"), ); - private_caps.set(super::PrivateCapabilities::MEMORY_BARRIERS, ver >= (3, 1)); + private_caps.set( + super::PrivateCapabilities::MEMORY_BARRIERS, + supported((3, 1), (4, 2)), + ); private_caps.set( super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT, - ver >= (3, 1), + supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_vertex_attrib_binding"), ); private_caps.set( super::PrivateCapabilities::INDEX_BUFFER_ROLE_CHANGE, @@ -483,7 +561,7 @@ impl super::Adapter { let min_uniform_buffer_offset_alignment = (unsafe { gl.get_parameter_i32(glow::UNIFORM_BUFFER_OFFSET_ALIGNMENT) } as u32); - let min_storage_buffer_offset_alignment = if ver >= (3, 1) { + let min_storage_buffer_offset_alignment = if supports_storage { (unsafe { gl.get_parameter_i32(glow::SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT) } as u32) } else { 256 @@ -521,7 +599,7 @@ impl super::Adapter { max_uniform_buffer_binding_size: unsafe { gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE) } as u32, - max_storage_buffer_binding_size: if ver >= (3, 1) { + max_storage_buffer_binding_size: if supports_storage { unsafe { gl.get_parameter_i32(glow::MAX_SHADER_STORAGE_BLOCK_SIZE) } } else { 0 @@ -539,7 +617,29 @@ impl super::Adapter { max_vertex_buffer_array_stride: if private_caps .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT) { - (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) } as u32) + if let Some(full_ver) = full_ver { + if full_ver >= (4, 4) { + // We can query `GL_MAX_VERTEX_ATTRIB_STRIDE` in OpenGL 4.4+ + let value = + (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) }) + as u32; + + if value == 0 { + // This should be at least 2048, but the driver for AMD Radeon HD 5870 on + // Windows doesn't recognize `GL_MAX_VERTEX_ATTRIB_STRIDE`. + + log::warn!("Max vertex attribute stride is 0. Assuming it is 2048"); + 2048 + } else { + value + } + } else { + log::warn!("Max vertex attribute stride unknown. Assuming it is 2048"); + 2048 + } + } else { + (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) }) as u32 + } } else { !0 }, @@ -624,6 +724,7 @@ impl super::Adapter { max_texture_size, next_shader_id: Default::default(), program_cache: Default::default(), + es: es_ver.is_some(), }), }, info: Self::make_info(vendor, renderer), @@ -643,27 +744,73 @@ impl super::Adapter { }) } + unsafe fn compile_shader( + source: &str, + gl: &glow::Context, + shader_type: u32, + es: bool, + ) -> Option { + let source = if es { + format!("#version 300 es\nprecision lowp float;\n{source}") + } else { + format!("#version 130\n{source}") + }; + let shader = unsafe { gl.create_shader(shader_type) }.expect("Could not create shader"); + unsafe { gl.shader_source(shader, &source) }; + unsafe { gl.compile_shader(shader) }; + + if !unsafe { gl.get_shader_compile_status(shader) } { + let msg = unsafe { gl.get_shader_info_log(shader) }; + if !msg.is_empty() { + log::error!("\tShader compile error: {}", msg); + } + unsafe { gl.delete_shader(shader) }; + None + } else { + Some(shader) + } + } + unsafe fn create_shader_clear_program( gl: &glow::Context, - ) -> (glow::Program, glow::UniformLocation) { + es: bool, + ) -> Option<(glow::Program, glow::UniformLocation)> { let program = unsafe { gl.create_program() }.expect("Could not create shader program"); - let vertex = - unsafe { gl.create_shader(glow::VERTEX_SHADER) }.expect("Could not create shader"); - unsafe { gl.shader_source(vertex, include_str!("./shaders/clear.vert")) }; - unsafe { gl.compile_shader(vertex) }; - let fragment = - unsafe { gl.create_shader(glow::FRAGMENT_SHADER) }.expect("Could not create shader"); - unsafe { gl.shader_source(fragment, include_str!("./shaders/clear.frag")) }; - unsafe { gl.compile_shader(fragment) }; + let vertex = unsafe { + Self::compile_shader( + include_str!("./shaders/clear.vert"), + gl, + glow::VERTEX_SHADER, + es, + )? + }; + let fragment = unsafe { + Self::compile_shader( + include_str!("./shaders/clear.frag"), + gl, + glow::FRAGMENT_SHADER, + es, + )? + }; unsafe { gl.attach_shader(program, vertex) }; unsafe { gl.attach_shader(program, fragment) }; unsafe { gl.link_program(program) }; + + let linked_ok = unsafe { gl.get_program_link_status(program) }; + let msg = unsafe { gl.get_program_info_log(program) }; + if !msg.is_empty() { + log::warn!("Shader link error: {}", msg); + } + if !linked_ok { + return None; + } + let color_uniform_location = unsafe { gl.get_uniform_location(program, "color") } .expect("Could not find color uniform in shader clear shader"); unsafe { gl.delete_shader(vertex) }; unsafe { gl.delete_shader(fragment) }; - (program, color_uniform_location) + Some((program, color_uniform_location)) } } @@ -688,8 +835,11 @@ impl crate::Adapter for super::Adapter { // Compile the shader program we use for doing manual clears to work around Mesa fastclear // bug. - let (shader_clear_program, shader_clear_program_color_uniform_location) = - unsafe { Self::create_shader_clear_program(gl) }; + + let (shader_clear_program, shader_clear_program_color_uniform_location) = unsafe { + Self::create_shader_clear_program(gl, self.shared.es) + .ok_or(crate::DeviceError::ResourceCreationFailed)? + }; Ok(crate::OpenDevice { device: super::Device { @@ -909,7 +1059,11 @@ impl crate::Adapter for super::Adapter { Some(crate::SurfaceCapabilities { formats, - present_modes: vec![wgt::PresentMode::Fifo], //TODO + present_modes: if cfg!(windows) { + vec![wgt::PresentMode::Fifo, wgt::PresentMode::Mailbox] + } else { + vec![wgt::PresentMode::Fifo] //TODO + }, composite_alpha_modes: vec![wgt::CompositeAlphaMode::Opaque], //TODO swap_chain_sizes: 2..=2, current_extent: None, diff --git a/wgpu-hal/src/gles/device.rs b/wgpu-hal/src/gles/device.rs index 994e44397f..f77857e67f 100644 --- a/wgpu-hal/src/gles/device.rs +++ b/wgpu-hal/src/gles/device.rs @@ -272,10 +272,6 @@ impl super::Device { entry_point: stage.entry_point.to_owned(), }); } - let glsl_version = match self.shared.shading_language_version { - naga::back::glsl::Version::Embedded { version, .. } => version, - naga::back::glsl::Version::Desktop(_) => unreachable!(), - }; let mut guard = self .shared .program_cache @@ -295,7 +291,7 @@ impl super::Device { layout, label, multiview, - glsl_version, + self.shared.shading_language_version, self.shared.private_caps, ) }) @@ -311,9 +307,13 @@ impl super::Device { layout: &super::PipelineLayout, #[cfg_attr(target_arch = "wasm32", allow(unused))] label: Option<&str>, multiview: Option, - glsl_version: u16, + glsl_version: naga::back::glsl::Version, private_caps: super::PrivateCapabilities, ) -> Result, crate::PipelineError> { + let glsl_version = match glsl_version { + naga::back::glsl::Version::Embedded { version, .. } => format!("{version} es"), + naga::back::glsl::Version::Desktop(version) => format!("{version}"), + }; let program = unsafe { gl.create_program() }.unwrap(); #[cfg(not(target_arch = "wasm32"))] if let Some(label) = label { @@ -343,7 +343,7 @@ impl super::Device { // Create empty fragment shader if only vertex shader is present if has_stages == wgt::ShaderStages::VERTEX { - let shader_src = format!("#version {glsl_version} es \n void main(void) {{}}",); + let shader_src = format!("#version {glsl_version}\n void main(void) {{}}",); log::info!("Only vertex shader is present. Creating an empty fragment shader",); let shader = unsafe { Self::compile_shader( diff --git a/wgpu-hal/src/gles/egl.rs b/wgpu-hal/src/gles/egl.rs index a2661e6323..5332e92778 100644 --- a/wgpu-hal/src/gles/egl.rs +++ b/wgpu-hal/src/gles/egl.rs @@ -289,55 +289,6 @@ fn choose_config( ))) } -fn gl_debug_message_callback(source: u32, gltype: u32, id: u32, severity: u32, message: &str) { - let source_str = match source { - glow::DEBUG_SOURCE_API => "API", - glow::DEBUG_SOURCE_WINDOW_SYSTEM => "Window System", - glow::DEBUG_SOURCE_SHADER_COMPILER => "ShaderCompiler", - glow::DEBUG_SOURCE_THIRD_PARTY => "Third Party", - glow::DEBUG_SOURCE_APPLICATION => "Application", - glow::DEBUG_SOURCE_OTHER => "Other", - _ => unreachable!(), - }; - - let log_severity = match severity { - glow::DEBUG_SEVERITY_HIGH => log::Level::Error, - glow::DEBUG_SEVERITY_MEDIUM => log::Level::Warn, - glow::DEBUG_SEVERITY_LOW => log::Level::Info, - glow::DEBUG_SEVERITY_NOTIFICATION => log::Level::Trace, - _ => unreachable!(), - }; - - let type_str = match gltype { - glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior", - glow::DEBUG_TYPE_ERROR => "Error", - glow::DEBUG_TYPE_MARKER => "Marker", - glow::DEBUG_TYPE_OTHER => "Other", - glow::DEBUG_TYPE_PERFORMANCE => "Performance", - glow::DEBUG_TYPE_POP_GROUP => "Pop Group", - glow::DEBUG_TYPE_PORTABILITY => "Portability", - glow::DEBUG_TYPE_PUSH_GROUP => "Push Group", - glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior", - _ => unreachable!(), - }; - - let _ = std::panic::catch_unwind(|| { - log::log!( - log_severity, - "GLES: [{}/{}] ID {} : {}", - source_str, - type_str, - id, - message - ); - }); - - if cfg!(debug_assertions) && log_severity == log::Level::Error { - // Set canary and continue - crate::VALIDATION_CANARY.set(); - } -} - #[derive(Clone, Debug)] struct EglContext { instance: Arc, @@ -1014,7 +965,7 @@ impl crate::Instance for Instance { if self.flags.contains(wgt::InstanceFlags::VALIDATION) && gl.supports_debug() { log::info!("Enabling GLES debug output"); unsafe { gl.enable(glow::DEBUG_OUTPUT) }; - unsafe { gl.debug_message_callback(gl_debug_message_callback) }; + unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; } inner.egl.unmake_current(); @@ -1094,8 +1045,9 @@ impl Surface { pub(super) unsafe fn present( &mut self, _suf_texture: super::Texture, - gl: &glow::Context, + context: &AdapterContext, ) -> Result<(), crate::SurfaceError> { + let gl = unsafe { context.get_without_egl_lock() }; let sc = self.swapchain.as_ref().unwrap(); self.egl diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index 4eded6bcd6..9f3a0c7a55 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -57,12 +57,14 @@ To address this, we invalidate the vertex buffers based on: */ ///cbindgen:ignore -#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] +#[cfg(not(any(windows, all(target_arch = "wasm32", not(target_os = "emscripten")))))] mod egl; #[cfg(target_os = "emscripten")] mod emscripten; #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] mod web; +#[cfg(windows)] +mod wgl; mod adapter; mod command; @@ -72,9 +74,9 @@ mod queue; use crate::{CopyExtent, TextureDescriptor}; -#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] +#[cfg(not(any(windows, all(target_arch = "wasm32", not(target_os = "emscripten")))))] pub use self::egl::{AdapterContext, AdapterContextLock}; -#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] +#[cfg(not(any(windows, all(target_arch = "wasm32", not(target_os = "emscripten")))))] use self::egl::{Instance, Surface}; #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] @@ -82,6 +84,11 @@ pub use self::web::AdapterContext; #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] use self::web::{Instance, Surface}; +#[cfg(windows)] +use self::wgl::AdapterContext; +#[cfg(windows)] +use self::wgl::{Instance, Surface}; + use arrayvec::ArrayVec; use glow::HasContext; @@ -204,6 +211,7 @@ struct AdapterShared { max_texture_size: u32, next_shader_id: AtomicU32, program_cache: Mutex, + es: bool, } pub struct Adapter { @@ -904,3 +912,53 @@ impl fmt::Debug for CommandEncoder { .finish() } } + +#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))] +fn gl_debug_message_callback(source: u32, gltype: u32, id: u32, severity: u32, message: &str) { + let source_str = match source { + glow::DEBUG_SOURCE_API => "API", + glow::DEBUG_SOURCE_WINDOW_SYSTEM => "Window System", + glow::DEBUG_SOURCE_SHADER_COMPILER => "ShaderCompiler", + glow::DEBUG_SOURCE_THIRD_PARTY => "Third Party", + glow::DEBUG_SOURCE_APPLICATION => "Application", + glow::DEBUG_SOURCE_OTHER => "Other", + _ => unreachable!(), + }; + + let log_severity = match severity { + glow::DEBUG_SEVERITY_HIGH => log::Level::Error, + glow::DEBUG_SEVERITY_MEDIUM => log::Level::Warn, + glow::DEBUG_SEVERITY_LOW => log::Level::Info, + glow::DEBUG_SEVERITY_NOTIFICATION => log::Level::Trace, + _ => unreachable!(), + }; + + let type_str = match gltype { + glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior", + glow::DEBUG_TYPE_ERROR => "Error", + glow::DEBUG_TYPE_MARKER => "Marker", + glow::DEBUG_TYPE_OTHER => "Other", + glow::DEBUG_TYPE_PERFORMANCE => "Performance", + glow::DEBUG_TYPE_POP_GROUP => "Pop Group", + glow::DEBUG_TYPE_PORTABILITY => "Portability", + glow::DEBUG_TYPE_PUSH_GROUP => "Push Group", + glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior", + _ => unreachable!(), + }; + + let _ = std::panic::catch_unwind(|| { + log::log!( + log_severity, + "GLES: [{}/{}] ID {} : {}", + source_str, + type_str, + id, + message + ); + }); + + if cfg!(debug_assertions) && log_severity == log::Level::Error { + // Set canary and continue + crate::VALIDATION_CANARY.set(); + } +} diff --git a/wgpu-hal/src/gles/queue.rs b/wgpu-hal/src/gles/queue.rs index dd24b45ac9..22e1d26ce1 100644 --- a/wgpu-hal/src/gles/queue.rs +++ b/wgpu-hal/src/gles/queue.rs @@ -1443,13 +1443,7 @@ impl crate::Queue for super::Queue { surface: &mut super::Surface, texture: super::Texture, ) -> Result<(), crate::SurfaceError> { - #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] - let gl = unsafe { &self.shared.context.get_without_egl_lock() }; - - #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] - let gl = &self.shared.context.glow_context; - - unsafe { surface.present(texture, gl) } + unsafe { surface.present(texture, &self.shared.context) } } unsafe fn get_timestamp_period(&self) -> f32 { diff --git a/wgpu-hal/src/gles/shaders/clear.frag b/wgpu-hal/src/gles/shaders/clear.frag index 7766c12d9f..1d0e414b28 100644 --- a/wgpu-hal/src/gles/shaders/clear.frag +++ b/wgpu-hal/src/gles/shaders/clear.frag @@ -1,5 +1,3 @@ -#version 300 es -precision lowp float; uniform vec4 color; //Hack: Some WebGL implementations don't find "color" otherwise. uniform vec4 color_workaround; diff --git a/wgpu-hal/src/gles/shaders/clear.vert b/wgpu-hal/src/gles/shaders/clear.vert index ac655e7f31..341b4e5f06 100644 --- a/wgpu-hal/src/gles/shaders/clear.vert +++ b/wgpu-hal/src/gles/shaders/clear.vert @@ -1,7 +1,5 @@ -#version 300 es -precision lowp float; // A triangle that fills the whole screen -const vec2[3] TRIANGLE_POS = vec2[]( +vec2[3] TRIANGLE_POS = vec2[]( vec2( 0.0, -3.0), vec2(-3.0, 1.0), vec2( 3.0, 1.0) diff --git a/wgpu-hal/src/gles/web.rs b/wgpu-hal/src/gles/web.rs index 3c5a750151..767c7b8c5b 100644 --- a/wgpu-hal/src/gles/web.rs +++ b/wgpu-hal/src/gles/web.rs @@ -215,8 +215,9 @@ impl Surface { pub(super) unsafe fn present( &mut self, _suf_texture: super::Texture, - gl: &glow::Context, + context: &AdapterContext, ) -> Result<(), crate::SurfaceError> { + let gl = &context.glow_context; let swapchain = self.swapchain.as_ref().ok_or(crate::SurfaceError::Other( "need to configure surface before presenting", ))?; diff --git a/wgpu-hal/src/gles/wgl.rs b/wgpu-hal/src/gles/wgl.rs new file mode 100644 index 0000000000..4d17b07c34 --- /dev/null +++ b/wgpu-hal/src/gles/wgl.rs @@ -0,0 +1,775 @@ +use glow::HasContext; +use glutin_wgl_sys::wgl_extra::{ + Wgl, CONTEXT_CORE_PROFILE_BIT_ARB, CONTEXT_DEBUG_BIT_ARB, CONTEXT_FLAGS_ARB, + CONTEXT_PROFILE_MASK_ARB, +}; +use once_cell::sync::Lazy; +use parking_lot::{Mutex, MutexGuard}; +use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; +use std::{ + collections::HashSet, + ffi::{c_void, CStr, CString}, + io::Error, + mem, + os::raw::c_int, + ptr, + sync::Arc, + time::Duration, +}; +use wgt::InstanceFlags; +use winapi::{ + shared::{ + minwindef::{FALSE, HMODULE, LPARAM, LRESULT, UINT, WPARAM}, + windef::{HDC, HGLRC, HWND}, + }, + um::{ + libloaderapi::{GetModuleHandleA, GetProcAddress, LoadLibraryA}, + wingdi::{ + wglCreateContext, wglDeleteContext, wglGetCurrentContext, wglGetProcAddress, + wglMakeCurrent, wglShareLists, ChoosePixelFormat, DescribePixelFormat, GetPixelFormat, + SetPixelFormat, SwapBuffers, PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, PFD_SUPPORT_OPENGL, + PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, + }, + winuser::{ + CreateWindowExA, DefWindowProcA, GetDC, RegisterClassExA, ReleaseDC, CS_OWNDC, + WNDCLASSEXA, + }, + }, +}; + +/// The amount of time to wait while trying to obtain a lock to the adapter context +const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 1; + +/// A wrapper around a `[`glow::Context`]` and the required WGL context that uses locking to +/// guarantee exclusive access when shared with multiple threads. +pub struct AdapterContext { + inner: Arc>, +} + +unsafe impl Sync for AdapterContext {} +unsafe impl Send for AdapterContext {} + +impl AdapterContext { + pub fn is_owned(&self) -> bool { + true + } + + pub fn raw_context(&self) -> *mut c_void { + self.inner.lock().context.context as *mut _ + } + + /// Obtain a lock to the WGL context and get handle to the [`glow::Context`] that can be used to + /// do rendering. + #[track_caller] + pub fn lock(&self) -> AdapterContextLock<'_> { + let inner = self + .inner + // Don't lock forever. If it takes longer than 1 second to get the lock we've got a + // deadlock and should panic to show where we got stuck + .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) + .expect("Could not lock adapter context. This is most-likely a deadlock."); + + inner.context.make_current(inner.device).unwrap(); + + AdapterContextLock { inner } + } +} + +/// A guard containing a lock to an [`AdapterContext`] +pub struct AdapterContextLock<'a> { + inner: MutexGuard<'a, Inner>, +} + +impl<'a> std::ops::Deref for AdapterContextLock<'a> { + type Target = glow::Context; + + fn deref(&self) -> &Self::Target { + &self.inner.gl + } +} + +impl<'a> Drop for AdapterContextLock<'a> { + fn drop(&mut self) { + self.inner.context.unmake_current().unwrap(); + } +} + +struct WglContext { + context: HGLRC, +} + +impl WglContext { + fn make_current(&self, device: HDC) -> Result<(), Error> { + if unsafe { wglMakeCurrent(device, self.context) } == FALSE { + Err(Error::last_os_error()) + } else { + Ok(()) + } + } + + fn unmake_current(&self) -> Result<(), Error> { + if unsafe { wglGetCurrentContext().is_null() } { + return Ok(()); + } + if unsafe { wglMakeCurrent(ptr::null_mut(), ptr::null_mut()) } == FALSE { + Err(Error::last_os_error()) + } else { + Ok(()) + } + } +} + +impl Drop for WglContext { + fn drop(&mut self) { + unsafe { + if wglDeleteContext(self.context) == FALSE { + log::error!("failed to delete WGL context {}", Error::last_os_error()); + } + }; + } +} + +unsafe impl Send for WglContext {} +unsafe impl Sync for WglContext {} + +struct Inner { + opengl_module: HMODULE, + gl: glow::Context, + device: HDC, + context: WglContext, +} + +pub struct Instance { + srgb_capable: bool, + inner: Arc>, +} + +unsafe impl Send for Instance {} +unsafe impl Sync for Instance {} + +fn load_gl_func(name: &str, module: Option) -> *const c_void { + let addr = CString::new(name.as_bytes()).unwrap(); + let mut ptr = unsafe { wglGetProcAddress(addr.as_ptr()) }; + if ptr.is_null() { + if let Some(module) = module { + ptr = unsafe { GetProcAddress(module, addr.as_ptr()) }; + } + } + ptr.cast() +} + +fn extensions(extra: &Wgl, dc: HDC) -> HashSet { + if extra.GetExtensionsStringARB.is_loaded() { + unsafe { CStr::from_ptr(extra.GetExtensionsStringARB(dc as *const _)) } + .to_str() + .unwrap_or("") + } else { + "" + } + .split(' ') + .map(|s| s.to_owned()) + .collect() +} + +unsafe fn setup_pixel_format(dc: HDC) -> Result<(), crate::InstanceError> { + let mut format: PIXELFORMATDESCRIPTOR = unsafe { mem::zeroed() }; + format.nVersion = 1; + format.nSize = mem::size_of_val(&format) as u16; + format.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + format.iPixelType = PFD_TYPE_RGBA; + format.cColorBits = 8; + + let index = unsafe { ChoosePixelFormat(dc, &format) }; + if index == 0 { + return Err(crate::InstanceError::with_source( + String::from("unable to choose pixel format"), + Error::last_os_error(), + )); + } + + let current = unsafe { GetPixelFormat(dc) }; + + if index != current && unsafe { SetPixelFormat(dc, index, &format) } == FALSE { + return Err(crate::InstanceError::with_source( + String::from("unable to set pixel format"), + Error::last_os_error(), + )); + } + + let index = unsafe { GetPixelFormat(dc) }; + if index == 0 { + return Err(crate::InstanceError::with_source( + String::from("unable to get pixel format index"), + Error::last_os_error(), + )); + } + if unsafe { DescribePixelFormat(dc, index, mem::size_of_val(&format) as UINT, &mut format) } + == 0 + { + return Err(crate::InstanceError::with_source( + String::from("unable to read pixel format"), + Error::last_os_error(), + )); + } + + if format.dwFlags & PFD_SUPPORT_OPENGL == 0 || format.iPixelType != PFD_TYPE_RGBA { + return Err(crate::InstanceError::new(String::from( + "unsuitable pixel format", + ))); + } + Ok(()) +} + +fn create_global_device_context() -> Result { + let instance = unsafe { GetModuleHandleA(ptr::null()) }; + if instance.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to get executable instance"), + Error::last_os_error(), + )); + } + + // Use the address of `UNIQUE` as part of the window class name to ensure different + // `wgpu` versions use different names. + static UNIQUE: Mutex = Mutex::new(0); + let class_addr: *const _ = &UNIQUE; + let name = format!("wgpu Device Class {:x}\0", class_addr as usize); + let name = CString::from_vec_with_nul(name.into_bytes()).unwrap(); + + // Use a wrapper function for compatibility with `windows-rs`. + unsafe extern "system" fn wnd_proc( + window: HWND, + msg: UINT, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + unsafe { DefWindowProcA(window, msg, wparam, lparam) } + } + + let window_class = WNDCLASSEXA { + cbSize: mem::size_of::() as u32, + style: CS_OWNDC, + lpfnWndProc: Some(wnd_proc), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: instance, + hIcon: ptr::null_mut(), + hCursor: ptr::null_mut(), + hbrBackground: ptr::null_mut(), + lpszMenuName: ptr::null_mut(), + lpszClassName: name.as_ptr(), + hIconSm: ptr::null_mut(), + }; + + let atom = unsafe { RegisterClassExA(&window_class) }; + + if atom == 0 { + return Err(crate::InstanceError::with_source( + String::from("unable to register window class"), + Error::last_os_error(), + )); + } + + // Create a hidden window since we don't pass `WS_VISIBLE`. + let window = unsafe { + CreateWindowExA( + 0, + name.as_ptr(), + name.as_ptr(), + 0, + 0, + 0, + 1, + 1, + ptr::null_mut(), + ptr::null_mut(), + instance, + ptr::null_mut(), + ) + }; + if window.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create hidden instance window"), + Error::last_os_error(), + )); + } + let dc = unsafe { GetDC(window) }; + if dc.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create memory device"), + Error::last_os_error(), + )); + } + unsafe { setup_pixel_format(dc)? }; + + // We intentionally leak the window class, window and device context handle to avoid + // spawning a thread to destroy them. We cannot use `DestroyWindow` and `ReleaseDC` on + // different threads. + + Ok(dc) +} + +fn get_global_device_context() -> Result { + #[derive(Clone, Copy)] + struct SendDc(HDC); + unsafe impl Sync for SendDc {} + unsafe impl Send for SendDc {} + + static GLOBAL: Lazy> = + Lazy::new(|| create_global_device_context().map(SendDc)); + GLOBAL.clone().map(|dc| dc.0) +} + +impl crate::Instance for Instance { + unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { + let opengl_module = unsafe { LoadLibraryA("opengl32.dll\0".as_ptr() as *const _) }; + if opengl_module.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to load the OpenGL library"), + Error::last_os_error(), + )); + } + + let dc = get_global_device_context()?; + + let context = unsafe { wglCreateContext(dc) }; + if context.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create initial OpenGL context"), + Error::last_os_error(), + )); + } + let context = WglContext { context }; + context.make_current(dc).map_err(|e| { + crate::InstanceError::with_source( + String::from("unable to set initial OpenGL context as current"), + e, + ) + })?; + + let extra = Wgl::load_with(|name| load_gl_func(name, None)); + let extentions = extensions(&extra, dc); + + let can_use_profile = extentions.contains("WGL_ARB_create_context_profile") + && extra.CreateContextAttribsARB.is_loaded(); + + let context = if can_use_profile { + let attributes = [ + CONTEXT_PROFILE_MASK_ARB as c_int, + CONTEXT_CORE_PROFILE_BIT_ARB as c_int, + CONTEXT_FLAGS_ARB as c_int, + if desc.flags.contains(InstanceFlags::DEBUG) { + CONTEXT_DEBUG_BIT_ARB as c_int + } else { + 0 + }, + 0, // End of list + ]; + let context = unsafe { + extra.CreateContextAttribsARB(dc as *const _, ptr::null(), attributes.as_ptr()) + }; + if context.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create OpenGL context"), + Error::last_os_error(), + )); + } + WglContext { + context: context as *mut _, + } + } else { + context + }; + + context.make_current(dc).map_err(|e| { + crate::InstanceError::with_source( + String::from("unable to set OpenGL context as current"), + e, + ) + })?; + + let gl = unsafe { + glow::Context::from_loader_function(|name| load_gl_func(name, Some(opengl_module))) + }; + + let extra = Wgl::load_with(|name| load_gl_func(name, None)); + let extentions = extensions(&extra, dc); + + let srgb_capable = extentions.contains("WGL_EXT_framebuffer_sRGB") + || extentions.contains("WGL_ARB_framebuffer_sRGB") + || gl + .supported_extensions() + .contains("GL_ARB_framebuffer_sRGB"); + + if srgb_capable { + unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) }; + } + + if desc.flags.contains(InstanceFlags::VALIDATION) && gl.supports_debug() { + log::info!("Enabling GL debug output"); + unsafe { gl.enable(glow::DEBUG_OUTPUT) }; + unsafe { gl.debug_message_callback(super::gl_debug_message_callback) }; + } + + context.unmake_current().map_err(|e| { + crate::InstanceError::with_source( + String::from("unable to unset the current WGL context"), + e, + ) + })?; + + Ok(Instance { + inner: Arc::new(Mutex::new(Inner { + device: dc, + opengl_module, + gl, + context, + })), + srgb_capable, + }) + } + + #[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))] + unsafe fn create_surface( + &self, + _display_handle: RawDisplayHandle, + window_handle: RawWindowHandle, + ) -> Result { + let window = if let RawWindowHandle::Win32(handle) = window_handle { + handle + } else { + return Err(crate::InstanceError::new(format!( + "unsupported window: {window_handle:?}" + ))); + }; + Ok(Surface { + window: window.hwnd as *mut _, + presentable: true, + swapchain: None, + srgb_capable: self.srgb_capable, + }) + } + unsafe fn destroy_surface(&self, _surface: Surface) {} + + unsafe fn enumerate_adapters(&self) -> Vec> { + unsafe { + super::Adapter::expose(AdapterContext { + inner: self.inner.clone(), + }) + } + .into_iter() + .collect() + } +} + +struct DeviceContextHandle { + device: HDC, + window: HWND, +} + +impl Drop for DeviceContextHandle { + fn drop(&mut self) { + unsafe { + ReleaseDC(self.window, self.device); + }; + } +} + +pub struct Swapchain { + surface_context: WglContext, + surface_gl: glow::Context, + framebuffer: glow::Framebuffer, + renderbuffer: glow::Renderbuffer, + /// Extent because the window lies + extent: wgt::Extent3d, + format: wgt::TextureFormat, + format_desc: super::TextureFormatDesc, + #[allow(unused)] + sample_type: wgt::TextureSampleType, +} + +pub struct Surface { + window: HWND, + pub(super) presentable: bool, + swapchain: Option, + srgb_capable: bool, +} + +unsafe impl Send for Surface {} +unsafe impl Sync for Surface {} + +impl Surface { + pub(super) unsafe fn present( + &mut self, + _suf_texture: super::Texture, + context: &AdapterContext, + ) -> Result<(), crate::SurfaceError> { + let sc = self.swapchain.as_ref().unwrap(); + let dc = unsafe { GetDC(self.window) }; + if dc.is_null() { + log::error!( + "unable to get the device context from window: {}", + Error::last_os_error() + ); + return Err(crate::SurfaceError::Other( + "unable to get the device context from window", + )); + } + let dc = DeviceContextHandle { + device: dc, + window: self.window, + }; + + // Hold the lock for the shared context as we're using resources from there. + let _inner = context.inner.lock(); + + if let Err(e) = sc.surface_context.make_current(dc.device) { + log::error!("unable to make the surface OpenGL context current: {e}",); + return Err(crate::SurfaceError::Other( + "unable to make the surface OpenGL context current", + )); + } + + let gl = &sc.surface_gl; + + // Note the Y-flipping here. GL's presentation is not flipped, + // but main rendering is. Therefore, we Y-flip the output positions + // in the shader, and also this blit. + unsafe { + gl.blit_framebuffer( + 0, + sc.extent.height as i32, + sc.extent.width as i32, + 0, + 0, + 0, + sc.extent.width as i32, + sc.extent.height as i32, + glow::COLOR_BUFFER_BIT, + glow::NEAREST, + ) + }; + + if unsafe { SwapBuffers(dc.device) } == FALSE { + log::error!("unable to swap buffers: {}", Error::last_os_error()); + return Err(crate::SurfaceError::Other("unable to swap buffers")); + } + + Ok(()) + } + + pub fn supports_srgb(&self) -> bool { + self.srgb_capable + } +} + +impl crate::Surface for Surface { + unsafe fn configure( + &mut self, + device: &super::Device, + config: &crate::SurfaceConfiguration, + ) -> Result<(), crate::SurfaceError> { + // Remove the old configuration. + unsafe { self.unconfigure(device) }; + + let format_desc = device.shared.describe_texture_format(config.format); + let inner = &device.shared.context.inner.lock(); + + if let Err(e) = inner.context.make_current(inner.device) { + log::error!("unable to make the shared OpenGL context current: {e}",); + return Err(crate::SurfaceError::Other( + "unable to make the shared OpenGL context current", + )); + } + + let gl = &inner.gl; + let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| { + log::error!("Internal swapchain renderbuffer creation failed: {error}"); + crate::DeviceError::OutOfMemory + })?; + unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) }; + unsafe { + gl.renderbuffer_storage( + glow::RENDERBUFFER, + format_desc.internal, + config.extent.width as _, + config.extent.height as _, + ) + }; + + // Create the swap chain OpenGL context + + let dc = unsafe { GetDC(self.window) }; + if dc.is_null() { + log::error!( + "unable to get the device context from window: {}", + Error::last_os_error() + ); + return Err(crate::SurfaceError::Other( + "unable to get the device context from window", + )); + } + let dc = DeviceContextHandle { + device: dc, + window: self.window, + }; + + if let Err(e) = unsafe { setup_pixel_format(dc.device) } { + log::error!("unable to setup surface pixel format: {e}",); + return Err(crate::SurfaceError::Other( + "unable to setup surface pixel format", + )); + } + + let context = unsafe { wglCreateContext(dc.device) }; + if context.is_null() { + log::error!( + "unable to create surface OpenGL context: {}", + Error::last_os_error() + ); + return Err(crate::SurfaceError::Other( + "unable to create surface OpenGL context", + )); + } + let surface_context = WglContext { context }; + + if unsafe { wglShareLists(inner.context.context, surface_context.context) } == FALSE { + log::error!( + "unable to share objects between OpenGL contexts: {}", + Error::last_os_error() + ); + return Err(crate::SurfaceError::Other( + "unable to share objects between OpenGL contexts", + )); + } + + if let Err(e) = surface_context.make_current(dc.device) { + log::error!("unable to make the surface OpengL context current: {e}",); + return Err(crate::SurfaceError::Other( + "unable to make the surface OpengL context current", + )); + } + + let extra = Wgl::load_with(|name| load_gl_func(name, None)); + let extentions = extensions(&extra, dc.device); + if !(extentions.contains("WGL_EXT_swap_control") && extra.SwapIntervalEXT.is_loaded()) { + log::error!("WGL_EXT_swap_control is unsupported"); + return Err(crate::SurfaceError::Other( + "WGL_EXT_swap_control is unsupported", + )); + } + + let vsync = match config.present_mode { + wgt::PresentMode::Mailbox => false, + wgt::PresentMode::Fifo => true, + _ => { + log::error!("unsupported present mode: {:?}", config.present_mode); + return Err(crate::SurfaceError::Other("unsupported present mode")); + } + }; + + if unsafe { extra.SwapIntervalEXT(if vsync { 1 } else { 0 }) } == FALSE { + log::error!("unable to set swap interval: {}", Error::last_os_error()); + return Err(crate::SurfaceError::Other("unable to set swap interval")); + } + + let surface_gl = unsafe { + glow::Context::from_loader_function(|name| { + load_gl_func(name, Some(inner.opengl_module)) + }) + }; + + // Check that the surface context OpenGL is new enough to support framebuffers. + let version = unsafe { gl.get_parameter_string(glow::VERSION) }; + let version = super::Adapter::parse_full_version(&version); + match version { + Ok(version) => { + if version < (3, 0) { + log::error!( + "surface context OpenGL version ({}.{}) too old", + version.0, + version.1 + ); + return Err(crate::SurfaceError::Other( + "surface context OpenGL version too old", + )); + } + } + Err(e) => { + log::error!("unable to parse surface context OpenGL version: {e}",); + return Err(crate::SurfaceError::Other( + "unable to parse surface context OpenGL version", + )); + } + } + + let framebuffer = unsafe { surface_gl.create_framebuffer() }.map_err(|error| { + log::error!("Internal swapchain framebuffer creation failed: {error}"); + crate::DeviceError::OutOfMemory + })?; + unsafe { surface_gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) }; + unsafe { + surface_gl.framebuffer_renderbuffer( + glow::READ_FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + glow::RENDERBUFFER, + Some(renderbuffer), + ) + }; + unsafe { surface_gl.bind_renderbuffer(glow::RENDERBUFFER, None) }; + unsafe { surface_gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) }; + + unsafe { surface_gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) }; + unsafe { surface_gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) }; + + self.swapchain = Some(Swapchain { + surface_context, + surface_gl, + renderbuffer, + framebuffer, + extent: config.extent, + format: config.format, + format_desc, + sample_type: wgt::TextureSampleType::Float { filterable: false }, + }); + + Ok(()) + } + + unsafe fn unconfigure(&mut self, device: &super::Device) { + let gl = &device.shared.context.lock(); + if let Some(sc) = self.swapchain.take() { + unsafe { + gl.delete_renderbuffer(sc.renderbuffer); + gl.delete_framebuffer(sc.framebuffer) + }; + } + } + + unsafe fn acquire_texture( + &mut self, + _timeout_ms: Option, + ) -> Result>, crate::SurfaceError> { + let sc = self.swapchain.as_ref().unwrap(); + let texture = super::Texture { + inner: super::TextureInner::Renderbuffer { + raw: sc.renderbuffer, + }, + drop_guard: None, + array_layer_count: 1, + mip_level_count: 1, + format: sc.format, + format_desc: sc.format_desc.clone(), + copy_size: crate::CopyExtent { + width: sc.extent.width, + height: sc.extent.height, + depth: 1, + }, + }; + Ok(Some(crate::AcquiredSurfaceTexture { + texture, + suboptimal: false, + })) + } + unsafe fn discard_texture(&mut self, _texture: super::Texture) {} +} diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 57f67f9ef1..c44ba8962d 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -1176,7 +1176,7 @@ impl Limits { /// max_push_constant_size: 0, /// min_uniform_buffer_offset_alignment: 256, /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 60, + /// max_inter_stage_shader_components: 31, /// max_compute_workgroup_storage_size: 0, // + /// max_compute_invocations_per_workgroup: 0, // + /// max_compute_workgroup_size_x: 0, // + @@ -1202,6 +1202,9 @@ impl Limits { max_compute_workgroup_size_z: 0, max_compute_workgroups_per_dimension: 0, + // Value supported by Intel Celeron B830 on Windows (OpenGL 3.1) + max_inter_stage_shader_components: 31, + // Most of the values should be the same as the downlevel defaults ..Self::downlevel_defaults() } diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index d494b9c023..2114715866 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -61,10 +61,10 @@ features = ["raw-window-handle"] workspace = true features = ["metal"] -# We want the wgpu-core Direct3D backends on Windows. +# We want the wgpu-core Direct3D backends and OpenGL (via WGL) on Windows. [target.'cfg(windows)'.dependencies.wgc] workspace = true -features = ["dx11", "dx12"] +features = ["dx11", "dx12", "gles"] # We want the wgpu-core Vulkan backend on Unix (but not emscripten, macOS, iOS) and Windows. [target.'cfg(any(windows, all(unix, not(target_os = "emscripten"), not(target_os = "ios"), not(target_os = "macos"))))'.dependencies.wgc]