Compute Shader падает, когда буфер слишком велик - PullRequest
0 голосов
/ 10 мая 2019

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

  • У меня есть черно-белая картинка и относительная 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

...