Правильный способ вызова glDeleteTextures в финализаторе объекта .net

Я собираюсь реализовать управляемый класс-оболочку вокруг текстуры OpenGL и хочу, чтобы финализатор объекта вызывал glDeleteTextures.

Итак, поток, вызывающий финализатор (поток GC?), Должен быть привязан к контексту рендеринга OpenGL, которому принадлежит текстура, путем вызова wglMakeCurrent.

Но в wglMakeCurrent документации четко указано, что контекст визуализации OpenGL не может быть текущим контекстом визуализации нескольких потоков одновременно.

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

Как правильно вызвать glDeleteTextures в финализаторе объекта .net?

Изменить

Этот класс-оболочка будет использоваться в сложной системе «загрузки по требованию» графа сцены со стратегией кэширования, реализованной WeakReference и т.п. Итак, «удаление вручную» - это не вариант, который я хочу рассматривать: я действительно хочу, чтобы сборщик мусора справился с этим.


person Nicolas Repiquet    schedule 23.04.2012    source источник
comment
Не отвечая снова, еще два варианта: 1. Используйте пользовательский интерфейс SynchronizationContext, чтобы запланировать удаление в потоке OpenGL. 2. Создайте вторичный контекст с wglShareLists, единственная цель которого - освободить дескрипторы OpenGL. Не думаю, что есть решение, которое не заставит вас съежиться ...   -  person Stefan Hanke    schedule 29.04.2012


Ответы (3)


Нет.

Используйте интерфейс IDisposable для освобождения детерминированный. Освобождая объект текстуры, вызовите метод dispose. Внутри удалите текстуру.

Это справедливо для всех объектов, которые выделяют неуправляемые ресурсы; в этом случае обрабатывает OpenGL.


Изменить: шаблон Dispose здесь не применяется. Ресурс должен быть завершен в потоке его создания, и стандартный способ сделать это не обрабатывает этот случай.

person Stefan Hanke    schedule 23.04.2012
comment
Я не знаю, когда я смогу избавиться от текстур. Я хочу, чтобы сборщик мусора справился с этим за меня :) - person Nicolas Repiquet; 23.04.2012
comment
Вы не можете позволить GC обрабатывать объекты OpenGL (потоки, fbo, vbo, ...). Вы должны вручную вызвать удаление объектов OpenGL в потоке, в котором вы их создали. Простой способ сделать это - использовать конструкцию на объектах IDisposable. - person Mārtiņš Možeiko; 23.04.2012
comment
По сути, это та же проблема с COM-объектами, которые необходимо завершить в потоке STA. Интересно, как .Net может запланировать финализатор в другом потоке ... - person Stefan Hanke; 23.04.2012
comment
... система отправляет сообщение в поток STA, предлагая COM-объекту завершить работу. - person Stefan Hanke; 23.04.2012
comment
Образованные догадки по этой статье. Однако не мог понять, как это будет работать. Вам нужно будет отправить сообщение в нужную ветку и должным образом отреагировать на него ... - person Stefan Hanke; 23.04.2012
comment
В моем приложении все равно будет какой-то насос / цикл сообщений. Поэтому вместо вызова glDeleteTextures в финализаторе я отправляю сообщение делегата, которое вызывает glDeleteTextures в очереди сообщений основного потока. Надо подумать, но это звучит многообещающе. - person Nicolas Repiquet; 23.04.2012
comment
Что ж, это способ работы COM. Немного более прямо: определите общедоступную статику, в которую финализаторы помещают дескрипторы, и позвольте потоку OpenGL удалить их в подходящее время. Не знаю, возникает ли проблема с блокировкой ... - person Stefan Hanke; 23.04.2012
comment
@Nicolas Я не знаю, когда я смогу избавиться от текстур. Мне любопытно: как вы не знаете, когда вы закончили использовать текстуры? Что еще более важно, было бы легче их очистить, если бы вы знали, когда закончите; большинству программ удается узнать, когда они что-то сделали. - person Nicol Bolas; 29.04.2012
comment
@NicolBolas Это очень похоже на управление памятью. Конечно, вы можете отслеживать все ссылки и вызывать удаление неиспользуемых объектов, но это обременительно и чревато ошибками. Сборщик мусора действительно облегчит жизнь вашему программисту и позволит вам сосредоточиться на менее тривиальных вещах, чем Подождите, эта текстура все еще где-то используется? - person Nicolas Repiquet; 30.04.2012
comment
@Nicolas: И насколько легче сборщик мусора делает вашу жизнь, когда вы пытаетесь управлять текстурами OpenGL с его помощью? Не так много. Почему? Потому что OpenGL не является C # API. Это C API, у которого есть ограничения; объекты имеют зависимости времени жизни от других объектов, на которые нет ссылок C #. Так что просто делайте то, что делают все, и просто управляйте своими текстурами. Не то чтобы вас просили отслеживать каждое выделение памяти; просто создайте объект, задача которого - хранить и удалять коллекции текстур. GC не подходит для всего; вы используете правильный инструмент для правильной работы. - person Nicol Bolas; 30.04.2012

