Почему порядок имеет значение в шейдерах? - PullRequest
0 голосов
/ 16 ноября 2018

A Краткое примечание

Этот вопрос имеет тег C++, поскольку с DirectX в C++ работает больше разработчиков, чем в C#.Я не верю, что этот вопрос напрямую связан с каким-либо языком, но вместо этого с используемыми типами (которые, как я понимаю, точно такие же) или с самим DirectX и с тем, как он компилирует шейдеры.Если кто-то, работающий в C++, знает лучший и более описательный ответ, то я бы предпочел его вместо моего собственного ответа.Я понимаю оба языка, но в основном использую C#.


Обзор

В шейдере HLSL при настройке моих постоянных буферов я столкнулся с довольно необычной проблемой.Исходные рассматриваемые постоянные буферы были настроены следующим образом:

cbuffer ObjectBuffer : register(b0) {
    float4x4 WorldViewProjection;
    float4x4 World;
    float4x4 WorldInverseTranspose;
}

cbuffer ViewBuffer : register(b1) {
    DirectionalLight Light;
    float3 CameraPosition;
    float3 CameraUp;
    float2 RenderTargetSize;
}

Если я поменяю местами регистры b0 и b1, рендеринг больше не работает ( e1 ).Если я оставлю эти регистры в покое и поменяю порядок между World и WorldViewProjection снова, рендеринг больше не будет работать ( e2 ).Тем не менее, просто перемещая ViewBuffer над ObjectBuffer в файле HLSL без внесения других изменений, он работает просто отлично.

Теперь я ожидаю, что размещение регистра довольно важно, и что первый регистрb0 требует три свойства, указанные в этом буфере, и я понимаю, что HLSL константные буферы должны быть в 16-байтовых чанках.Однако это оставляет меня с некоторыми вопросами.


Вопросы

Учитывая тот факт, что HLSL ожидает, что постоянные буферы будут в 16-байтовых чанках;

  • Почему порядок в e2 так важен?

Разве float4x4 типы не совпадают с типами Matrix, где по сути это массив массивов?

[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[    TOTAL   ] = 64 bytes

Поскольку float составляет 4 байта само по себе, это будет означать, что float4 составляет 16 байтов, и, таким образом, float4x4 составляет 64 байта.Так почему же порядок имеет значение, если размер остался прежним?

  • Почему ObjectBuffer должен быть назначен b0 в этом случае вместо любого другого регистра b?

1 Ответ

0 голосов
/ 17 ноября 2018

Краткое примечание

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


Основной ответ

Точная проблема с вопросом выше (который был неизвестен навремя публикации), то, что буферы HLSL не соответствуют их представлениям C#;таким образом, переупорядочение переменных привело к сбою шейдера.Тем не менее, я все еще не уверен, почему, когда типы одинаковы.Я узнал о некоторых других вещах на своем пути для ответа и решил опубликовать их здесь.


Почему важен заказ

После некоторых дальнейших исследований и испытаний я все ещене уверен на 100% в том, что за причины этого одинаковы.В целом, я полагаю, это может быть связано с ожидаемыми типами в cbuffer и порядком типов в struct.В этом случае, если ваш cbuffer ожидает сначала bool, а затем float, то перестановка вызывает проблемы.

cbuffer MaterialBuffer : register(b0) {
    bool HasTexture;
    float SpecularPower;
    float4 Ambient;
    ...
}
// Won't work.
public struct MaterialBuffer {
    public float SpecularPower;
    public Vector2 padding2;
    public bool HasTexture;
    private bool padding0;
    private short padding1;
    public Color4 Ambient;
    ...
}
// Works.
public struct MaterialBuffer {
    public bool HasTexture;
    private bool padding0;
    private short padding1;
    public float SpecularPower;
    public Vector2 padding2;
    public Color4 Ambient;
    ...
}

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

1 Byte  : bool, sbyte, byte
2 Bytes : short, ushort
4 Bytes : int, uint, float
8 Bytes : long, ulong, double
16 Bytes: decimal

Вы должны помнить об основных типах, используемых для создания более сложных типов,Например, у вас есть Vector2 со свойством X и свойством Y.Если они представлены типами float, то вам потребуется 8-байтовый отступ до следующего свойства, если у вас нет других вещей, которые помогут достичь 16 байтов.Однако, если они представлены double типами или decimal типами, тогда размер будет другим, и вам нужно об этом знать.


Регистрация назначений

Iудалось решить проблему с реестром;это также соответствует стороне C# при установке буферов.Когда вы устанавливаете буферы, вы назначаете индексы этим буферам, и HLSL, как ожидается, будет использовать те же индексы.

// Buffer declarations in HLSL.
cbuffer ViewBuffer : register(b0)
cbuffer CameraBuffer : register(b1);
cbuffer MaterialBuffer : register(b2);

// Buffer assignments in C#.
context.VertexShader.SetConstantBuffer(0, viewBuffer);
context.VertexShader.SetConstantBuffer(1, cameraBuffer);
context.VertexShader.SetConstantBuffer(2, materialBuffer);

Приведенный выше код будет работать, как и ожидалось, поскольку буферы назначены на правильные регистры,Однако, если мы, например, изменим буфер для камеры на 8, тогда для правильной работы cbuffer потребуется назначить регистр b8.Код ниже не работает именно по этой причине.

cbuffer CameraBuffer : register(b1)
context.VertexShader.SetConstantBuffer(8, cameraBuffer);
...