2017-01-31 4 views
0

유체 시뮬레이션을 구현하고 싶습니다. this과 같은 것입니다. 알고리즘은 중요하지 않습니다. 중요한 문제는 픽셀 쉐이더에서 구현할 경우 여러 번 통과해야한다는 것입니다.다중 패스 픽셀 쉐이더를 쉐이더를 계산하도록 변환하는 기술은 무엇입니까?

내가 사용했던 기술의 문제점은 성능이 매우 나쁘다는 것입니다. 한 번에 계산을 해결하는 데 사용한 기술과 기술에 대해 간략히 설명하고 타이밍 정보를 설명합니다.

개요 :

우리는 지형을 가지고 우리가 그 위에 비와 물 흐름을보고 싶어요. 1024x1024 텍스처에 대한 데이터가 있습니다. 지형의 높이와 각 지점의 물의 양이 있습니다. 이는 반복 시뮬레이션입니다. 반복 1은 지형과 물 텍스처를 입력으로 계산하고 그 결과를 지형과 물 텍스처에 씁니다. 그런 다음 반복 2가 실행되고 텍스처가 조금 더 변경됩니다. 이러한 단계 일 각 반복에서

enter image description here

:

  1. 하는 지형과 물 높이를 가져옵니다 반복 수백 후 우리는 이런 일이있다.
  2. 유량을 계산하십시오.
  3. 흐름 값을 그룹 공유 메모리에 씁니다.
  4. 동기화 그룹 메모리
  5. 이 스레드와 현재 스레드의 왼쪽, 오른쪽, 위쪽 및 아래쪽에있는 스레드의 그룹 공유 메모리에서 플로우 값을 읽습니다.
  6. 이전 단계에서 읽은 유량 값을 기준으로 물의 새 값을 계산합니다.
  7. 지형 및 수구에 결과를 작성하십시오.

그래서 기본적으로 우리는 데이터를 가져 calculate1는, 공유 메모리, 동기화에 calculate1 결과를 배치해야합니까, 현재의 thread와 이웃에 대한 공유 메모리에서 가져 calculate2를 수행하고 결과를 작성합니다.

이것은 매우 광범위한 이미지 처리 문제에서 발생하는 명확한 패턴입니다. 고전적인 솔루션은 다중 패스 셰이더가되지만 대역폭을 절약하기 위해 하나의 패스 계산 쉐이더에서 수행했습니다.

기술 : 나는 기술을 사용

Practical Rendering and Computation with Direct3D 11 12 장에서 설명 우리는 각 스레드 그룹이 16x16x1 스레드가되고 싶어요 가정합니다. 그러나 두 번째 계산에는 이웃이 필요하므로 각 방향의 픽셀을 채 웁니다. 즉, 18x18x1 스레드 그룹을 갖게됩니다. 이 패딩으로 인해 두 번째 계산에서 유효한 이웃을 갖게됩니다. 다음은 패딩을 보여주는 그림입니다. 옐로 스레드는 계산해야 할 것이고 빨간색 스레드는 패딩입니다. 그것들은 쓰레드 그룹의 일부분이지만 중간 처리를 위해서 그것들을 사용하고 그것들을 텍스쳐에 저장하지 않을 것이다. 이 그림에서 패딩이있는 그룹은 10x10x1이지만 스레드 그룹은 18x18x1입니다.

enter image description here

프로세스 실행 및 반환 올바른 결과. 유일한 문제는 성능입니다.

타이밍 : 시스템에서 Geforce GT 710을 사용하여 시뮬레이션을 10000 회 반복 실행합니다.

  • 완전하고 정확한 시뮬레이션을 실행하려면 60 초가 걸립니다.
  • 테두리를 채우지 않고 16x16x1 스레드 그룹을 사용하는 경우 시간은 40 초가됩니다. 분명히 결과가 잘못되었습니다.
  • groupshared 메모리를 사용하지 않고 두 번째 계산에 더미 값을 입력하면 시간은 19 초가됩니다. 그 결과는 물론 잘못된 것입니다.

질문 :

  1. 이이 문제를 해결하는 가장 좋은 방법인가? 우리가 두 개의 다른 커널에서 대신 계산한다면 더 빠를 것입니다. 2x19 < 60.
  2. 그룹 공유 메모리가 너무 느린 이유는 무엇입니까?

다음은 계산 쉐이더 코드입니다. 60 초가 걸리는 올바른 버전입니다.

#pragma kernel CSMain 


Texture2D<float> _waterAddTex; 
Texture2D<float4> _flowTex; 

RWTexture2D<float4> _watNormTex; 
RWTexture2D<float4> _flowOutTex; 

RWTexture2D<float> terrainFieldX; 
RWTexture2D<float> terrainFieldY; 
RWTexture2D<float> waterField; 


SamplerState _LinearClamp; 
SamplerState _LinearRepeat; 

#define _gpSize 16 
#define _padGPSize 18 

groupshared float4 f4shared[_padGPSize * _padGPSize]; 

float _timeStep, _resolution, _groupCount, _pixelMeter, _watAddStrength, watDamping, watOutConstantParam, _evaporation; 
int _addWater, _computeWaterNormals; 
float2 _rainUV; 
bool _usePrevOutflow,_useStava; 

float terrHeight(float2 texData) { 
    return dot(texData, identity2); 
} 

