Я хочу форсировать рендеринг, но рисую только настолько быстро, насколько это возможно (InvalidateVisual / CompositionTarget.Rendering) - PullRequest
1 голос
/ 06 января 2012

Я работаю над компонентом визуализации WPF / Silverlight (и вскоре WP7) в реальном времени, и я ищу лучшее решение для перерисовки всего компонента в стиле Game-loop.Перерисовка должна производиться по требованию, но я не хочу создавать резервные копии сообщений с помощью перерисовки вызовов.Большая часть рисования в моем компоненте выполняется с использованием не-WPF-примитивов (например, Bitmap Interop, Direct2D), поэтому мой код не использует InvalidateVisual, и в результате в настоящее время выглядит так

// Pseudocode, doesnt compile, just to convey the meaning
public void InvalidateElement()
{
    if (CurrentlyDrawing)
        return;

    Dispatcher.BeginInvoke(() => 
    {
        CurrentlyDrawing = true;

        DoDrawInternal();

        CurrentlyDrawing = false;            
    }
}

Хорошо, так чтоотлично.Если я звоню InvalidateElement много раз, я получаю хорошую отзывчивость.Однако я хочу, чтобы я мог как можно быстрее передавать данные в свой компонент визуализации, но рисовать только тогда, когда компонент способен рисовать, и не продолжать рисовать, чтобы догнать данные после завершения входного потока.

Нет, я не могу переопределить OnRender, я использую не WPF-рисование внутри WPF; -)

По сути, мне нужно что-то вроде старого Invalidate () / OnPaint в WindowsForms,или, что еще лучше, игровой цикл в DirectX.

В настоящий момент я получаю ситуацию, когда, если у меня есть внешний поток, который с высокой скоростью передает данные в компонент визуализации, то, если я перестаю отправлять данные, я получаю обновления еще на 20 секунд, прежде чемкомпонент перестает рисовать.Я хочу прекратить рисование, как только данные поступят.

Еще одна идея, которая у меня возникла, заключалась в обработке CompositionTarget.Rendering в компоненте визуализации, а затем в реализации некоторой элементарной очереди для передачи данных, и событие Rendering потребляет это.данные так быстро, как это возможно.

Вкратце

С учетом компонента визуализации WPF, V, и источника данных, который передает данные каждые 1 мс, D, как я могу гарантировать, что независимо от данныхD, V рисует данные со скоростью 30 кадров в секунду (или что-то еще) и обновляется в виде фрагментов, что делает цикл рендеринга игры в DirectX?

Когда данные останавливаются, V должен перерисовать все, что у него было до сих пор, за один раз.Когда данные слишком быстрые, V рисует большие куски за раз, чтобы компенсировать это.

Если вам нужна дополнительная информация, я буду рад поделиться ею.Сейчас я только что опубликовал краткий обзор, чтобы оценить, есть ли какие-либо быстрые исправления, но более полный Q с примерами кода может быть предоставлен по запросу.

С уважением,

Ответы [ 5 ]

3 голосов
/ 06 января 2012

Возможно, вы захотите рассмотреть рендеринг в событии CompositionTarget.Rendering и регулирование недействительного состояния.

Пример игрового цикла Silverlight (F #):

/// Run game
let runGame () =
    let state = gameState.GetEnumerator()
    let rate = TimeSpan.FromSeconds(1.0/50.0)
    let lastUpdate = ref DateTime.Now
    let residual = ref (TimeSpan())
    CompositionTarget.Rendering.Add (fun x -> 
        let now = DateTime.Now
        residual := !residual + (now - !lastUpdate)
        while !residual > rate do
            state.MoveNext() |> ignore
            residual := !residual - rate
        lastUpdate := now
    )

Играть в игру: http://trelford.com/blog/post/LightCycles.aspx

Читайте источник: https://bitbucket.org/ptrelford/lightcycles

1 голос
/ 08 июня 2017

Вы можете прослушать событие CompositionTarget.Rendering, которое запускается непосредственно перед тем, как WPF отрисовывает пользовательский интерфейс, и выполнять там свой отрисовку.

Еще один лакомый кусочек .. InvalidateVisuals () не имеет ничего общего с Form.Invalidate (), так как это также вызывает перепланировку, которая стоит дорого.Если вы хотите что-то вроде Form.Invalidate (), затем создайте DrawingGroup (или растровое изображение) «backingStore», поместите его в DrawingContext во время OnRender (), а затем обновите его, когда захотите.WPF автоматически обновит и перекрасит интерфейс.

1 голос
/ 06 января 2012

Я недавно работал с проектом, который требовал игровой цикл, подобный стилю.Хотя мой пример основан исключительно на F #, вы можете понять, как это можно сделать и в C #, возможно, используйте некоторый код взаимодействия для инициализации таймера и подключения событий, как показано в этой ссылке ниже,

http://dl.dropbox.com/u/23500975/Demos/loopstate.zip

Образец не показывает, как перерисовать, он просто обновляет базовые данные запаса каждые 500 мсек. Он должен в значительной степени работать для любого вида механизмов рисования с WPF.Основная идея состоит в том, чтобы использовать компонуемые события, в F # событие - это гражданин первого класса + IObservable (реактивные расширения для C #), поэтому мы можем легко составлять функции, которые в свою очередь возвращают набор событий или одно событие.Существует функция Observable.await, которая принимает Observable, а также имеет состояние для возврата.

            eventSource
            |> Observable.await(fun (t:State.t) e ->
                // return the modified state back on every check or in the end
                match e with
                // start button click
                | Choice1Of3(_) ->
                    {t with start=true}

                // stop button click
                | Choice2Of3(_) ->
                    {t with start=false}

                // timer tick event,
                | Choice3Of3(_) ->
                    if t.start = true then
                        handleStockUpdate(t)
                    else t
            ) (state)

Я только что использовал некоторые термины FP здесь, но он должен прекрасно работать при обычном C # (OO) способе работы здесь.

Надеюсь, это поможет!

-Fahad

1 голос
/ 06 января 2012

Задумывались ли вы об использовании таймера диспетчеризации, работающего со скоростью 30 кадров в секунду, а затем сделать снимок текущих данных и отобразить их при каждом такте таймера?Если вы хотите избежать перерисовки, если ничего не изменилось, вы можете просто сохранить метки времени для LastChanged и LastRendered, выполняя только фактическую перерисовку, если LastChanged> LastRendered.В основном обновление данных и рендеринг данных отделены друг от друга;основной трюк заключается в том, чтобы вы могли каким-то образом получить согласованный снимок данных, когда поток рендеринга хочет их визуализировать (т. е. вам понадобится какая-то блокировка).

0 голосов
/ 06 января 2012

Я не уверен, зачем вам использовать WPF для внешнего интерфейса, если вы рисуете с использованием элементов не-WPF и вам требуется метод Invalidate (), предоставленный WinForms?Разве вы не можете просто переключить интерфейс для использования WinForms?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...