D3DImage и SharpDX мерцают на медленном оборудовании

Я использую проект SharpDX.WPF для возможностей WPF, это кажется простой для понимания библиотекой с низкими накладными расходами по сравнению с Toolkit, который поставляется с SharpDX (с той же проблемой!)

Во-первых: я исправил проект SharpDX.WPF для последней версии SharpDX, используя следующее: https://stackoverflow.com/a/19791534/442833

Затем я сделал следующую хакерскую корректировку в DXElement.cs, решение, которое также было сделано здесь:

private Query queryForCompletion;
    public void Render()
    {
        if (Renderer == null || IsInDesignMode)
            return;

        var test = Renderer as D3D11;
        if (queryForCompletion == null)
        {

            queryForCompletion = new Query(test.Device,
                new QueryDescription {Type = QueryType.Event, Flags = QueryFlags.None});
        }

        Renderer.Render(GetDrawEventArgs());

        Surface.Lock();
        test.Device.ImmediateContext.End(queryForCompletion);
        // wait until drawing completes
        Bool completed;
        var counter = 0;
        while (!(test.Device.ImmediateContext.GetData(queryForCompletion, out completed)
                 && completed))
        {
            Console.WriteLine("Yielding..." + ++counter);
            Thread.Yield();
        }
        //Surface.Invalidate();
        Surface.AddDirtyRect(new Int32Rect(0, 0, Surface.PixelWidth, Surface.PixelHeight));
        Surface.Unlock();
    }

Затем я визуализирую 8000 кубов в виде куба ...

Уступая ...

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

Изображения, демонстрирующие проблему:

Примечание: он случайным образом переключается между этими старыми изображениями. Я также использую действительно старое оборудование, которое делает мерцание более привлекательным (GeForce Quadro FX 1700).

Создал репозиторий, содержащий тот же исходный код, который я использую для решения этой проблемы: https://github.com/ManIkWeet/FlickeringIssue/


person ManIkWeet    schedule 08.12.2014    source источник
comment
Проблема заключается в синхронизации D3DImage с DirectX, проблема не только у вас. Посмотрите эту беседу с создателем SharpDX: twitter.com/xoofx/status/544974189430448128 Я ищу решение сам ... :(   -  person Dr. Andrew Burnett-Thompson    schedule 20.02.2015


Ответы (3)


Я думаю, вы неправильно запираете. Насколько я понимаю, Документацию MSDN, которую вы должны блокировать во время всего рендеринга, а не только в конце:

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

Информация, которую вы найдете в сети о D3DImage / SharpDX, несколько сбивает с толку, потому что ребятам из SharpDX не очень нравится способ реализации D3DImage (их нельзя винить), поэтому со стороны Microsoft есть заявления о том, что это «ошибка». когда на самом деле это просто неправильное использование API.

Да, у блокировки во время рендеринга есть проблемы с производительностью, но, вероятно, их невозможно исправить без переноса WPF на DirectX11 и реализации чего-то вроде SwapChainPanel, который доступен в приложениях UWP. (Сам WPF по-прежнему работает на DirectX9)

Если блокировка является для вас проблемой производительности, у меня была одна идея (но никогда не тестировавшаяся) заключается в том, что вы можете выполнить рендеринг на внеэкранную поверхность и уменьшить продолжительность блокировки, скопировав эту поверхность в D3DImage. Не знаю, поможет ли это с точки зрения производительности, но это то, что нужно попробовать.

person Zarat    schedule 18.09.2016
comment
С тех пор я перешел на DirectX11, он реализован намного лучше. Отмечу ваш ответ как решение, хотя я его не пробовал. - person ManIkWeet; 18.09.2016
comment
@Zarat Вау, я действительно не видел вашего ответа, пока не предоставил свой собственный stackoverflow.com/questions/13368940/, и похоже, что мы оба ходим вокруг одной и той же идеи ... - person Glenn Slayden; 19.09.2016

Что касается D3DImage блокировки, обратите внимание, что D3DImage.TryLock API имеет довольно нетрадиционную семантику, чего не ожидает большинство разработчиков:

Осторожно!
Вы должны вызвать Разблокировать даже в том случае, если TryLock указывает на сбой (т. е. возвращает false < / em>)

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

Следующий код является правильным отрисовкой WPF D3D без мерцания в моем приложении:

void WPF_D3D_render(IntPtr pSurface)
{
    if (TryLock(new Duration(default(TimeSpan))))
    {
        SetBackBuffer(D3DResourceType.IDirect3DSurface9, pSurface);
        AddDirtyRect(new Int32Rect(0, 0, PixelWidth, PixelHeight));
    }
    Unlock();    //  <--- !
}

Да, этот неинтуитивный код действительно правильный; это случай, когда D3DImage.TryLock(0) происходит утечка одной внутренней блокировки буфера D3D каждый раз, когда возвращается сообщение об ошибке. Не верьте мне на слово, вот код CLR из PresentationCore.dll v4.0.30319:

private bool LockImpl(Duration timeout)
{
    bool flag = false;

    if (_lockCount == uint.MaxValue)
        throw new InvalidOperationException();

    if (_lockCount == 0)
    {
        if (timeout == Duration.Forever)
            flag = _canWriteEvent.WaitOne();
        else
            flag = _canWriteEvent.WaitOne(timeout.TimeSpan, false);

        UnsubscribeFromCommittingBatch();
    }
    _lockCount++;
    return flag;
}

Обратите внимание, что внутреннее поле _lockCount увеличивается независимо от того, возвращает функция успех или сбой. Вы должны вызвать Unlock() самостоятельно, как показано в первом примере кода выше, если вы хотите избежать определенного тупика. Невыполнение этого требования также неприятно для отладки, потому что компонент (потенциально) не заблокируется до следующего прохода рендеринга, к которому соответствующие свидетельства уже давно исчезнут.

Необычное поведение, похоже, не упоминается в MSDN, но, честно говоря, в этой документации не упоминается, что вы также должны вызывать Unlock(), если вызов успешен.

введите здесь описание изображения

person Glenn Slayden    schedule 19.09.2016

Проблема не в механизме блокировки. Обычно вы используете Present для рисования, чтобы представить изображение. Present будет ждать, пока весь рисунок будет готов. С D3DImage вы не используете метод Present(). Вместо представления вы блокируете, добавляете DirtyRect и разблокируете D3DImage.

Рендеринг выполняется асинхронно, поэтому при разблокировке действия рисования могут быть не готовы. Это вызывает эффект мерцания. Иногда вы видите наполовину нарисованные предметы. Плохое решение (с которым я тестировал) добавляет небольшую задержку перед разблокировкой. Это немного помогло, но это было не изящное решение. Это было ужасно!

Решение:

Я продолжил кое-что еще; Я заканчивал использование MSAA (сглаживание), и первая проблема, с которой я столкнулся, была; MSAA не может выполняться для общей текстуры dx11 / dx9, поэтому я решил выполнить рендеринг в новую текстуру (dx11) и создать копию общей текстуры dx9. Я ударился головой о табель, потому что теперь он был сглажен и без щелчка !! Не забудьте вызвать Flush() перед добавлением грязного прямоугольника.

Итак, создание копии текстуры: DXDevice11.Device.ImmediateContext.ResolveSubresource(_dx11RenderTexture, 0, _dx11BackpageTexture, 0, ColorFormat); (_dx11BackpageTexture это общая текстура) будет ждать, пока рендеринг будет готов, и создаст копию.

Вот как я избавился от мерцания ....

person Jeroen van Langen    schedule 11.11.2016
comment
А у вас есть рабочий пример этого метода? - person Jake; 01.03.2019