[numthreads(_padGPSize, _padGPSize, 1)] 
void CSMain(int2 groupID : SV_GroupID, uint2 dispatchIdx : SV_DispatchThreadID, uint2 padThreadID : SV_GroupThreadID) 
{ 

    int2 id = groupID * _gpSize + padThreadID - 1; 
    int gsmID = padThreadID.x + _padGPSize * padThreadID.y; 
    float2 uv = (id + 0.5)/_resolution; 

    bool outOfGroupBound = (padThreadID.x == 0 || padThreadID.y == 0 || padThreadID.x == _padGPSize - 1 
     || padThreadID.y == _padGPSize - 1) ? true : false; 

    // -------------FETCH------------- 

    float2 cenTer, lTer, rTer, tTer, bTer; 
    sampleUavNei(terrainFieldX,terrainFieldY, id, cenTer, lTer, rTer, tTer, bTer); 

    float cenWat, lWat, rWat, tWat, bWat; 
    sampleUavNei(waterField, id, cenWat, lWat, rWat, tWat, bWat); 

    // -------------Calculate 1------------- 

    float cenTerHei = terrHeight(cenTer); 
    float cenTotHei = cenWat + cenTerHei; 
    float4 neisTerHei = float4(terrHeight(lTer), terrHeight(rTer), terrHeight(tTer), terrHeight(bTer)); 
    float4 neisWat = float4(lWat, rWat, tWat, bWat); 
    float4 neisTotHei = neisWat + neisTerHei; 
    float4 neisTotHeiDiff = cenTotHei - neisTotHei; 


    float4 prevOutflow = _usePrevOutflow? _flowTex.SampleLevel(_LinearClamp, uv, 0):float4(0,0,0,0); 
    float4 watOutflow; 
      float4 flowFac = min(abs(neisTotHeiDiff), (cenWat + neisWat) * 0.5f); 
      flowFac = min(1, flowFac); 
      watOutflow = max(watDamping* prevOutflow + watOutConstantParam * neisTotHeiDiff * flowFac, 0); 
      float outWatFac = cenWat/max(dot(watOutflow, identity4) * _timeStep, 0.001f); 
      outWatFac = min(outWatFac, 1); 
      watOutflow *= outWatFac; 

    // -------------groupshared memory------------- 
    f4shared[gsmID] = watOutflow; 

    GroupMemoryBarrierWithGroupSync(); 

    float4 cenFlow = f4shared[gsmID]; 
    float4 lFlow = f4shared[gsmID - 1]; 
    float4 rFlow = f4shared[gsmID + 1]; 
    float4 tFlow = f4shared[gsmID + _padGPSize]; 
    float4 bFlow = f4shared[gsmID - _padGPSize]; 

    //float4 cenFlow = 0; 
    //float4 lFlow = 0; 
    //float4 rFlow = 0; 
    //float4 tFlow = 0; 
    //float4 bFlow = 0; 

    // -------------Calculate 2------------- 

    if (!outOfGroupBound) { 
      float watDiff = _timeStep *((lFlow.y + rFlow.x + tFlow.w + bFlow.z) - dot(cenFlow, identity4)); 
      cenWat = cenWat + watDiff - _evaporation; 
      cenWat = max(cenWat, 0); 
    } 

    // -------------End of calculation------------- 

    //Water Addition 
    if (_addWater) 
     cenWat += _timeStep * _watAddStrength * _waterAddTex.SampleLevel(_LinearRepeat, uv + _rainUV, 0); 

    if (_computeWaterNormals) 
     _watNormTex[id] = float4(0, 1, 0, 0); 

    // -------------Write results------------- 

    if (!outOfGroupBound) { 
     _flowOutTex[id] = cenFlow; 
     waterField[id] = cenWat; 
    } 

} 

답변

0

확인 끔찍한 실수를 저질 렀습니다. 그룹 공유 메모리의 성능을 테스트하기 위해 다음을 수행했습니다.

// -------------groupshared memory------------- 
f4shared[gsmID] = watOutflow; 

GroupMemoryBarrierWithGroupSync(); 

float4 cenFlow = f4shared[gsmID]; 
float4 lFlow = f4shared[gsmID - 1]; 
float4 rFlow = f4shared[gsmID + 1]; 
float4 tFlow = f4shared[gsmID + _padGPSize]; 
float4 bFlow = f4shared[gsmID - _padGPSize]; 

//float4 cenFlow = 0; 
//float4 lFlow = 0; 
//float4 rFlow = 0; 
//float4 tFlow = 0; 
//float4 bFlow = 0; 

동기화 후 일부 변수가 표시됩니다. 변수를 초기화하여 값을 수정하는 상위 5 개 문으로 테스트했습니다. 그런 다음 groupshared없이 테스트하려면 5 개의 문장을 주석 처리하고 변수를 0으로 초기화하는 5 개의 주석문의 주석 처리를 제거하십시오. 이것이 다음과 같이되었습니다.

// -------------groupshared memory------------- 
//f4shared[gsmID] = watOutflow; 

//GroupMemoryBarrierWithGroupSync(); 

//float4 cenFlow = f4shared[gsmID]; 
//float4 lFlow = f4shared[gsmID - 1]; 
//float4 rFlow = f4shared[gsmID + 1]; 
//float4 tFlow = f4shared[gsmID + _padGPSize]; 
//float4 bFlow = f4shared[gsmID - _padGPSize]; 

float4 cenFlow = 0; 
float4 lFlow = 0; 
float4 rFlow = 0; 
float4 tFlow = 0; 
float4 bFlow = 0; 

그리고 나서 프로세스가 훨씬 빨라졌습니다. 내가 눈치 채지 못했던 한 가지는 더 이상 이전 계산을 사용하지 않고 컴파일러가 최적화하지 않았기 때문입니다. 알고리즘을 더 구현 한 후에는 groupshared 메모리가 실제로 잘 작동하고 텍스처 읽기에 비해 약간의 시간이 걸린다는 것을 알게되었습니다.

결국 나는 1 패스에서 알고리즘을 처리했습니다. 그것은 원래 구현보다 2.5 배 빠르며 질문에 설명 된 기술은 필자의 경우 정상적으로 작동합니다.