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

Textured Backgrounds for Widgets and Areas #1758

Open
coderedart opened this issue Jun 21, 2022 · 4 comments
Open

Textured Backgrounds for Widgets and Areas #1758

coderedart opened this issue Jun 21, 2022 · 4 comments
Labels
feature New feature or request

Comments

@coderedart
Copy link
Contributor

coderedart commented Jun 21, 2022

Is your feature request related to a problem? Please describe.
Colors of a widget decided by the Style. sometimes, we require more customization to create great looking games / apps.

Describe the solution you'd like
allow widgets / areas to decide what they want to draw as their background. I would say the present custom paint callbacks should be a good way to let users do whatever they want.

Describe alternatives you've considered
the obvious simpler alternative is to use a image (TextureID) as a background. but sometimes we need to use the already drawn background pixels (game scenes / other widgets) to derive the present widget's background pixels. for example, blur themes popular in KDE desktop ricing. basically the background of a widget is transparent BUT the stuff behind the widget is slightly blurred.

https://reddit.com/r/unixporn should have more examples, but a simple screenshot is here to clearly show what i mean by blur
blur

Additional context
if using paint callbacks seems too risky / unsafe. just using a texture as a background of widget and being able to set transparency / blur in egui Style is fine too. once i realized how cool blur Styles in egui would look, i just can't stop imagining all the themes people would share.

@coderedart coderedart added the feature New feature or request label Jun 21, 2022
@emilk
Copy link
Owner

emilk commented Jul 21, 2022

Blur is difficult, as it requires a post-processing pass which complicates the egui rendering by a lot.

But adding custom Shape:s as background for widgets would be nice.

Once #1516 is solved this could be as easy as ui.label(…).background(my_texture)

@notgull
Copy link

notgull commented Jul 22, 2022

I'm not sure this is globally portable. This would require a compositor. For X11, at least, it's not guaranteed that a compositor is running at all, meaning that you would have to implement composition from scratch.

@coderedart
Copy link
Contributor Author

I'm not sure this is globally portable. This would require a compositor. For X11, at least, it's not guaranteed that a compositor is running at all, meaning that you would have to implement composition from scratch.

Compositor is required for window level transparency. I am talking within egui itself.

Basically, a container just draws a rectangle and fills it with background color atm. But, by allowing containers to instead take a paint callback which will draw the rectangle and use fragment shader to mix frame buffer and maybe background color of style to create blur effect.

Egui itself has no idea about what is happening. It just takes a shape paint callback when container needs to draw its background. The callback could simply be drawing a texture or blur effect or whatever as the background of the window.

@mxsdev
Copy link

mxsdev commented Dec 21, 2022

I was able to accomplish this - see here.

For anyone else who wants to accomplish it, the idea is basically this: register two textures at window size, let's call them back and front. The main render pass should render to the front texture view, and you should do one final render pass which copies front to the main surface view. You'll also have to recreate these textures on re-size so they are synced to the current window size.

Note that if you use eframe (or the winit extensions for egui-wgpu/egui-glow) then this will not be possible, since those frameworks render directly onto the window surface.

You'll need a basic blur shader which takes a texture as input and performs a gaussian blur (you can optimize this in a few ways, primarily as a Separable Filter and/or by performing many simple box blurs). I'll call this shader blur_rect.

Basically, you go:

  • Pass 1: front -- blur_rect --> back
  • Pass 2:back -- (copy) --> front

Pass 1 should perform a clear operation on back to transparent.

The reason you need two render passes and textures here is because you generally can't write to a texture while also reading from it. If anyone sees any way to do this with just one texture/pass, I'd love to hear it!

For me, I used WGPU. Since a wgpu::RenderPass encapsulates it's wgpu::Encoder, it would normally not be possible to create another render pass in a paint callback, so I had to write the following hack (requires package wgpu-core):

struct RenderPassExposed<'a> {
    id: wgpu_core::command::RenderPass,
    pub parent: &'a mut wgpu::CommandEncoder,
}

pub trait NewRenderPass<'a> {
    fn encoder(&mut self) -> &'a mut wgpu::CommandEncoder;
    fn begin_new_render_pass(&mut self, descriptor: &wgpu::RenderPassDescriptor<'a, '_>);
}

impl<'a> NewRenderPass<'a> for wgpu::RenderPass<'a> {
    fn begin_new_render_pass(
        &mut self, 
        descriptor: &wgpu::RenderPassDescriptor<'a, '_>
    ) {
        *self = self.encoder().begin_render_pass(descriptor);
    }

    fn encoder(&mut self) -> &'a mut wgpu::CommandEncoder {
        unsafe {
            std::mem::transmute::<
                &mut wgpu::RenderPass,
                &mut RenderPassExposed,
            >(self).parent
        }
    }
}

Then in PaintCallback, you can create a new RenderPass like so:

*render_pass = render_pass.begin_new_render_pass(&wgpu::RenderPassDescriptor {
    // ...
}

I thought a lot about how you could possibly pass the encoder to a callback, but I literally don't see any way since it's being mutably borrowed by the render_pass. I'm assuming that egui-glow exposes something like a native gl context, so it probably doesn't have the above issue.

Let me know if that makes sense @coderedart. I can draft a simple example repo in OpenGL (or WGPU) if you're interested. I'm also working on some more in-depth tutorials for egui where I'll probably go over it in a lot more in depth (as well as other stuff like popout-window context menus) :)

Regarding implementing this into egui itself, the existence of blur requires rendering to a separate texture in some capacity (the window surface texture [known in opengl as "default framebuffer"] cannot be read from, only written to as a color attachment). This is additional overhead that IMO users shouldn't be forced to opt into. These issues also prevents abstracting away blur-regions into something like a library, since it requires a totally different render pipeline.

One last aside: one major optimization is to perform filter separation on the blur_rect shader between the two render passes. Basically, you can perform the blur purely horizontally on the first pass from front to back, and then purely vertically back to front. Since the filter is separable, this will work, and it takes the algorithm from $O(r^2)$ to $O(r)$, where $r$ is the blur radius.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants