diff --git a/Cargo.lock b/Cargo.lock index 940b86cbba3..4663774fa73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3233,10 +3233,12 @@ version = "0.17.0" dependencies = [ "arrayvec 0.7.4", "cfg-if", + "env_logger", "js-sys", "log", "naga", "parking_lot", + "pollster", "profiling", "raw-window-handle 0.5.2", "serde", diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 66c25109161..48aaeb7c3d2 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -244,9 +244,9 @@ impl super::Adapter { } if let Some(full_ver) = full_ver { - if full_ver < (3, 2) { + if full_ver < (3, 3) { log::warn!( - "Returned GL context is {}.{}, when 3.2+ was requested", + "Returned GL context is {}.{}, when 3.3+ is needed", full_ver.0, full_ver.1 ); @@ -722,6 +722,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), @@ -741,27 +742,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)) } } @@ -786,8 +833,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 { diff --git a/wgpu-hal/src/gles/mod.rs b/wgpu-hal/src/gles/mod.rs index 6468a972d69..9a8bee8ea6f 100644 --- a/wgpu-hal/src/gles/mod.rs +++ b/wgpu-hal/src/gles/mod.rs @@ -211,6 +211,7 @@ struct AdapterShared { max_texture_size: u32, next_shader_id: AtomicU32, program_cache: Mutex, + es: bool, } pub struct Adapter { diff --git a/wgpu-hal/src/gles/shaders/clear.frag b/wgpu-hal/src/gles/shaders/clear.frag index 7766c12d9f6..1d0e414b28b 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 ac655e7f312..341b4e5f066 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/wgl.rs b/wgpu-hal/src/gles/wgl.rs index 115a886cbc1..4f1c2e30428 100644 --- a/wgpu-hal/src/gles/wgl.rs +++ b/wgpu-hal/src/gles/wgl.rs @@ -18,18 +18,20 @@ use std::{ use wgt::InstanceFlags; use winapi::{ shared::{ - minwindef::{FALSE, HMODULE, UINT}, - windef::{HDC, HGLRC}, + minwindef::{FALSE, HMODULE, LPARAM, LRESULT, UINT, WPARAM}, + windef::{HDC, HGLRC, HWND}, }, um::{ - libloaderapi::{GetProcAddress, LoadLibraryA}, + libloaderapi::{GetModuleHandleA, GetProcAddress, LoadLibraryA}, wingdi::{ wglCreateContext, wglDeleteContext, wglGetCurrentContext, wglGetProcAddress, - wglMakeCurrent, wglShareLists, ChoosePixelFormat, CreateCompatibleDC, DeleteDC, - DescribePixelFormat, GetPixelFormat, SetPixelFormat, SwapBuffers, PFD_DOUBLEBUFFER, - PFD_DRAW_TO_WINDOW, PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, + 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, CS_OWNDC, WNDCLASSEXA, }, - winuser::GetDC, }, }; @@ -133,8 +135,6 @@ unsafe impl Sync for WglContext {} struct Inner { opengl_module: HMODULE, gl: glow::Context, - /// Keep this alive as it's referenced by `context.device`. - _memory_device: DeviceContext, context: WglContext, } @@ -146,20 +146,6 @@ pub struct Instance { unsafe impl Send for Instance {} unsafe impl Sync for Instance {} -struct DeviceContext { - dc: HDC, -} - -impl Drop for DeviceContext { - fn drop(&mut self) { - unsafe { - if DeleteDC(self.dc) == FALSE { - log::error!("failed to delete device context {}", Error::last_os_error()); - } - }; - } -} - 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()) }; @@ -186,10 +172,11 @@ fn extensions(extra: &Wgl, dc: HDC) -> HashSet { 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 = 32; + format.cColorBits = 8; let index = unsafe { ChoosePixelFormat(dc, &format) }; if index == 0 { @@ -198,7 +185,10 @@ unsafe fn setup_pixel_format(dc: HDC) -> Result<(), crate::InstanceError> { Error::last_os_error(), )); } - if unsafe { SetPixelFormat(dc, index, &format) } == FALSE { + + 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(), @@ -229,6 +219,107 @@ unsafe fn setup_pixel_format(dc: HDC) -> Result<(), crate::InstanceError> { 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(); + + 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(), + )); + } + + 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: Mutex>> = Mutex::new(None); + let mut guard = GLOBAL.lock(); + if guard.is_none() { + *guard = Some(create_global_device_context().map(SendDc)); + } + guard.clone().unwrap().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 _) }; @@ -239,20 +330,9 @@ impl crate::Instance for Instance { )); } - // TODO: Try using EnumDisplayDevices to look for multiple GPUs. + let dc = get_global_device_context()?; - let dc = unsafe { CreateCompatibleDC(ptr::null_mut()) }; - if dc.is_null() { - return Err(crate::InstanceError::with_source( - String::from("unable to create memory device"), - Error::last_os_error(), - )); - } - let dc = DeviceContext { dc }; - - unsafe { setup_pixel_format(dc.dc)? }; - - let context = unsafe { wglCreateContext(dc.dc) }; + let context = unsafe { wglCreateContext(dc) }; if context.is_null() { return Err(crate::InstanceError::with_source( String::from("unable to create initial OpenGL context"), @@ -261,7 +341,7 @@ impl crate::Instance for Instance { } let context = WglContext { context, - device: dc.dc, + device: dc, }; context.make_current().map_err(|e| { crate::InstanceError::with_source( @@ -271,43 +351,42 @@ impl crate::Instance for Instance { })?; let extra = Wgl::load_with(|name| load_gl_func(name, None)); - let extentions = extensions(&extra, dc.dc); - - if !extentions.contains("WGL_ARB_create_context_profile") - || !extra.CreateContextAttribsARB.is_loaded() - { - return Err(crate::InstanceError::new(String::from( - "WGL_ARB_create_context_profile unsupported", - ))); - } - - let context = unsafe { - extra.CreateContextAttribsARB( - dc.dc as *const _, - ptr::null(), - [ - 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 - ] - .as_ptr(), - ) - }; - if context.is_null() { - return Err(crate::InstanceError::with_source( - String::from("unable to create OpenGL context"), - Error::last_os_error(), - )); - } - let context = WglContext { - context: context as *mut _, - device: dc.dc, + 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 context = unsafe { + extra.CreateContextAttribsARB( + dc as *const _, + ptr::null(), + [ + 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 + ] + .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 _, + device: dc, + } + } else { + context }; context.make_current().map_err(|e| { @@ -322,10 +401,13 @@ impl crate::Instance for Instance { }; let extra = Wgl::load_with(|name| load_gl_func(name, None)); - let extentions = extensions(&extra, dc.dc); + let extentions = extensions(&extra, dc); - let srgb_capable = extentions.contains("GL_ARB_framebuffer_sRGB") - || extentions.contains("WGL_EXT_framebuffer_sRGB"); + let srgb_capable = extentions.contains("WGL_EXT_framebuffer_sRGB") + || extentions.contains("WGL_ARB_framebuffer_sRGB") + || gl + .supported_extensions() + .contains("GL_ARB_framebuffer_sRGB"); if desc.flags.contains(InstanceFlags::VALIDATION) && gl.supports_debug() { log::info!("Enabling GL debug output"); @@ -345,7 +427,6 @@ impl crate::Instance for Instance { opengl_module, gl, context, - _memory_device: dc, })), srgb_capable, }) diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 57f67f9ef1b..c44ba8962d5 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 21147158664..e2186f280c9 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -109,6 +109,11 @@ workspace = true features = ["clone"] optional = true +# used for integration tests +[dev-dependencies] +pollster.workspace = true +env_logger.workspace = true + # used to test all the example shaders [dev-dependencies.naga] workspace = true @@ -248,9 +253,9 @@ web-sys = { workspace = true, features = [ "ImageBitmapRenderingContext", "Window", "WorkerGlobalScope", - "WorkerNavigator" + "WorkerNavigator", ] } wasm-bindgen.workspace = true js-sys.workspace = true wasm-bindgen-futures.workspace = true -parking_lot.workspace = true \ No newline at end of file +parking_lot.workspace = true diff --git a/wgpu/tests/multi-instance.rs b/wgpu/tests/multi-instance.rs new file mode 100644 index 00000000000..087fac71371 --- /dev/null +++ b/wgpu/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(); + } +}