16

考虑两个程序,以及它们之间的区别:

$ 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;

两个程序的视频演示。

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

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

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

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

在仔细阅读 API 时,我没有想出明显的方法来“什么都不做”。我假设我必须将屏幕更改写入某个缓冲区对象,然后在调用GlGraphics时反馈此缓冲区对象,e.render_args()但我不需要更新屏幕。

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

我怎样才能“什么都不做”而不让屏幕闪烁?如果我的理论是正确的,我怎样才能绘制到GlGraphics缓冲区而不是屏幕,然后当我没有任何新东西要绘制时将缓冲区反馈回屏幕?


货运.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"

dont_flash.rs

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
    );
}

闪光灯.rs

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
    );
}
4

1 回答 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
    );
}
于 2017-12-25T22:10:12.010 回答