当我每次渲染屏幕时都不调用`graphics :: clear`时,如何阻止Piston使屏幕闪烁?

问题描述 投票:16回答:1

考虑two programs,以及它们之间的区别:

$ diff flashes/src/main.rs doesnt_flash/src/main.rs
22,23c22
<
<     let mut i = 0;
---
>     let mut cursor_poses: Vec<(f64, f64)> = Vec::new();
28c27
<             mx = x; my = y;
---
>             cursor_poses.push((x,y));
32,33c31,33
<                     if i == 0 {
<                         graphics::clear([1.0; 4], g);
---
>                     graphics::clear([1.0; 4], g);
>                     for &(x, y) in cursor_poses.iter() {
>                         draw_cursor_pos([x, y], &c, g);
35,36d34
<                     draw_cursor_pos([mx, my], &c, g);
<                     i+=1;

Video demonstration of the two programs.

该程序是一个非常基本的绘图程序,只有一个画笔宽度,画笔笔触颜色,画布大小,没有保存等;哦,要停止绘画,将鼠标移出窗口,因为每次你越过窗户,这都算作绘图;-)

flashes.rs不会在每次达到e.render_args()时绘制每个像素,除了第一次。每次达到doesnt_flash.rs时,e.render_args()都会绘制每个像素。这是两个程序之间的唯一区别。

虽然在这个程序中生成内容并不需要很长时间,但是当鼠标在窗口上移动时重新生成数百次是可以接受的,这似乎是低效的。理论上,随着越来越多的点被添加到屏幕上,gl.draw的每次迭代都需要越来越长的时间。在实践中,调用graphics::ellipse一次与一万次之间的差异在现代硬件上并不重要。

我想写的其他程序不会那么奢侈,因为生成结果要花在屏幕上需要更长的时间。

在仔细阅读API的过程中,我想出了“无所事事”的明显方法。我假设我必须将我的屏幕更改写入某个缓冲区对象,然后如果调用GlGraphics,则将e.render_args()反馈给此缓冲区对象,但我不需要更新屏幕。

问题是,我似乎无法找到这个缓冲区对象。 :-(

如果没有屏幕闪烁,我怎么能“无所事事”?如果我的理论是正确的,我如何绘制到GlGraphics缓冲区而不是屏幕,然后当我没有任何新的绘制时,将我的缓冲区送回屏幕?


Cargo.toml

[package]
name = "stackoverflow-piston-example"
version = "0.0.0"
authors = ["Fred"]
description = "Note: This program can be used for both of the programs below. Simply use `cargo new` and save either of the below files as `src/main.rs`"
keywords = []

[dependencies]
piston = "0.35.0"
piston2d-opengl_graphics = "0.50.0"
piston2d-graphics = "0.24.0"
piston2d-touch_visualizer = "0.8.0"
pistoncore-sdl2_window = "0.47.0"

doesn't_flash.人生

extern crate piston;
extern crate opengl_graphics;
extern crate graphics;
extern crate touch_visualizer;
extern crate sdl2_window;

use opengl_graphics::{ GlGraphics, OpenGL };
use graphics::{ Context, Graphics };
use piston::input::*;
use piston::event_loop::*;
use sdl2_window::Sdl2Window as AppWindow;

static CURSOR_POS_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];

fn main() {
    let opengl = OpenGL::V3_2;
    let mut window: AppWindow = piston::window::WindowSettings::new("Example for StackOverflow", [600, 600])
        .exit_on_esc(true).opengl(opengl).build().unwrap();

    let ref mut gl = GlGraphics::new(opengl);
    let (mut mx, mut my) = (0., 0.);
    let mut cursor_poses: Vec<(f64, f64)> = Vec::new();

    let mut events = Events::new(EventSettings::new().lazy(true));
    while let Some(e) = events.next(&mut window) {
        e.mouse_cursor(|x, y| {
            cursor_poses.push((x,y));
        });
        if let Some(args) = e.render_args() {
            gl.draw(args.viewport(), |c, g| {
                    graphics::clear([1.0; 4], g);
                    for &(x, y) in cursor_poses.iter() {
                        draw_cursor_pos([x, y], &c, g);
                    }
                }
            );
        }
    }
}

fn draw_cursor_pos<G: Graphics>(
    cursor: [f64; 2],
    c: &Context,
    g: &mut G,
) {
    graphics::ellipse(
        CURSOR_POS_COLOR,
        graphics::ellipse::circle(cursor[0], cursor[1], 4.0),
        c.transform,
        g
    );
}

flashes.人生

extern crate piston;
extern crate opengl_graphics;
extern crate graphics;
extern crate touch_visualizer;
extern crate sdl2_window;

use opengl_graphics::{ GlGraphics, OpenGL };
use graphics::{ Context, Graphics };
use piston::input::*;
use piston::event_loop::*;
use sdl2_window::Sdl2Window as AppWindow;

static CURSOR_POS_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];

fn main() {
    let opengl = OpenGL::V3_2;
    let mut window: AppWindow = piston::window::WindowSettings::new("Example for StackOverflow", [600, 600])
        .exit_on_esc(true).opengl(opengl).build().unwrap();

    let ref mut gl = GlGraphics::new(opengl);
    let (mut mx, mut my) = (0., 0.);

    let mut i = 0;

    let mut events = Events::new(EventSettings::new().lazy(true));
    while let Some(e) = events.next(&mut window) {
        e.mouse_cursor(|x, y| {
            mx = x; my = y;
        });
        if let Some(args) = e.render_args() {
            gl.draw(args.viewport(), |c, g| {
                    if i == 0 {
                        graphics::clear([1.0; 4], g);
                    }
                    draw_cursor_pos([mx, my], &c, g);
                    i+=1;
                }
            );
        }
    }
}

fn draw_cursor_pos<G: Graphics>(
    cursor: [f64; 2],
    c: &Context,
    g: &mut G,
) {
    graphics::ellipse(
        CURSOR_POS_COLOR,
        graphics::ellipse::circle(cursor[0], cursor[1], 4.0),
        c.transform,
        g
    );
}
rust rust-piston
1个回答
11
投票

我认为闪烁是由缓冲区交换引起的:在flashes.rs中,只有第一个要绘制的缓冲区被清除。如果你不走运,第二个将全部为零,或者剩余的gpu内存。根据OpenGL wiki,没有好办法调用graphics::clear

现代OpenGL程序应始终使用双缓冲。 。 。应始终清除缓冲区。在更老的硬件上,有一种技术可以在不清除场景的情况下逃脱,但即便是在半新硬件上,这实际上也会让事情变慢。所以总是要清楚。

相反,通常的方法是将您的更改累积到纹理或渲染缓冲区,然后将其绘制到屏幕上,完全如您所述。

我无法在opengl_graphics内找到任何方法(在任何地方都没有调用gl::GenFramebuffers)但是使用原始gl调用设置相对简单。 (我使用纹理而不是渲染缓冲区,因为它们具有得到Image::draw等高级方法支持的显着优势。)

extern crate piston;
extern crate opengl_graphics;
extern crate graphics;
extern crate sdl2_window;
extern crate gl;

use opengl_graphics::{ GlGraphics, OpenGL, Texture, TextureSettings };
use graphics::{ Context, Graphics, Transformed };
use graphics::image::Image;
use piston::input::*;
use piston::event_loop::*;
use piston::window::Window;
use sdl2_window::Sdl2Window as AppWindow;
use gl::types::GLuint;

static CURSOR_POS_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];

