Как предотвратить удаление GraphicsDevice при применении новых настроек?

В моем игровом окне разрешено изменение размера вручную, что означает, что его можно изменять, как и любого другого обычного окна, перетаскивая его края. В игре также используется RenderTarget2D rt2d, для которого устанавливается основная цель рендеринга в основном методе Draw: GraphicsDevice.SetRenderTarget(rt2d), но он сбрасывается обратно на null (цель рендеринга по умолчанию) в конце основного метода Draw, что делает его немного сбивает с толку: действительно ли это источник проблемы, изменение размера окна игры прямо между моментом, когда Render Target устанавливается на rt2d, а не сбрасывается до значений по умолчанию? Прямо сейчас похоже на это.

Код в основном методе Draw должен всегда сбрасывать основную цель Render Target обратно на null, поэтому нет ожидаемых случаев, когда этого обычно не происходило бы.

Тем не менее, результат изменения размера окна игры иногда вызывает GraphicsDevice.isDisposed return true, а затем игра выдает System.ObjectDisposedException на первом SpriteBatch.End(). Я нашел сообщения об этой ошибке, относящиеся к первым дням использования XNA, но без хорошего объяснения (а также без упоминания об изменении Render Target, так что это могло быть источником проблемы и для этих плакатов).

Прямо сейчас я могу вызвать эту ошибку, вызвав этот метод несколько раз:

graphics.PreferredBackBufferWidth = graphics.PreferredBackBufferWidth;
graphics.PreferredBackBufferHeight = graphics.PreferredBackBufferHeight;
graphics.ApplyChanges();

… С помощью следующих строк в основном методе Draw:

RenderTarget2D rt2d = new RenderTarget2D(GraphicsDevice,
                                         graphics.PreferredBackBufferWidth,
                                         graphics.PreferredBackBufferHeight);
GraphicsDevice.SetRenderTarget(rt2d);
sb.Begin();
// main draw method here, it's pretty big, so it might be taking long
//  enough to process to actually resize before resetting render target
sb.End();

GraphicsDevice.SetRenderTarget(null);
sb.Begin();
// draw the whole rt2d to the screen
sb.End();

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

UPD: есть события Window.ClientSizeChanged и graphics.PreparingDeviceSettings, но даже когда они срабатывают, установка цели рендеринга по умолчанию не помогает.

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

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

UPD2: я просто попытался переключить полноэкранный режим на ожидающую операцию, установив для F11 isFullscreenTogglePending значение true и проверив его в начале основного Update метод, и это совсем не помогло. Затем я понял, что ранее полноэкранный режим также переключается из основного метода обновления, но не в самом начале, а в середине метода обновления ввода, поэтому на самом деле не имеет значения, где в основном методе Update он запускается, это все еще вызывает эту ошибку. Интересно, что GraphicsDevice.isDisposed имеет значение false при возникновении исключения.


Это сообщение об исключении:

System.ObjectDisposedException occurred
  Message=Cannot access a disposed object.
Object name: 'GraphicsDevice'.
  Source=Microsoft.Xna.Framework
  ObjectName=GraphicsDevice
  StackTrace:
       at Microsoft.Xna.Framework.Helpers.CheckDisposed(Object obj, IntPtr pComPtr)
       at Microsoft.Xna.Framework.Graphics.BlendState.Apply(GraphicsDevice device)
       at Microsoft.Xna.Framework.Graphics.GraphicsDevice.set_BlendState(BlendState value)
       at Microsoft.Xna.Framework.Graphics.SpriteBatch.SetRenderState()
       at Microsoft.Xna.Framework.Graphics.SpriteBatch.End()
       at secret_project.Game1.Draw(GameTime gameTime) in P:\msvs projects\secret_project\Game1.cs:line 3310
  InnerException: 

Он находится в spriteBatch.End() в основном вызове Draw.

Как мне предотвратить эту ошибку?


Возможно связанные вопросы:


