Как передать результаты вычисления шейдера в вершинный шейдер без использования буфера вершин?

Прежде чем углубляться в детали, я хочу обрисовать проблему:

Я использую RWStructuredBuffers для хранения вывода моих вычислительных шейдеров (CS). Поскольку вершинные и пиксельные шейдеры не могут читать из RWStructuredBuffers, я сопоставляю StructuredBuffer в один и тот же слот (u0/t0) и (u4/t4):

cbuffer cbWorld : register (b1) 
{
    float4x4 worldViewProj;
    int dummy;
}   

struct VS_IN
{
    float4 pos : POSITION;
    float4 col : COLOR;
};

struct PS_IN
{

    float4 pos : SV_POSITION;
    float4 col : COLOR;
};

RWStructuredBuffer<float4> colorOutputTable : register (u0);    // 2D color data
StructuredBuffer<float4> output2 :            register (t0);    // same as u0
RWStructuredBuffer<int> counterTable :        register (u1);    // depth data for z values
RWStructuredBuffer<VS_IN>vertexTable :        register (u4);    // triangle list
StructuredBuffer<VS_IN>vertexTable2 :         register (t4);    // same as u4

Я использую ShaderRecourceView для предоставления доступа к буферам пиксельных и / или вершинных шейдеров. Эта концепция прекрасно работает для моего пиксельного шейдера, однако вершинный шейдер, похоже, читает только 0 значений (я использую SV_VertexID в качестве индекса для буферов):

PS_IN VS_3DA ( uint vid : SV_VertexID ) 
{           
    PS_IN output = (PS_IN)0; 
    PS_IN input = vertexTable2[vid];
    output.pos = mul(input.pos, worldViewProj); 
    output.col = input.col; 
    return output;
}

Нет сообщений об ошибках или предупреждений от компилятора hlsl, renderloop работает со скоростью 60 кадров в секунду (используя vsync), но экран остается черным. Так как я очищаю экран с помощью Color.White до вызова Draw(..), конвейер рендеринга кажется активным.

Когда я считываю содержимое данных треугольника через UAView из графического процессора в "vertArray" и возвращаю его обратно в буфер вершин, все работает, однако:

Программа:

    let vertices = Buffer.Create(device, BindFlags.VertexBuffer, vertArray)
    context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertices, Utilities.SizeOf<Vector4>() * 2, 0))

HLSL:

PS_IN VS_3D (VS_IN input )
{
    PS_IN output = (PS_IN)0;    
    output.pos = mul(input.pos, worldViewProj);
    output.col = input.col; 
    return output;
}

Здесь определение 2D - Vertex / Pixelshaders. Обратите внимание, что PS_2D обращается к буферу "output2" в слоте t0 - и это именно та "хитрость", которую я хочу повторить для трехмерного вершинного шейдера "VS_3DA":

float4 PS_2D ( float4 input : SV_Position) : SV_Target
{        
    uint2 pixel =  uint2(input.x, input.y);         
    return output2[ pixel.y * width + pixel.x]; 
}

float4 VS_2D ( uint vid : SV_VertexID ) : SV_POSITION
{
if (vid == 0)
    return float4(-1, -1, 0, 1);
if (vid == 1)
    return float4( 1, -1, 0, 1);
if (vid == 2)
    return float4(-1,  1, 0, 1);    

return float4( 1,  1, 0, 1);    
}

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

Кто-нибудь может дать совет? Спасибо, что прочитали мой пост!

================================================== ===================

ПОДРОБНОСТИ:

Мне очень нравится концепция вычислительных шейдеров DirectX 11, и я хочу использовать ее для алгебраических вычислений. В качестве тестового примера я рендерил фракталы (множества Мандельброта) в 3D. Все работает как положено - кроме одного последнего кирпича в стене не хватает.

