Информация о рендеринге, партиях, графической карте, производительности и т. Д. + XNA?

12

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

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

Так что я на самом деле хочу знать, что происходит, когда и где (CPU / GPU), когда вы выполняете конкретные действия при рисовании? Что такое партия? Какое влияние оказывают эффекты, прогнозы и т.д.? Сохраняются ли данные на видеокарте или они передаются на каждом этапе? Когда говорят о пропускной способности, вы говорите о внутренней пропускной способности видеокарты или о конвейере от процессора к графическому процессору?
Примечание: я на самом деле не ищу информацию о том, как происходит процесс рисования, это дело GPU, меня интересуют все накладные расходы, которые предшествуют этому.

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

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

Aidiakapi
источник
2
Хотя изначально это была XNA, я добавил тег DirectX, так как это базовая технология - она ​​может помочь вам получить лучшие ответы. Также проверьте этот ответ, который может дать вам хорошую отправную точку.
Эндрю Рассел
@AndrewRussell Большое спасибо :). На самом деле я уже читал различные статьи на эту тему, в том числе и эту. Но это не охватило всего, что я хотел бы знать.
Aidiakapi

Ответы:

20

Мне нравится думать о производительности в терминах « ограничений ». Это удобный способ концептуализации довольно сложной, взаимосвязанной системы. Когда у вас есть проблемы с производительностью, вы задаете вопрос: «Какие ограничения я бью?» (Или: «Связан ли я с CPU / GPU?»)

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

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

(В то время как я делаю широкий обзор производительности в отношении XNA, я укажу, что выделение ссылочного типа ( classне struct), хотя обычно и дешевое, может вызвать сборщик мусора, который сожжет много циклов - особенно на Xbox 360 . Смотрите здесь для более подробной информации).

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

Проще говоря, вот некоторые из самых больших из них: « предел заполнения » (сколько пикселей вы можете записать в буферный буфер - часто, сколько оверрейда вы можете иметь), « предел шейдеров » (насколько сложными могут быть ваши шейдеры и сколько данных вы можете протолкнуть через них), " texture-fetch / texture-band limit " (сколько данных текстуры вы можете получить).

И теперь мы подошли к большому вопросу - о чем вы действительно спрашиваете - где процессор и графический процессор должны взаимодействовать (через различные API и драйверы). Слабовато существует « пакетный лимит » и « пропускная способность ». (Обратите внимание , что часть одна из серии я упоминал ранее переходит в обширные подробности.)

Но, по сути, пакет ( как вы уже знаете ) происходит всякий раз, когда вы вызываете одну из GraphicsDevice.Draw*функций (или часть XNA, например SpriteBatch, делает это для вас). Как вы, без сомнения, уже прочитали, вы получаете несколько тысяч * из них за кадр. Это ограничение ЦП, поэтому оно конкурирует с другим использованием ЦП. По сути, это драйвер, упаковывающий все, что вы сказали, чтобы нарисовать, и отправляющий его в графический процессор.

И затем есть пропускная способность для графического процессора. Это сколько необработанных данных вы можете перенести туда. Это включает в себя всю информацию о состоянии, которая идет с пакетами - все от установки состояния рендеринга и констант / параметров шейдера (включая такие вещи, как матрицы мира / представления / проекта) до вершин при использовании DrawUser*функций. Она также включает в себя любые вызовы SetDataи GetDataна текстур, буферов вершин и т.д.

На данный момент я должен сказать, что все, что вы можете вызвать SetData(текстуры, вершинные и индексные буферы и т. Д.), А также Effects - остается в памяти GPU. Он не постоянно пересылается в GPU. Команда рисования, которая ссылается на эти данные, просто отправляется с указателем на эти данные.

(Также: вы можете отправлять команды рисования только из основного потока, но вы можете это делать SetDataв любом потоке.)

XNA усложняет вещи несколько с его делают государственные классы ( BlendState, DepthStencilStateи т.д.). Это состояние данные будут отправлен за вызов отрисовки (в каждой партии). Я не уверен на 100%, но у меня сложилось впечатление, что оно отправляется лениво (оно только посылает состояние, которое меняется). В любом случае, изменения состояния обходятся дешево, по сравнению со стоимостью партии.

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

Два конкретных случая, на которые стоит обратить внимание: это вызов GetDataчего-то динамического - особенно того, на RenderTarget2Dкоторый может записывать графический процессор. Это очень плохо для производительности - не делай этого.

Другой случай - вызов SetDataбуферов вершин / индексов. Если вам нужно делать это часто, используйте DynamicVertexBuffer(также DynamicIndexBuffer). Это позволяет графическому процессору знать, что они будут часто меняться, и выполнять некоторую магию буферизации внутри, чтобы избежать сброса конвейера.

(Также обратите внимание, что динамические буферы работают быстрее, чем DrawUser*методы - но они должны быть предварительно выделены с максимальным требуемым размером.)

... и это почти все, что я знаю о производительности XNA :)

Эндрю Рассел
источник
Большое спасибо! Это именно то , что я искал и надеялся на :).
Aidiakapi
1
Несколько сотен партий в кадре звучит слишком пессимистично. Эмпирическое правило, которое я всегда слышал, - от 2K до 3K пакетов на кадр. Известно, что некоторые игры получают на ПК до 10K, но я думаю, что для этого требуется большая осторожность.
Натан Рид
Совершенно верно. Цифра «несколько сотен» взята из бумаги «Пакетная партия», в которой указано «25 Кбит / с при 100% 1 ГГц процессора». Но этой бумаге уже десять лет, и с тех пор драйверы и процессоры значительно улучшились. Я обновлю это (и мои другие), чтобы прочитать «несколько тысяч».
Эндрю Рассел