person user1306322    schedule 25.04.2013    source источник
comment
Насколько маленьким вы его делаете?   -  person ClassicThunder    schedule 26.04.2013
comment
800 × 480 и 1920 × 1080, вперед и назад. Я знаю, что есть эта проблема, когда установка предпочтительной высоты на ноль приводит к удалению, я думаю, что что-то подобное может произойти при каком-то применении нового разрешения. Может это какая-то латентность с таймаутом проверки размера, и надо ставить больший интервал?   -  person user1306322    schedule 26.04.2013
comment
Можете ли вы опубликовать образец, который постоянно воспроизводит ошибку?   -  person borrillis    schedule 02.05.2013
comment
@borrillis Я обновил точный метод, который позволяет это сделать.   -  person user1306322    schedule 02.05.2013
comment
Используя проект XNA по умолчанию и приведенный выше код (при нажатии клавиши), мне не удалось воспроизвести это исключение. Что мне еще нужно сделать? Было бы неплохо получить минимальный образец, который воспроизводит это поведение.   -  person Ani    schedule 02.05.2013
comment
@ user1306322 Где именно вы размещаете эти строки кода? Как часто им звонят?   -  person borrillis    schedule 02.05.2013
comment
Обновлено с дополнительной информацией и кодом.   -  person user1306322    schedule 02.05.2013
comment
Я не могу воспроизвести это с помощью добавленного вами кода. Я поместил первые три строки в Update(), вызывая их каждый кадр, когда кнопка мыши удерживается нажатой, сделал размер окна изменяемым, а остальные поместил в Draw(). Что бы я ни делал, он не вылетает. Вы уверены, что утилизировано GraphicsDevice? Ошибки, возникающие внутри Begin () и End (), могут не проявляться до End (), возможно, что-то, что вы пытаетесь нарисовать, было удалено. Кратко, xboxforums.create.msdn.com/forums/p /86840/536058.aspx кажется актуальным.   -  person Elle    schedule 14.05.2013
comment
@JordanTrudgett Я не уверен, я сужу по полученным исключениям. Есть ли способ избавиться от чего-либо, применив настройки графики, когда для цели рендеринга установлено значение, отличное от значения по умолчанию?   -  person user1306322    schedule 14.05.2013
comment
@ user1306322 Не знаю. Я думаю, что проблема скрыта где-то еще и всплывает только постфактум, поэтому для решения этой проблемы будет полезен минимальный образец, вызывающий ошибки. Сообщение об ошибке: ObjectDisposedException: невозможно получить доступ к удаленному объекту. Имя объекта: 'GraphicsDevice'?   -  person Elle    schedule 14.05.2013
comment
@JordanTrudgett добавил сообщение об исключении.   -  person user1306322    schedule 14.05.2013
comment
Хм ... Я не думаю, что вам следует создавать новый RT каждый кадр. Скорее, вы должны воссоздать его после изменения размера вашего окна. Хотя я понятия не имею, имеет ли это какое-либо отношение к вашей аварии. Также; Я думаю, что есть ссылка на GraphicsDevice в каком-то используемом вами ресурсе, который может не обновляться с помощью нового GraphicsDevice (cus он удаляется / воссоздается при изменении размера?).   -  person Stig-Rune Skansgård    schedule 15.05.2013
comment
Необновленная ссылка на @ Stig-RuneSkansgård? Вы уверены, что это вообще возможно? Не могли бы вы описать, как это может происходить?   -  person user1306322    schedule 15.05.2013
comment
@ user1306322, вы используете несколько устройств отображения или что-то другое с точки зрения графики, что может вызвать проблему (поскольку никто другой не может ее воспроизвести?) Это может быть недокументированная ошибка или что-то в этом роде. Можете ли вы запустить код в другой системе или компьютере и увидеть, что ошибка все еще возникает?   -  person Elle    schedule 16.05.2013
comment
@ user1306322 Я не уверен, может ли это случиться с GraphicsDevice, но допустим, у вас есть класс, на который есть ссылка. Затем в другом контексте вы удаляете GraphicsDevice, что делает эту ссылку IsDisposed == истинной. В том же контексте Game.GraphicsDevice снова обновляется. Тогда у вас на практике есть два графических устройства; Один утилизирован, а другой не утилизирован. Но теперь я почти уверен, что в XNA этого обычно не происходит ...   -  person Stig-Rune Skansgård    schedule 16.05.2013
comment
@ Stig-RuneSkansgård Я попробую достать другой настольный компьютер, способный запускать приложения XNA профиля Hidef, чтобы проверить это. Я никогда намеренно не утилизирую свое графическое устройство, это происходит внезапно во время применения настроек. Это может занять некоторое время и варьируется от первой попытки до сотни иногда.   -  person user1306322    schedule 16.05.2013


Ответы (3)


Две вещи: 1. Я не знаком с целевыми объектами рендеринга ... но, может быть, это поможет? Из MSDN:

