Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERROR_CLASS_DOES_NOT_EXIST opening window from DLL #1445

Closed
SamRodri opened this issue Aug 23, 2022 · 8 comments
Closed

ERROR_CLASS_DOES_NOT_EXIST opening window from DLL #1445

SamRodri opened this issue Aug 23, 2022 · 8 comments
Labels

Comments

@SamRodri
Copy link

Building a glutin context compiled as a dynamically loaded DLL causes:

OsError("GetClassInfoExW function failed: <localized message> (os error 1411)")

This error is caused by the GetModuleHandleW(std::ptr::null()) usage in glutin/src/api/wgl/mod.rs, the function returns the handle for the executable, not the DLL.

A similar bug was fixed in winit, see winit/2301

@kchibisov
Copy link
Member

kchibisov commented Sep 3, 2022

Fixed in #1435.

@Boscop
Copy link

Boscop commented Dec 4, 2022

I'm running into a similar issue:
I'm spawning an egui window from a DLL and after upgrading egui from 0.18 to 0.19 and glutin from 0.28 to 0.29 I get this panic:

thread '' panicked at 'called Result::unwrap() on an Err value: OsError("GetClassInfoExW function failed: Class does not exist. (os error 1411)")'

let gl_window = unsafe {
	ContextBuilder::new()
		.with_depth_buffer(0)
		.with_srgb(true)
		.with_stencil_buffer(0)
		.with_vsync(true)
		.build_windowed(window_builder, event_loop)
		.unwrap() // Panics on this line
		.make_current()
		.unwrap()
};

This code used to work before with glutin 0.28.

@kchibisov That PR was merged on September 3rd, and glutin 0.29.1 was released on August 10th, so this fix is only in glutin 0.30, right?
Is this definitely fixed in version 0.30? (Or is this maybe a different issue than the one described above?)

I want to upgrade to glutin 0.30 but I cannot find ContextBuilder anymore. What is the equivalent now? :)

This is my code that I need to upgrade:

pub struct GuiState {
	event_loop: EventLoop<()>,
	gl_window: WindowedContext<PossiblyCurrent>,
	gl: Arc<glow::Context>,
	egui_glow: egui_glow::EguiGlow,
	clear_color: [f32; 3],
}

impl GuiState {
	pub fn new(title: &str) -> Self {
		fn create_display(
			event_loop: &EventLoop<()>,
			title: &str,
		) -> (WindowedContext<PossiblyCurrent>, glow::Context) {
			let window_builder = WindowBuilder::new()
				.with_resizable(true)
				.with_inner_size(LogicalSize { width: 1600., height: 1000. })
				.with_maximized(true)
				.with_title(title);

			let gl_window = unsafe {
				ContextBuilder::new()
					.with_depth_buffer(0)
					.with_srgb(true)
					.with_stencil_buffer(0)
					.with_vsync(true)
					.build_windowed(window_builder, event_loop)
					.unwrap()
					.make_current()
					.unwrap()
			};

			let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };

			(gl_window, gl)
		}

		let event_loop = EventLoopBuilder::new().with_any_thread(true).build();
		let (gl_window, gl) = create_display(&event_loop, title);
		let gl = std::sync::Arc::new(gl);
		let egui_glow = egui_glow::EguiGlow::new(&event_loop, gl.clone());

		egui_glow.egui_ctx.set_pixels_per_point(gl_window.window().scale_factor() as _);

		let fonts = FontDefinitions::default();
		egui_glow.egui_ctx.set_fonts(fonts);

		egui_glow.egui_ctx.set_visuals(egui::style::Visuals::dark());

		Self { event_loop, gl_window, gl, egui_glow, clear_color: [0.1, 0.1, 0.1] }
	}

// ...

@madsmtm madsmtm added the WGL label Dec 4, 2022
@kchibisov
Copy link
Member

@Boscop I'd suggest to look into the example. It has everything in place and should provide you with enough of information on how to adjust your current code.

Basically you have separate context, config, and a window. So what you'd need to do here is to pass your window_builder to glutin-winit crate, and then use the config and window it returned you to create NotCurrentContext and Surface for your rendering.

@Boscop
Copy link

Boscop commented Dec 5, 2022

@kchibisov Thanks for the quick reply! Btw, which example do you mean I should look at?

@kchibisov
Copy link
Member

@Boscop
Copy link

Boscop commented Dec 5, 2022

Thanks, I was able to make it compile, but I'm not sure if my new code is equivalent to the old code.
Could you take a look please, and tell me if I did it correctly? :)
This is my new code:

pub struct GuiState {
	event_loop: EventLoop<()>,
	gl_window: GlWindow,
	gl_context: PossiblyCurrentContext,
	gl: Arc<glow::Context>,
	egui_glow: egui_glow::EguiGlow,
	clear_color: [f32; 3],
}
impl GuiState {
	pub fn new(title: &str) -> Self {
		fn create_display(
			event_loop: &EventLoop<()>,
			title: &str,
		) -> (GlWindow, PossiblyCurrentContext, glow::Context) {
			let window_builder = WindowBuilder::new()
				.with_resizable(true)
				.with_inner_size(LogicalSize { width: 1600., height: 1000. })
				.with_maximized(true)
				.with_title(title);

			let template = ConfigTemplateBuilder::new()
				.with_alpha_size(8)
				.with_depth_size(0)
				.with_stencil_size(0);

			let display_builder = DisplayBuilder::new()
				.with_window_builder(Some(window_builder));

			let (window, gl_config) = display_builder
				.build(&event_loop, template, |configs| {
					// Find the config with the maximum number of samples
					configs
						.reduce(
							|accum, config| {
								if config.num_samples() > accum.num_samples() { config } else { accum }
							},
						)
						.unwrap()
				})
				.unwrap();
			let window = window.unwrap();

			debug!("Picked a config with {} samples", gl_config.num_samples());

			let raw_window_handle = window.raw_window_handle();

			// XXX The display could be obtained from the any object created by it, so we
			// can query it from the config.
			let gl_display = gl_config.display();

			// The context creation part. It can be created before surface and that's how
			// it's expected in multithreaded + multiwindow operation mode, since you
			// can send NotCurrentContext, but not Surface.
			let context_attributes = ContextAttributesBuilder::new()
				// .with_profile(GlProfile::Core)
				.build(raw_window_handle);

			// Since glutin by default tries to create OpenGL core context, which may not be
			// present we should try gles.
			let fallback_context_attributes = ContextAttributesBuilder::new()
				.with_context_api(ContextApi::OpenGl(None /* Some(Version::new(4, 6)) */))
				.build(raw_window_handle);
			let mut not_current_gl_context = Some(unsafe {
				gl_display.create_context(&gl_config, &context_attributes).unwrap_or_else(|_| {
					gl_display
						.create_context(&gl_config, &fallback_context_attributes)
						.expect("failed to create context")
				})
			});

			let gl_window = GlWindow::new(window, &gl_config);

			// Make it current.
			let gl_context = not_current_gl_context.take().unwrap().make_current(&gl_window.surface).unwrap();

			// Try setting vsync
			if let Err(res) = gl_window
				.surface
				.set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
			{
				error!("Error setting vsync: {:?}", res);
			}

			let gl = unsafe {
				glow::Context::from_loader_function(|s| {
					gl_display.get_proc_address(CString::new(s).unwrap().as_c_str())
				})
			};

			(gl_window, gl_context, gl)
		}

		let event_loop = EventLoopBuilder::new().with_any_thread(true).build();
		let (gl_window, gl_context, gl) = create_display(&event_loop, title);
		let gl = Arc::new(gl);
		let egui_glow = egui_glow::EguiGlow::new(&event_loop, Arc::clone(&gl));
		egui_glow.egui_ctx.set_pixels_per_point(gl_window.window.scale_factor() as _);
		let fonts = FontDefinitions::default();
		egui_glow.egui_ctx.set_fonts(fonts);
		egui_glow.egui_ctx.set_visuals(egui::style::Visuals::dark());

		Self { event_loop, gl_window, gl_context, gl, egui_glow, clear_color: [0.1, 0.1, 0.1] }
	}
	// ...
}

// From: https://github.com/rust-windowing/glutin/blob/b4d54f9a766db90fcd1b08048e35310313e89d1a/glutin_examples/src/lib.rs#L176
struct GlWindow {
	// XXX the surface must be dropped before the window.
	surface: Surface<WindowSurface>,
	window: Window,
}
impl GlWindow {
	fn new(window: Window, config: &Config) -> Self {
		let (width, height): (u32, u32) = window.inner_size().into();
		let raw_window_handle = window.raw_window_handle();
		let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().build(
			raw_window_handle,
			NonZeroU32::new(width).unwrap(),
			NonZeroU32::new(height).unwrap(),
		);
		let surface = unsafe { config.display().create_window_surface(config, &attrs).unwrap() };
		Self { window, surface }
	}
}

It works but I'm wondering:

  1. How can I ensure I get the latest opengl version at runtime?
  2. Should I use .with_context_api(ContextApi::OpenGl(None)) or .with_context_api(ContextApi::OpenGl(Some(Version::new(4, 6))))?
  3. If I don't need to support Android, is it ok that I create the window (and call make_current) before the loop?
  4. Is my code equivalent to my previous code in all aspects? I couldn't find any equivalent for ContextBuilder::with_srgb(true)
  5. Is the closure I'm passing to display_builder.build() correct regarding my intentions to match my previous code?
  6. Why do I have to enable vsync in this weird way? :)

Btw, since I need to run the GUI in the same thread, I'm using run_return. But I'm not sure how I should use time_until_next_repaint to set the ControlFlow correctly. Do you see any obvious improvement potential in this function?

