考虑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.

该程序是一个extremely basic画图程序,只有一个画笔宽度、画笔笔划 colored颜色 、画布大小、不保存等;哦,要停止画画,请将鼠标移出窗口,因为每次你经过窗口,这都算作画画;-)

flashes.rs不会在每次达到e.render_args()时绘制每个像素,第一次除外.doesnt_flash.rs确实会在每次达到e.render_args()时绘制每个像素.这是两个项目之间唯一的区别.

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

我想写的其他程序不会有这样的奢侈,因为它需要更长的时间才能生成结果并放到屏幕上.

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

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

我怎么能"什么都不做"而不让屏幕闪烁?如果我的理论是正确的,我怎么能画一个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"

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

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

推荐答案

我认为闪烁是由缓冲区交换引起的:在flashes.rs中,只有第一个要被抽取的缓冲区被清除.第二个是全零,如果你运气不好的话,也可以是gpu剩余的内存.根据OpenGL wiki,拨打graphics::clear没有什么好办法:

现代OpenGL程序应始终使用双缓冲.

相反,通常的方法是累积对纹理或renderbuffer的更改,然后将其绘制到屏幕上,就像您描述的那样.

我也找不到任何方法在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
    );
}

Rust相关问答推荐

空字符串转换为Box字符串时是否分配?<>

有没有更好的方法从HashMap的条目初始化 struct ?

使用Clap时如何将String作为Into Str参数传递?

有没有办法指定只在Rust的测试中有效的断言?

如何将映射反序列化为具有与键匹配的字段的定制 struct 的向量?

作为1字节位掩码的布尔值 struct

`*mut[T]`与`*mut T`的区别

失真图像图形捕获Api

我应该如何表达具有生命周期参数的类型的总排序,同时允许与不同生命周期进行比较?

如何在 Rust 中打印 let-else 语句中的错误?

Rust 并行获取对 ndarray 的每个元素的可变引用

相当于 Rust 中 C++ 的 std::istringstream

Rust中的位移操作对范围有什么影响?

我如何取消转义,在 Rust 中多次转义的字符串?

Rust 中的方法调用有什么区别?

强制特征仅在 Rust 中的给定类型大小上实现

将 `&T` 转换为新类型 `&N`

你能告诉我如何在 Rust 中使用定时器吗?

Rust 跨同一文件夹中文件的可见性

HashMap entry() 方法使borrow 的时间比预期的长