Мне нравится думать о производительности в терминах « ограничений ». Это удобный способ концептуализации довольно сложной, взаимосвязанной системы. Когда у вас есть проблемы с производительностью, вы задаете вопрос: «Какие ограничения я бью?» (Или: «Связан ли я с 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
(текстуры, вершинные и индексные буферы и т. Д.), А также Effect
s - остается в памяти GPU. Он не постоянно пересылается в GPU. Команда рисования, которая ссылается на эти данные, просто отправляется с указателем на эти данные.
(Также: вы можете отправлять команды рисования только из основного потока, но вы можете это делать SetData
в любом потоке.)
XNA усложняет вещи несколько с его делают государственные классы ( BlendState
, DepthStencilState
и т.д.). Это состояние данные будут отправлен за вызов отрисовки (в каждой партии). Я не уверен на 100%, но у меня сложилось впечатление, что оно отправляется лениво (оно только посылает состояние, которое меняется). В любом случае, изменения состояния обходятся дешево, по сравнению со стоимостью партии.
Наконец, последнее, что следует упомянуть, это внутренний конвейер графического процессора . Вы не хотите принудительно сбрасывать его, записывая данные, которые ему все еще нужно прочитать, или читая данные, которые ему все еще нужно записать. Сброс конвейера означает, что он ожидает завершения операций, поэтому при обращении к данным все находится в согласованном состоянии.
Два конкретных случая, на которые стоит обратить внимание: это вызов GetData
чего-то динамического - особенно того, на RenderTarget2D
который может записывать графический процессор. Это очень плохо для производительности - не делай этого.
Другой случай - вызов SetData
буферов вершин / индексов. Если вам нужно делать это часто, используйте DynamicVertexBuffer
(также DynamicIndexBuffer
). Это позволяет графическому процессору знать, что они будут часто меняться, и выполнять некоторую магию буферизации внутри, чтобы избежать сброса конвейера.
(Также обратите внимание, что динамические буферы работают быстрее, чем DrawUser*
методы - но они должны быть предварительно выделены с максимальным требуемым размером.)
... и это почти все, что я знаю о производительности XNA :)