C# / XNA - Загрузка возражает против памяти - как это работает?

Я запускаю с C# и XNA. В методе "Обновления" "Игрового" класса у меня есть этот код:

t = Texture2D.FromFile( [...] ); //t is a 'Texture2D t;'

который загружает маленькое изображение. Метод "Обновления" работает как цикл, таким образом, этот код называют много раз за секунду. Теперь, когда я выполняю свою игру, требуется 95 МБ RAM, и это медленно переходит приблизительно к 130 МБ (из-за кода, который я отправил без этого кода, которым это остается на уровне 95 МБ), затем сразу переходит приблизительно к 100 МБ (garbare colletion?) и снова медленно переходит к 130 МБ, затем сразу к 100 МБ и так далее. Так мой первый вопрос:

  1. Можно ли объяснить почему (как) это работает как этот?

Я нашел, это, если я изменяю код на:

t.Dispose()
t = Texture2D.FromFile( [...] );

это работает как этот: сначала это берет 95 МБ и затем медленно переходит приблизительно к 101 МБ (из-за кода) и остается на этом уровне.

  1. Я не понимаю, почему этому требуются 6 МБ (101-95)...?

  2. Я хочу сделать это работами как этот: загрузите изображение, выпуск из памяти, изображение загрузки, выпуск из памяти и так далее, таким образом, программа должна всегда, берет 95 МБ (требуется 95 МБ, когда изображение загружается только однажды в предыдущем методе). Что такое инструкции, я должен использовать?

Если это важно, размер изображения составляет приблизительно 10 КБ.

Спасибо!

7
задан Femaref 16 June 2010 в 01:55
поделиться

3 ответа

Прежде всего вам нужно понять, что то, что вы делаете, довольно странно!

«Обычным» способом загрузки текстуры является использование конвейера содержимого и диспетчера содержимого.

Если вам действительно нужно загрузить текстуру из файла, а не из системы содержимого, вы должны сделать это только один раз для каждой текстуры. Вы должны использовать Texture2D.FromFile в Game.LoadContent и вызвать Dispose в Game.UnloadContent (обычно вызовы выполняются диспетчером содержимого Dispose для вас - но поскольку вы не проходите через диспетчер содержимого, вам нужно вызвать Dispose самостоятельно).


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

Память, используемая управляемыми объектами, будет обрабатываться сборщиком мусора - в этом случае каждый экземпляр Texture2D использует крошечный бит управляемой памяти. В 99% случаев вам не нужно беспокоиться об этом - сборщик мусора действительно хорош в работе с управляемой памятью - это его работа!

Что вам , нужно беспокоить, так это неуправляемые ресурсы - в данном случае память текстуры используется всеми этими текстурами, которые вы загружаете. В конце концов сборщик мусора запустится, увидит все эти объекты Texture2D, на которые нет ссылок, и соберет их. Когда он их соберет, он завершит их, что, как и вызов Dispose, освободит неуправляемые ресурсы.

Но сборщик мусора не может «видеть» все неуправляемые ресурсы, которые используют эти объекты Texture2D.Он не знает, когда нужно освободить эти объекты - вы можете сделать гораздо лучше сами. Все, что вам нужно сделать, это вызвать Dispose для ваших текстур, когда они больше не нужны.

Это связано с тем, что сборщик мусора не знает о 30 МБ дополнительной памяти, используемой вашими текстурами, которая накапливается, когда вы не вызываете Dispose. Вдобавок к этому, когда вы смотрите на использование памяти процессом, вы не видите всю память графического процессора, которую используют эти текстуры!

(Базовая реализация Texture2D заключается в том, что она содержит ссылку на объект текстуры DirectX, который сам по себе является неуправляемым объектом, использующим неуправляемую основную память, который, в свою очередь, обрабатывает текстуру и связанную с ней память на графическом процессоре. Управляемая часть объект Texture2D занимает всего около 100-200 байт - в нем хранится вышеупомянутая ссылка и кэш с информацией о ширине, высоте, формате текстуры и т. д.)


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

Ну, сначала в общем, вызов Dispose для неиспользуемой текстуры каждого кадра является допустимым методом. Вы будете зависеть от сборщика мусора, чтобы очистить весь мусор, который он создает (управляемая сторона объектов Texture2D) - это нормально для Windows, но может снизить вашу производительность на Xbox. По крайней мере, у вас не будет утечки неуправляемых ресурсов.

Лучший способ, особенно если текстура каждый раз имеет один и тот же размер, - это просто продолжать использовать один и тот же объект текстуры. Затем вызовите на нем Texture2D.SetData с новыми данными текстуры в каждом кадре.

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

Конечно, LoadFile принимает реальный файл, SetData - необработанные данные. Вам придется реализовать преобразование данных самостоятельно. Хорошей отправной точкой может быть эта ветка форума XNA .

13
ответ дан 6 December 2019 в 12:46
поделиться

Метод Update не следует использовать для загрузки текстур, потому что он загружается очень часто. В .net объекты собираются в мусор, что означает, что вы не можете явно освободить объект (даже .Dispose этого не делает).

Если система находится под большим давлением, GC может работать не так часто, как мог бы.

Короче говоря: вы "сливаете" тысячи Texture2Ds.

Правильный способ загрузить их - в одном из событий инициализации и хранить их в переменной класса, словаре, списке или любой другой структуре. По сути, загрузите их только один раз, а затем используйте повторно.

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

Редактирование: Ваш подход "Загрузить изображение, освободить, загрузить, освободить" не будет работать в .net, просто потому что вы не можете явно освободить память. Вы можете вызвать GC.Collect, но это а) не освобождает память и б) GC.Collect невероятно медленный.

Есть ли конкретная причина, по которой вам нужно перезагружать изображение 60 раз в секунду? Если вам нужно перезагружать его только каждую секунду или около того, тогда вы можете использовать elapsedGameTime для измерения времени, прошедшего с момента последнего .Update, и если оно больше вашего порога, перезагрузить изображение. (Вам нужна переменная класса, например "LastUpdated", с которой вы сравниваете и которую вы обновляете при перезагрузке изображения)

.
3
ответ дан 6 December 2019 в 12:46
поделиться

Как говорит Эндрю Рассел, загрузка изображения 60 раз в секунду - это не то, что вы хотите сделать. Однако я бы добавил: Texture2D.FromFile вообще не используйте!

XNA предоставляет вам надежный конвейер контента - используйте его!

  1. Добавьте ваше изображение в ваш игровой проект. По умолчанию XNA знает, что делать с изображениями PNG, GIF и JP(E)G. Когда вы компилируете свой проект, XNA также обработает ваше изображение в формат файла XNA Binary (*.XNB).
  2. В MyGame.LoadContent загрузите ваше изображение, используя myTexture = Content.Load(@"My/Image/Folder/MyImageAssetName")

Content.Load загрузит скомпилированную XNB версию вашего изображения. В свойствах изображения в проекте можно также задать цвет маскировки (например, все белые пиксели в JPG будут прозрачными в игре), масштабировать изображение и изменить имя актива. (Имя актива по умолчанию равно имени файла изображения, но вы можете изменить его в свойствах, и имя актива - это то, что вы будете использовать для Content.Load.)

Content.Load также кэширует загружаемые объекты, так что если по какой-то причине вам придется вызывать Load для одного и того же актива несколько раз, вы не увеличите объем используемой памяти.

Конвейер содержимого XNA можно использовать не только для изображений, но и для многих других вещей, включая 3D-модели, звук и даже ваши собственные классы.

0
ответ дан 6 December 2019 в 12:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: