Как остановить Piston от создания экрана, если я не называю "graphics: clear" каждый раз, когда экран отображается?

Рассмотрим две программы и разницу между ними:

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

Видео демонстрация двух программ.

Программа представляет собой чрезвычайно базовую программу рисования с единственной шириной кисти, цветом штриховки, размером холста, без сохранения и т.д.; oh и прекратить рисование, вытащите мышь из окна, так как каждый раз, когда вы переходите через окно, это считается рисованием; -)

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"

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

Ответы

Ответ 1

Я думаю, что мигание вызвано заменой буфера: в flashes.rs очищается только первый буфер, который нужно вставить. Второй - все нули или оставшаяся память gpu, если вам не повезло. В соответствии с OpenGL wiki там нет хорошего способа вызова graphics::clear:

Современная программа OpenGL должна всегда использовать двойную буферизацию., Буферы всегда должны быть очищены. На гораздо более старом оборудовании был способ уйти, не очищая сцену, но даже на полу-недавнее оборудование, это фактически сделает вещи медленнее. Поэтому всегда сделайте ясно.

Вместо этого обычный способ состоит в том, чтобы скопировать ваши изменения в текстуру или рендеринг буфера, а затем нарисовать это на экране точно так, как вы описали.

Я не мог найти никакого способа сделать это изнутри opengl_graphics либо (нет вызовов в gl::GenFramebuffers в любом месте в нем), но относительно просто настроить его с помощью raw gl вызовов. (Я использовал текстуры вместо renderbuffers, потому что они имеют значительное преимущество в поддержке высокоуровневых методов, таких как 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 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
    );
}