pub fn run_frame_blocking(&mut self, mut frame: impl FnMut(&egui::Context)) -> bool {
	let mut running = true;
	// TODO: Don't block while window/mouse events arrive
	self.event_loop.run_return(|event, _, control_flow| {
		let mut redraw = || {
			// This was before, with egui 0.18
			// let needs_repaint = self.egui_glow.run(self.gl_window.window(), |ctx| frame(ctx));

			// The return value changed with egui 0.19
			let _time_until_next_repaint = self.egui_glow.run(&self.gl_window.window, |ctx| frame(ctx));

			let needs_repaint = false; // TODO: Determine based on time_until_next_repaint

			*control_flow = if needs_repaint {
				self.gl_window.window.request_redraw();
				ControlFlow::Poll
			} else {
				ControlFlow::Exit
			};

			{
				unsafe {
					use glow::HasContext as _;
					self.gl.clear_color(
						self.clear_color[0],
						self.clear_color[1],
						self.clear_color[2],
						1.0,
					);
					self.gl.clear(glow::COLOR_BUFFER_BIT);
				}

				// draw things behind egui here

				self.egui_glow.paint(&self.gl_window.window);

				// draw things on top of egui here

				self.gl_window.surface.swap_buffers(&self.gl_context).unwrap();
			}
		};

		// Based on: https://github.com/emilk/egui/blob/7eeb292adfacd9311a420ac3ea225e2261a8f8d3/egui_glow/examples/pure_glow.rs#L56
		match event {
			// Platform-dependent event handlers to workaround a winit bug
			// See: https://github.com/rust-windowing/winit/issues/987
			// See: https://github.com/rust-windowing/winit/issues/1619
			Event::RedrawEventsCleared if cfg!(windows) => redraw(),
			Event::RedrawRequested(_) if !cfg!(windows) => redraw(),

			Event::WindowEvent { event, .. } => {
				if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) {
					running = false;
				}

				if let WindowEvent::Resized(size) = &event {
					self.gl_window.surface.resize(
						&self.gl_context,
						NonZeroU32::new(size.width).unwrap(),
						NonZeroU32::new(size.height).unwrap(),
					);
				} else if let WindowEvent::ScaleFactorChanged { new_inner_size: size, scale_factor } =
					&event
				{
					self.gl_window.surface.resize(
						&self.gl_context,
						NonZeroU32::new(size.width).unwrap(),
						NonZeroU32::new(size.height).unwrap(),
					);
					self.egui_glow.egui_ctx.set_pixels_per_point(*scale_factor as _);
				}

				self.egui_glow.on_event(&event);

				self.gl_window.window.request_redraw();
			}
			/*Event::LoopDestroyed => {
				self.egui_glow.destroy();
			}*/
			_ => (),
		}
	});
	running
}

@kchibisov
Copy link
Member

How can I ensure I get the latest opengl version at runtime?

Unfortunately OpenGL has no such way to do so. Just request a version you want and fallback on versions you can work with limited set of features? Desktop GL tends to pick max available though, but it's not defined in the standard.

Should I use .with_context_api(ContextApi::OpenGl(None)) or .with_context_api(ContextApi::OpenGl(Some(Version::new(4, 6))))

If you use OpenGL 4.6 then yes? If you only use OpenGL 3.3 you should request 3.3?

Is my code equivalent to my previous code in all aspects? I couldn't find any equivalent for ContextBuilder::with_srgb(true)

You filter configs by srgb_capable in general you don't need such things in OpenGL since the way you request srgb is by enabling it with glEnable. The reason existed it before is purely for gles2 with EGL since you specify SRGB or Linear on context creation time.

If I don't need to support Android, is it ok that I create the window (and call make_current) before the loop?

Yes.

Why do I have to enable vsync in this weird way? :)

Multiple surfaces can use the same context, vsync is per surface and define during runtime and not on context creation. Also it may be disabled by the driver so you'll fail to activate it.

Is the closure I'm passing to display_builder.build() correct regarding my intentions to match my previous code?

Not really, since before you were not asking for any multisampling. You might either pick first or filter by srgb_capable. Be aware that srgb may not be present.

Btw, since I need to run the GUI in the same thread, I'm using run_return. But I'm not sure how I should use time_until_next_repaint to set the ControlFlow correctly. Do you see any obvious improvement potential in this function?

That's out of scope glutin.

In general it looks sort of good.

@Boscop
Copy link

Boscop commented Dec 6, 2022

Thanks :)

For the setting the ControlFlow in run_return, I settled on this:

let time_until_next_repaint = self.egui_glow.run(&self.gl_window.window, |ctx| frame(ctx));
let needs_repaint = time_until_next_repaint.is_zero();

*control_flow = if needs_repaint {
	self.gl_window.window.request_redraw();
	ControlFlow::Poll
} else {
	ControlFlow::Exit
};

Btw, also another issue appeared after upgrading winit/glutin, do you have any idea what changed in the window drop/destroy code that causes this? rust-windowing/winit#2583

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging a pull request may close this issue.

4 participants