Расчет состоит из следующих шагов:

  1. Использование CS для вычисления 2D-текстуры (выходные данные "counterTable" и "colorOutbutTable" (работает)

  2. При желании визуализировать эту текстуру на экране (работает)

  3. Использование другой CS для создания сетки (список треугольников). Эта CS берет значения x, y и цвета из шага 1, вычисляет координату z и, наконец, создает квад для каждого пикселя. Результат сохраняется в "vertexTable". (работает)

  4. Подача списка треугольников в вершинный шейдер (проблема!!!)

  5. Визуализация на экране (работает - с использованием буфера вершин).

Для программирования я использую F# 3.0 и SharpDX в качестве оболочки.NET. ShaderRessourceView для обоих шейдеров (пиксель и вершина) настроен с одинаковыми параметрами (кроме параметров размера):

let mutable descr = new BufferDescription()     
descr.BindFlags <- BindFlags.UnorderedAccess ||| BindFlags.ShaderResource 
descr.Usage <- ResourceUsage.Default  
descr.CpuAccessFlags <- CpuAccessFlags.None
descr.StructureByteStride <- xxx    / / depends on shader
descr.SizeInBytes <-  yyy       / / depends on shader
descr.OptionFlags <- ResourceOptionFlags.BufferStructured

Здесь нет ничего особенного. Создание 2D-буфера (привязывается к буферу "output2" в слоте t0):

outputBuffer2D <- new Buffer(device, descr) 
outputView2D <- new UnorderedAccessView (device, outputBuffer2D)  
shaderResourceView2D <- new ShaderResourceView (device, outputBuffer2D)

Создание 3D-буфера (привязывается к "vertexTable2" в слоте t4):

vertexBuffer3D <- new Buffer(device, descr) 
shaderResourceView3D <- new ShaderResourceView (device, vertexBuffer3D)
//  UAView not required here

Настройка ресурсов для 2D:

context.InputAssembler.PrimitiveTopology <- PrimitiveTopology.TriangleStrip
context.OutputMerger.SetRenderTargets(renderTargetView2D)
context.OutputMerger.SetDepthStencilState(depthStencilState2D)
context.VertexShader.Set (vertexShader2D)
context.PixelShader.Set (pixelShader2D) 

визуализация 2D:

context.PixelShader.SetShaderResource(COLOR_OUT_SLOT, shaderResourceView2D)
context.PixelShader.SetConstantBuffer(CONSTANT_SLOT_GLOBAL, constantBuffer2D )
context.ClearRenderTargetView (renderTargetView2D, Color.White.ToColor4())         
context.Draw(4,0)                                                
swapChain.Present(1, PresentFlags.None)            

Настройка ресурсов для 3D:

context.InputAssembler.PrimitiveTopology <- PrimitiveTopology.TriangleList
context.OutputMerger.SetTargets(depthView3D, renderTargetView2D)
context.VertexShader.SetShaderResource(TRIANGLE_SLOT, shaderResourceView3D )
context.VertexShader.SetConstantBuffer(CONSTANT_SLOT_3D, constantBuffer3D)
context.VertexShader.Set(vertexShader3D)
context.PixelShader.Set(pixelShader3D)

рендеринг 3D (не работает - черный экран как результат вывода)

context.ClearDepthStencilView(depthView3D, DepthStencilClearFlags.Depth, 1.0f, 0uy)
context.Draw(dataXsize * dataYsize * 6, 0)
swapChain.Present(1, PresentFlags.None)

Наконец номера слотов:

static let CONSTANT_SLOT_GLOBAL = 0
static let CONSTANT_SLOT_3D = 1
static let COLOR_OUT_SLOT = 0
static let COUNTER_SLOT = 1
static let COLOR_SLOT = 2    
static let TRIANGLE_SLOT = 4

2 ответа

Решение

Хорошо, во-первых, я бы предложил включить слой отладки (используйте флаг отладки при создании устройства), затем перейдите к свойствам проекта, вкладке отладки и отметьте "Включить отладку неуправляемого кода" или "Включить отладку собственного кода".

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

Одна потенциальная проблема (которая выглядит наиболее вероятной из того, что вы опубликовали): не забудьте очистить слоты БПЛА вашего вычислительного шейдера после отправки. Если вы попытаетесь привязать vertexTable2 к вашему вершинному шейдеру, но ресурс по-прежнему будет привязан к выходным данным вычислительного шейдера, среда выполнения автоматически установит для вашего ShaderView значение null (которое, в свою очередь, вернет 0, когда вы попытаетесь его прочитать).

Чтобы очистить ваш Compute Shader, вызовите это в контексте вашего устройства, которое вы сделали с диспетчеризацией:

ComputeShader.SetUnorderedAccessView(TRIANGLE_SLOT, null)

Также обратите внимание, что PixelShader может получить доступ к RWStructuredBuffer (технически вы можете использовать RWStructuredBuffer для любого типа шейдера, если у вас есть уровень функций 11.1, что означает недавнюю карту ATI и Windows 8+).

Подача списка треугольников в вершинный шейдер (проблема!!!)

Вместо того, чтобы использовать структурированные буферы (которые не позволяют вам связываться как vb), я бы хотел использовать необработанные буферы. Это требует приведения в шейдер, но позволяет вам использовать тот же буфер в вашей CS и против

При создании буфера выполните:

D3D11_BUFFER_DESC desc = {};
desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = byteSize;
desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;

Затем вы можете связать как шейдерный ресурс:

D3D11_SHADER_RESOURCE_VIEW_DESC desc = {};
desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;
desc.BufferEx.FirstElement = 0;
desc.Format = DXGI_FORMAT_R32_TYPELESS;
desc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW;
desc.BufferEx.NumElements = descBuf.ByteWidth / 4;

или просмотр неупорядоченного доступа:

D3D11_UNORDERED_ACCESS_VIEW_DESC desc = {};
desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
desc.Buffer.FirstElement = 0;
desc.Format = DXGI_FORMAT_R32_TYPELESS; // Format must be DXGI_FORMAT_R32_TYPELESS, when creating Raw Unordered Access View
desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
desc.Buffer.NumElements = descBuf.ByteWidth / 4; 

В шейдере вы бы использовали что-то вроде этого:

ByteAddressBuffer Buffer0 : register(t0);
ByteAddressBuffer Buffer1 : register(t1);
RWByteAddressBuffer BufferOut : register(u0);

int i0 = asint( Buffer0.Load( DTid.x*8 ) );
float f0 = asfloat( Buffer0.Load( DTid.x*8+4 ) );
int i1 = asint( Buffer1.Load( DTid.x*8 ) );
float f1 = asfloat( Buffer1.Load( DTid.x*8+4 ) );

BufferOut.Store( DTid.x*8, asuint(i0 + i1) );
BufferOut.Store( DTid.x*8+4, asuint(f0 + f1) );

Пример кода выше был взят из образца BasicCompute11 из DirectX June 2010 SDK. Это демонстрирует использование как структурированных буферов, так и необработанных буферов.

Другие вопросы по тегам