fn main() {
    let opengl = OpenGL::V3_2;
    let mut window: AppWindow = piston::window::WindowSettings::new("Example for StackOverflow", [600, 600])
        .exit_on_esc(true).opengl(opengl).build().expect("window");

    let ref mut gl = GlGraphics::new(opengl);
    let (mut mx, mut my) = (0., 0.);

    let draw_size = window.draw_size();
    // It would also be possible to create a texture by hand using gl::GenTextures and call
    // gl::TexImage2D with a null pointer for the data argument, which would require another unsafe
    // block but would save this allocation
    let texture_buf = vec![0u8; draw_size.width as usize * draw_size.height as usize];
    let texture = Texture::from_memory_alpha(&texture_buf, draw_size.width, draw_size.height,
                                             &TextureSettings::new()).expect("texture");

    let fbo;
    unsafe {
        let mut fbos: [GLuint; 1] = [0];
        // Create a Framebuffer Object that we can draw to later
        gl::GenFramebuffers(1, fbos.as_mut_ptr());
        fbo = fbos[0];
        // Switch to it as the active framebuffer
        gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
        // Set up the framebuffer object so that draws to it will go to the texture
        gl::FramebufferTexture2D(gl::FRAMEBUFFER,
                                 gl::COLOR_ATTACHMENT0, // draw colors, not depth or stencil data
                                 gl::TEXTURE_2D, // the texture's type
                                 texture.get_id(),
                                 0); // mipmap level
    }

    let mut events = Events::new(EventSettings::new().lazy(true));
    while let Some(e) = events.next(&mut window) {
        e.mouse_cursor(|x, y| {
            mx = x; my = y;
        });
        e.render(|args| {
            // Switch to the texture framebuffer and draw the cursor
            unsafe {
                gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
            }
            gl.draw(args.viewport(), |c, g| {
                draw_cursor_pos([mx, my], &c, g);
            });

            // Switch to the window framebuffer and draw the texture
            unsafe {
                gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
            }
            gl.draw(args.viewport(), |c, g| {
                graphics::clear([1f32, 1f32, 1f32, 0f32], g);
                // I can't entirely explain this.  We already applied the viewport transform when
                // we were rendering the cursor, so I think the texture is right-side-up for GL,
                // but piston::Image is expecting an image laid out in screen coordinates.
                // Since there is an offset in the viewport transform, the flip has to be applied
                // first, otherwise it would flip across the origin.
                let flipped = c.transform.prepend_transform(graphics::math::scale(1., -1.));
                Image::new().draw(&texture, &c.draw_state, flipped, g);
            });
        });
    }
}

fn draw_cursor_pos<G: Graphics>(
    cursor: [f64; 2],
    c: &Context,
    g: &mut G,
) {
    graphics::ellipse(
        CURSOR_POS_COLOR,
        graphics::ellipse::circle(cursor[0], cursor[1], 4.0),
        c.transform,
        g
    );
}

或者,gfx后端具有有前途的Factory::CreateRenderTarget方法。我的硬件不支持它,但我相信使用它看起来大概是这样的:

extern crate piston;
extern crate graphics;
extern crate piston_window;
extern crate gfx_core;

use graphics::{ Context, Graphics, Transformed };
use graphics::image::Image;
use piston::input::*;
use piston::event_loop::*;
use piston::window::Window;
use piston_window::{ PistonWindow, OpenGL, G2dTexture };
use gfx_core::factory::Factory;
use gfx_core::texture::{ SamplerInfo, FilterMethod, WrapMode, Size };

static CURSOR_POS_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];

fn main() {
    let opengl = OpenGL::V2_1;
    let window_settings =
        piston::window::WindowSettings::new("Example for StackOverflow", [600, 600])
        .opengl(opengl)
        .exit_on_esc(true);
    let mut window: PistonWindow = window_settings.build().expect("window");
    window.set_lazy(true);

    let size = window.draw_size();

    let (texture_handle, shader_view, target) = window.factory.create_render_target(size.width as Size, size.height as Size)
        .expect("render target");
    let sampler = window.factory.create_sampler(SamplerInfo::new(FilterMethod::Scale, WrapMode::Tile));
    let texture = G2dTexture {
        surface: texture_handle,
        sampler: sampler,
        view: shader_view,
    };
    let stencil = window.factory.create_depth_stencil_view_only(size.width as Size, size.height as Size)
        .expect("stencil");

    let (mut mx, mut my) = (0., 0.);

    while let Some(e) = window.next() {
        e.mouse_cursor(|x, y| {
            mx = x; my = y;
        });
        if let Some(args) = e.render_args() {
            window.g2d.draw(&mut window.encoder, &target, &stencil, args.viewport(), |c, g| {
                draw_cursor_pos([mx, my], &c, g);
            });

            window.draw_2d(&e, |c, g| {
                graphics::clear([1f32, 1f32, 1f32, 0f32], g);
                let flipped = c.transform.prepend_transform(graphics::math::scale(1., -1.));
                Image::new().draw(&texture, &c.draw_state, flipped, g);
            });
        }
    }
}

fn draw_cursor_pos<G: Graphics>(
    cursor: [f64; 2],
    c: &Context,
    g: &mut G,
) {
    graphics::ellipse(
        CURSOR_POS_COLOR,
        graphics::ellipse::circle(cursor[0], cursor[1], 4.0),
        c.transform,
        g
    );
}
© www.soinside.com 2019 - 2024. All rights reserved.