Я пытаюсь манипулировать произвольными данными, используя преимущества параллелизма, предлагаемого вычислительными шейдерами. Я создал пример сценария, чтобы возиться и провести несколько экспериментов. В частности, я пытаюсь сделать следующее:
- У меня есть черно-белая картинка и относительная json-сериализация, в которой пиксели хранятся в массиве с плавающей точкой. Если пиксель белый, то это 1,0, иначе 0,0
- Я передаю массив float в вычислительный шейдер, используя буфер вычислений
- Шейдер отправляет один поток на каждую ячейку в массиве (поэтому по одному на каждый пиксель изображения)
- Каждый поток читает значение своей ячейки / пикселя:
- Если значение равно "1.0", то оно должно повторить весь массив, сосчитать все "1.0" и сохранить счетчик в своей ячейке выходного буфера
Алгоритм прекрасно работает до определенного размера ввода: размеры изображения 400x400 (размер массива 160000), после чего происходит сбой.
Характеристики моей системы:
- Процессор: процессор Intel Core i7-4700mq @ 2,40 ГГц
- GPU: NVIDIA GeForce GT 750M с 2 ГБ видеопамяти GDDR5
- RAM: 8 ГБ DDR3
- HDD: 256 ГБ SSD
- ОС: Windows 10
- DirectX11
Я пытался запустить код на более мощном настольном ПК с GTX970 и, несмотря на то, что он может обрабатывать большие входные данные, размеры изображения 500x500 (размер массива 250000), после этого он также дает сбой.
Я посмотрел в файле журнала Unity, и когда он падает, он полон этих сообщений об ошибках:
- d3d11: не удалось создать 2D-текстуру в GfxDeviceD3D11
- d3d11: не удалось создать буфер (целевой режим 0x1, размер 960) [0x887A0005]
- Не удалось подтвердить выражение: 'SUCCEEDED (hr)'
Я также пытался использовать RenderDoc для запуска скомпилированной версии проекта Unity, в котором запущена сцена примера, и захватить фрейм, в котором выполняется диспетчерский вызов, но он выдает следующую ошибку: «renderdoc не удалось открыть захват для replay: воспроизведение перехвачено на уровне API ". Я думаю, что он не может захватить этот кадр, потому что DirectX11 падает.
Это соответствующая часть скрипта C #, которая отправляет вычислительный шейдер.
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class ComputeShaderTest1 : MonoBehaviour
{
public TextAsset inputTextureData;
private SerializableTextureData deserializedInputTextureData;
private ComputeShader computeShader;
private ComputeBuffer inputDataBuffer;
private float[] outputValuesData;
private ComputeBuffer outputDataBuffer;
// Use this for initialization
void Start()
{
deserializedInputTextureData = JsonUtility.FromJson<SerializableTextureData>(inputTextureData.text);
computeShader = Resources.Load<ComputeShader>("Shaders/ComputeShader1");
if (computeShader == null)
Debug.LogError("computeShader not found in the specified path");
else
compute();
}
private void compute()
{
int inputDataSize = deserializedInputTextureData.width * deserializedInputTextureData.height;
int csMain = computeShader.FindKernel("CSMain");
if (csMain < 0)
{
Debug.Log("Initialization failed.");
return;
}
uint threadGroupSizeX, threadGroupSizeY, threadGroupSizeZ;
int offsetX, offsetY;
int groupsX, groupsY, groupsZ;
computeShader.GetKernelThreadGroupSizes(csMain, out threadGroupSizeX, out threadGroupSizeY, out threadGroupSizeZ);
offsetX = (int)threadGroupSizeX - 1;
offsetY = (int)threadGroupSizeY - 1;
groupsX = (deserializedInputTextureData.width + offsetX) / (int)threadGroupSizeX;
groupsY = (deserializedInputTextureData.height + offsetY) / (int)threadGroupSizeY;
groupsZ = 1;
inputDataBuffer = new ComputeBuffer(inputDataSize, sizeof(float));
inputDataBuffer.SetData(deserializedInputTextureData.data);
computeShader.SetBuffer(csMain, "InputDataBuffer", inputDataBuffer);
computeShader.SetInt("InputDataWidth", deserializedInputTextureData.width);
computeShader.SetInt("InputDataHeight", deserializedInputTextureData.height);
outputDataBuffer = new ComputeBuffer(inputDataSize, sizeof(float));
computeShader.SetBuffer(csMain, "OutputDataBuffer", outputDataBuffer);
Debug.Log("Dispatching [" + groupsX + "," + groupsY + "," + groupsZ + "] groups");
var watch = System.Diagnostics.Stopwatch.StartNew();
computeShader.Dispatch(csMain, groupsX, groupsY, groupsZ);
watch.Stop();
outputValuesData = new float[inputDataSize];
outputDataBuffer.GetData(outputValuesData);
Debug.Log("Compute Shader Execution Completed. Time elapsed (ns): " + watch.Elapsed.TotalMilliseconds * 1000000);
saveOutDataAsJSON();
saveOutDataAsTexture2D();
}
void OnDestroy()
{
if (inputDataBuffer != null)
inputDataBuffer.Dispose();
if (outputDataBuffer != null)
outputDataBuffer.Dispose();
}
}
И это сам вычислительный шейдер:
#pragma enable_d3d11_debug_symbols
#pragma kernel CSMain
StructuredBuffer<float> InputDataBuffer;
uint InputDataWidth;
uint InputDataHeight;
RWStructuredBuffer<float> OutputDataBuffer;
[numthreads(32, 32, 1)]
void CSMain(uint3 groupID : SV_GroupID,
uint3 groupThreadID : SV_GroupThreadID,
uint groupIndex : SV_GroupIndex,
uint3 id : SV_DispatchThreadID)
{
uint navMeshRes = InputDataWidth * InputDataHeight;
// Each thread is mapped to a single "pixel" of the input
uint index = id.y * InputDataWidth + id.x;
// Check that we are inside the boundaries of the input
if(id.x < InputDataWidth && id.y < InputDataHeight)
{
OutputDataBuffer[index] = 0;
float val = InputDataBuffer[index];
uint i = 0, j = 0;
float v;
if (val == 1)
{
for(i = 0; i < InputDataWidth; i++)
{
for(j = 0; j < InputDataHeight; j++)
{
v = InputDataBuffer[j * InputDataWidth + i];
if (v == 1)
{
OutputDataBuffer[index] += 1;
}
}
}
}
}
}
Я знаю, что код действительно неоптимизирован, избыточен и не нужен, но он предназначен только в качестве контрольного примера для понимания, почему происходит сбой таким образом при увеличении входных данных.
Я ожидал бы некоторого снижения производительности на основе квадратичного коэффициента по сравнению с входными данными, поскольку каждый поток должен проходить через весь массив, плюс некоторые издержки, предоставляемые ресурсами, необходимыми для управления самими потоками.
Что я не понимаю, так это почему происходит сбой, и почему только тогда, когда вход достигает определенного размера.
Есть ли ограничения по размеру для вычислительных буферов? Я бы предположил, что, даже если бы это было так, они могли бы хранить гораздо больше данных, чем то, чем я пытаюсь управлять.
Если вам интересно посмотреть полный код, я сделал открытый репозиторий git: https://github.com/MichelangeloDiamanti/Compute-Shader-Tests