«Цели рендеринга представляют собой линейную область памяти дисплея и обычно находятся в памяти дисплея видеокарты. Из-за этого объекты RenderTarget должны быть воссозданы при перезагрузке устройства».

2. Кроме того, однажды у меня была похожая проблема. Я избавлялся от текстуры в конце вызова отрисовки. Это сработает нормально, если я не попытаюсь переместить окно. Время от времени, когда я пытался переместить окно игры, возникало исключение ObjectDisposed (для текстуры). Мое лучшее предположение заключалось в том, что поток обновления и поток отрисовки будут смещены, хотя бы на короткое время, и текстура будет вызываться снова, прежде чем у нее будет возможность сбросить. Я никогда не находил способа остановить эффект, кроме как просто убедиться, что объект не был удален, прежде чем пытаться нарисовать.

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

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

person Colton    schedule 16.05.2013

Вы не должны создавать какие-либо графические ресурсы внутри вызова Draw, как вы это делали с RenderTarget2D. Во-первых, создание таких ресурсов происходит медленно и должно выполняться только один раз за GraphicsDevice. Внутри метода Draw должны быть только вызовы Set, поскольку установка уже созданного ресурса выполняется намного быстрее, поскольку они уже находятся в памяти графического устройства.

Что вам следует сделать - это переместить все создание графических ресурсов (включая RenderTarget2D) внутрь вызова LoadContent и оставить только методы Set внутри Draw. Метод LoadContent вызывается всякий раз, когда GraphicsDevice воссоздается (например, при изменении размера области просмотра). Таким образом, все ранее созданные ресурсы тоже будут воссозданы.

person OpenMinded    schedule 18.05.2013
comment
Я поместил создание RenderTarget2D в метод Draw, потому что игра позволяет изменять размер окна, что означает, что это приведет к ошибке, если размер целевого объекта рендеринга внезапно изменится, чем ожидалось, поэтому, чтобы этого избежать, он устанавливает каждый основной вызов Draw. Сейчас я проведу тест с LoadContent. - person user1306322; 18.05.2013
comment
Вы пробовали разместить RenderTarget2D внутри LoadContent? Изменение размера области просмотра должно запускать воссоздание GraphicsDevice, которое само должно запускать вызов метода LoadContent. - person OpenMinded; 18.05.2013
comment
Только что сделал, и он больше не вызывается после первого запуска игры. - person user1306322; 18.05.2013
comment
Действительно, в версии 2.0 он был изменен (отличное сообщение в блоге об этом). Где находится вызов graphicsDevice.ApplyChanges ()? Это внутри игрового цикла (Обновление) или вызывается асинхронно? - person OpenMinded; 18.05.2013
comment
Внутри основного метода обновления. Во всяком случае, это также происходит при изменении размера окна, поэтому я не думаю, что проблема в местоположении метода. - person user1306322; 18.05.2013

Я думаю, ваше исключение возникает из-за того, что вы воссоздаете графическое устройство, пока активен метод рисования. Изменять настройки устройства в методе обновления следует только после запуска игры. Установите для некоторой переменной, например bool, значение true, если разрешение нужно изменить, проверьте это значение в методе обновления и примените новое разрешение там.

public class Game1 : Microsoft.Xna.Framework.Game
{
    protected override void Update(GameTime gameTime)
    {
        if(resolutionChanged)
        {
            graphics.PreferredBackBufferHeight = userRequestedHeight;
            graphics.PreferredBackBufferWidth = userRequestedWidth;
            graphics.ApplyChanges();
        }

        // ...
    }

    // ...
}

Также я согласен с OpenMinded: никогда не создавайте ресурсы для каждого кадра. Используйте событие GraphicsDevicerManagers PreparingDeviceSettings. Он будет запущен при сбросе или воссоздании графического устройства.

person ThoWoi    schedule 19.05.2013
comment
Это полезно, но все же не решает проблему возникновения того же исключения при изменении размера окна игры вручную. - person user1306322; 19.05.2013
comment
Где вы создаете свой пакет спрайтов? Вам нужно воссоздать его при сбросе настроек устройства. Обычно XNA делает это за вас, если вы создаете пакет спрайтов в функции Game.LoadContent. Но вы упомянули OpenMinded, что он больше никогда не вызывается, что странно. - person ThoWoi; 19.05.2013
comment
Он больше никогда не вызывается, потому что я использую последнюю версию XNA. Поведение давно изменилось. Я создаю свои партии спрайтов в основном методе Initialize. Я больше не воссоздаю их. - person user1306322; 19.05.2013