Я часто схильний робити передчасну оптимізацію при роботі з графікою. Я завжди намагаюся дотримуватися кількох принципів:
- Зведіть до мінімуму кількість компонентів D3D. (Стани візуалізації, буфери, шейдери тощо)
- Зв'язуйте компоненти лише в разі крайньої необхідності. (Не пов'язано вже тощо)
- Максимально спеціалізуйте компоненти. (Встановити лише необхідні BindFlags тощо)
Це привело мене до створення дуже складних обгортків для управління створеними компонентами та поточним станом трубопроводу. Це не тільки забирає багато мого цінного часу на розробку, але й додає ще один великий рівень складності.
І найгірше: я навіть не знаю, чи все це варте клопоту.
Деякі мої міркування щодо оптимізації вже можуть бути впроваджені на нижчому рівні, і я просто тиражую їх, додатково витрачаючи час на процесор. Інші міркування можуть бути абсолютно непотрібними, оскільки вплив на продуктивність незначний.
Отже, мої запитання:
- Які з перерахованих вище вказівок є дійсними і в якій мірі слід їх виконувати?
- Як GPU обробляє зміни стану?
- Що станеться, якщо я зміню стан, який ніколи не використовується? (Під час активного дзвінка не робиться дзвінок.)
- Які фактичні штрафи за ефективність прив’язки різних різних компонентів?
- Які ще міркування щодо ефективності слід враховувати?
Будь ласка, не кажіть мені, що я просто не повинен дбати про продуктивність, поки я не досяг фактичних меж. Хоча це, очевидно, вірно з практичної точки зору, мене в основному цікавить теорія. Мені якось потрібно боротися з прагненням створити оптимальну графічну структуру, і я не думаю, що я можу це зробити за допомогою звичайної "лекції передчасної оптимізації".
Управління компонентами
Зараз я пишу програми DirectX 11 в C #, використовуючи SlimDX як керовану обгортку. Це дуже низький рівень обгортки, і моя поточна абстракція побудована поверх неї.
Є деякі очевидні переваги при використанні абстракції Direct3D. Налаштування середовища, завантаження шейдерів, налаштування констант і малювання сітки набагато простіше і використовують набагато менше коду. Крім того, оскільки він керує створенням та видаленням більшості компонентів, їх можна автоматично використовувати скрізь, і я майже повністю уникаю витоків пам'яті.
- Як ви зазвичай керуєте всіма графічними компонентами та ресурсами?
- Чи можете ви порадити будь-яким керованим фасовкам робити щось подібне до мого прикладу нижче?
Ось приклад моєї поточної реалізації. Я цілком задоволений інтерфейсом. Він має достатню гнучкість для моїх потреб і дуже простий у використанні та розумінні:
// Init D3D environment
var window = new RenderForm();
var d3d = new Direct3D(window, GraphicsSettings.Default);
var graphics = new GraphicsManager(d3d.Device);
// Load assets
var mesh = GeometryPackage.FromFile(d3d, "teapot.gp");
var texture = Texture.FromFile(d3d, "bricks.dds");
// Render states
graphics.SetViewports(new Viewport(0, 0, 800, 600);
graphics.SetRasterizer(wireFrame: false, culling: CullMode.Back);
graphics.SetDepthState(depthEnabled: true, depthWriteEnabled: true);
graphics.SetBlendState(BlendMethod.Transparency);
// Input layout
graphics.SetLayout("effect.fx", "VS", "vs_4_0",
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0)
);
// Vertex shader
graphics.SetShader(Shader.Vertex, "effect.fx", "VS", "vs_4_0");
graphics.SetConstants(Shader.Vertex, 0, 4, stream => stream.Write(wvpMatrix));
// Pixel shader
graphics.SetShader(Shader.Pixel, "effect.fx", "PS", "ps_4_0");
graphics.SetTexture(Shader.Pixel, 0, texture);
graphics.SetSampler(Shader.Pixel, 0, Sampler.AnisotropicWrap);
graphics.SetConstants(Shader.Pixel, 0, 1, stream => stream.Write(new Color4(1, 0, 1, 0);
d3d.Run(() =>
{
// Draw and present
d3d.BackBuffer.Clear(new Color4(1, 0, 0.5f, 1));
graphics.SetOutput(d3d.BackBuffer);
graphics.Draw(mesh);
d3d.Present();
}