Как сказал Стефан Ханке как альредт, вы этого не сделаете.

Когда вы сможете позвонить glDeleteTextures? Когда контекст OpenGL, который создал базовую текстуру (и) (или, точнее, он разделяет пространство имен объектов) является текущим в вызывающем потоке.

Финализатор (деструктор класса) запускает поток GC, но на самом деле я не знаю, указано ли в нем, как выполняется GC (это ответственность .NET JIT); Я думаю, что наиболее очевидная реализация - это отдельный поток. В самом деле, вызывать glDeleteTextures в финализаторе не рекомендуется, поскольку вы не знаете, является ли контекст OpenGL текущим в этом потоке.

Реализация IDisposable может быть идеей, но проблема остается, поскольку реализация Dispose должна знать, действительно ли контекст OpenGL актуален. Для этой цели вы можете использовать процедуры, зависящие от платформы, например wglGetCurrentContext.


Я столкнулся с той же проблемой и пришел к следующему решению.

Абстракция контекста (RenderContext), которая сопоставляет контекст OpenGL с потоком. Вот реализация MakeCurrent:

public void MakeCurrent(IDeviceContext deviceContext, bool flag)
{
    if (deviceContext == null)
        throw new ArgumentNullException("deviceContext");
    if (mDeviceContext == null)
        throw new ObjectDisposedException("no context associated with this RenderContext");

    int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

    if (flag) {
        // Make this context current on device
        if (Gl.MakeContextCurrent(deviceContext, mRenderContext) == false)
            throw new InvalidOperationException("context cannot be current because error " + Marshal.GetLastWin32Error());

        // Cache current device context
        mCurrentDeviceContext = deviceContext;
        // Set current context on this thread (only on success)
        lock (sRenderThreadsLock) {
            sRenderThreads[threadId] = this;
        }
    } else {
        // Make this context uncurrent on device
        bool res = Gl.MakeContextCurrent(deviceContext, mRenderContext);

        // Reset current context on this thread (even on error)
        lock (sRenderThreadsLock) {
            sRenderThreads[threadId] = null;
        }

        if (res == false)
            throw new InvalidOperationException("context cannot be uncurrent because error " + Marshal.GetLastWin32Error());
    }
}

public static RenderContext GetCurrentContext()
{
    int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

    lock (sRenderThreadsLock) {
        RenderContext currentThreadContext;

        if (sRenderThreads.TryGetValue(threadId, out currentThreadContext) == false)
            return (null);

        return (currentThreadContext);
    }
}

private static readonly object sRenderThreadsLock = new object();

private static readonly Dictionary<int, RenderContext> sRenderThreads = new Dictionary<int,RenderContext>();

Если (и только если) контекстная валюта выполняется с использованием метода MakeCurrent, класс RenderContext может знать, является ли она текущей для вызывающего потока. В заключение, реализация Dispose может использовать класс RenderContext для реального удаления объектов OpenGL.

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

По сути, каждый ресурс имеет пространство имен объектов (список совместного использования контекста OpenGL; я определил с помощью GUID). Используя пространство имен объектов, экземпляр ресурса может получить соответствующий GraphicGarbageCollector, поставить в очередь имя ресурса (например, идентификатор текстуры); когда более уместно, GraphicGarbageCollector освобождает поставленные в очередь ресурсы, используя базовый контекст.

Тот же механизм можно использовать с системой ссылок: когда счетчик ссылок достигает 0, она удаляет ресурс, собирая его для сборки мусора. Это последовательная реализация: вы можете найти мою здесь.

person Luca    schedule 02.05.2012
comment
Слишком длинный ответ :( ... все можно резюмировать, собрав вручную ваши ресурсы OpenGL. - person Luca; 03.05.2012

Хорошо, вот что я сделал.

Я реализовал интерфейс IDisposable в моем объекте ресурса (базовый класс для текстуры, массива вершин, шейдеров и т. Д.).

Когда удаляется (или завершается, если не удаляется вручную), ресурс добавляет свой идентификатор и тип в синхронизированный список, принадлежащий его классу-оболочке контекста OpenGL, и в конечном итоге пробуждает основной поток, устанавливая событие.

Я изменил насос сообщений своего приложения (теперь я использую MsgWaitForMultipleObjects вместо GetMessage), и в цикле после получения контекста OpenGL поток сначала «освобождает» неиспользуемые ресурсы перед обработкой сообщения (если есть).

Пока это работает как шарм.

Спасибо Стефану Ханке за подсказки по этому поводу!

person Nicolas Repiquet    schedule 03.05.2012