📜 ⬆️ ⬇️

Calculations on the video card, manual, easy level

This guide explains the work of a simple program that performs calculations on the GPU. Here is a link to the Unity project of this program:

link to the project file .unitypackage

She draws a Mandelbrot fractal.
')
I will not explain each line of code, I will only indicate the necessary actions for the implementation of GPU calculations. Therefore, it is best to open the program code in Unity and there to see how the lines of code explained by me are used.

The shader that draws the fractal is written in the HLSL language. Below is the text. I briefly commented on meaningful lines, and detailed explanations will be below.

//   GPU       : RWTexture2D<float4> textureOut; //  ,       RWStructuredBuffer<double> rect; //      ,    RWStructuredBuffer<float4> colors; //    ,      CPU     #pragma kernel pixelCalc //    ,          CPU [numthreads(32,32,1)] //     ,      void pixelCalc (uint3 id : SV_DispatchThreadID){ //     .  id   ,      float k = 0.0009765625; //       10241024     22   double dx, dy; double p, q; double x, y, xnew, ynew, d = 0; //    ,           uint itn = 0; dx = rect[2] - rect[0]; dy = rect[3] - rect[1]; p = rect[0] + ((int)id.x) * k * dx; q = rect[1] + ((int)id.y) * k * dy; x = p; y = q; while (itn < 255 && d < 4){ //   :      ,      2x2 xnew = x * x - y * y + p; ynew = 2 * x * y + q; x = xnew; y = ynew; d = x * x + y * y; itn++; } textureOut[id.xy] = colors[itn]; //      :    ,    -   } 

The attentive reader will say: the author, explain! The size of the texture is 1024x1024, and the number of threads is 32x32. How does the id.xy parameter address all pixels of a texture?
Attentive, but inexperienced in matters of computing on the GPU reader will interrupt: let me! And where does it mean that the number of threads is 32x32? And how to understand "id.xy"?

To the second, I will answer this way: the directive [numthreads (32,32,1)] says that we have 32x32x1 threads. In this case, the streams form a three-dimensional grid, because the id parameter takes values ​​in the form of coordinates of the space 32x32x1. The range of values ​​id.x [0, 31], the range of values ​​id.y [0, 31], and id.z is 0. And id.xy is a short uint2 record (id.x, id.y)

We would have 32x32 threads (I’m already responding to the first attentive reader) if we called this kernel from the CPU side with the command

 ComputeShader.Dispatch(kernelIndex, 1, 1, 1) 

See these three units? This is the same as the digits in the [numthreads (32,32,1)] directive, they are multiplied with each other.

If we started the shader with the following parameters:

 ComputeShader.Dispatch(kernelIndex, 2, 4, 1) 

We would have 32 * 2 = 64 along the x axis, 32 * 4 = 128 along the y axis, that is, in total - 64x128 streams. Parameters are simply multiplied along each axis.

But in our case, the kernel is launched like this:

 ComputeShader.Dispatch(kernelIndex, 32, 32, 1) 

What gives us in the end 1024x1024 flow. And it means that id.xy index will take values ​​covering the whole texture space of 1024x1024

This is done for convenience. Data is stored in arrays, each stream performs the same operation on a data unit, and we create the number of threads equal to the number of data units, so that the flow index addresses its data unit. Very comfortably.

That's all you need to know about the shader code of our fractalizing program.

Now let's look at what we did on the CPU side to run the shader code.

We declare variables: shader, buffer and texture

 ComputeShader _shader RenderTexture outputTexture ComputeBuffer colorsBuffer 

We initialize the texture without forgetting to enable enableRandomWrite

 outputTexture = new RenderTexture(1024, 1024, 32); outputTexture.enableRandomWrite = true; outputTexture.Create(); 

Initialize the buffer by specifying the number of objects and the size of the object. And write the data of the pre-filled array of colors in the video memory

 colorsBuffer = new ComputeBuffer(colorArray.Length, 4 * 4); colorsBuffer.SetData(colorArray); 

Initialize the shader and set the texture and buffer for the kernel so that it can write data to them

 _shader = Resources.Load<ComputeShader>("csFractal"); kiCalc = _shader.FindKernel("pixelCalc"); _shader.SetBuffer(kiCalc, "colors", colorsBuffer); _shader.SetTexture(kiCalc, "textureOut", outputTexture); 

This is the preparation of the data. Now it only remains to run the shader kernel

 _shader.Dispatch(kiCalc, 32, 32, 1); 

After executing this command, the texture is filled with colors that we immediately see, because the RenderTexture texture is used as the mainTexture for the Image component that the camera is looking at.

Source: https://habr.com/ru/post/346260/


All Articles