C # / XNA - загрузка объектов в память - как это работает? - PullRequest
7 голосов
/ 16 июня 2010

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

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

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

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

Я обнаружил, что если я изменю код на:

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

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

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

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

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

Спасибо!

Ответы [ 3 ]

13 голосов
/ 16 июня 2010

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

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

Если вам действительно нужно загрузить текстуру из файла, а не из системы содержимого - вы должны сделать это только один раз для каждой текстуры. Вы должны использовать 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 .

3 голосов
/ 16 июня 2010

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

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

Короче говоря: вы «пропускаете» тысячи Texture2D.

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

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

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

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

0 голосов
/ 08 июля 2010

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

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

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

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

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

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

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