本篇博客给读者介绍关于计算着色,在directx11中,微软引入了计算着色器(也称为直接计算),它基于可编程着色器,并利用GPU来执行高速通用计算。这个想法是使用一个写在HLSL中的着色器来制作一些图形。不同于我们编写的通常的着色器,计算着色器提供了某种形式的内存共享和线程同步,这有助于改进我们使用该工具所能做的事情。计算着色器的执行即使它可以访问图形资源,它也不附加到图形管道的任何阶段,当我们分派一个计算着色器调用时,我们所做的是生成一些GPU线程,它们运行一些我们编写的着色程序代码。

我们需要理解的主要问题是,不同于像素和顶点着色,计算着色器不受输入/输出数据的约束;在执行代码和处理数据的线程之间

没有隐式映射。每个线程都可以从任何内存位置读取,也可以在任何地方写入。这是计算着色器的主要问题。它们提供了一种方法,

可以将GPU用作通用计算的大型矢量处理器。

在这里我们以一个简单的事例给读者介绍一下,假设从一个图像读取数据,并输出另一个图像,处理图像并不是唯一可以使用

计算着色器的方法,但从更简单的角度看,决定这样做。同样,由于线程和数据之间没有隐式关联,没有人强迫我们每个线程处理

一个像素;我们可以处理几百个。对于本篇博客,我们将使用简单的方法,我们将处理每个线程的像素,但是记住,这不是强制的,

我们开始吧。

下面开始实现上述功能,首先进行初始化操作,cpp实际上只是创建了一个DXApplication(简单命名的应用程序)实例,并调用如下方法。

if( FAILED( InitWindow( hInstance, nCmdShow ) ) )return 0;if(!application.initialize(g_hWnd, width, height))return 0;// Main message loopMSG msg = {0};while( WM_QUIT != msg.message ){if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ){TranslateMessage( &msg );DispatchMessage( &msg );}else{application.render();}}return ( int )msg.wParam;

继续介绍,我不会给出初始化或渲染,因为它们非常简单。前者创建DX11设备,加载纹理等,而后者则使用两种纹理来呈现一个大的quad。唯一有趣的是在初始化的地方,我们调用了两个方法,createInputBuffer和createOutputBuffer,以创建需要为计算着色器工作的缓冲区,并在它加载和运行计算着色器(runComputeShader)之后立即运行。我们将详细介绍这三个函数,因为它们是本博客的核心。

    // ... last lines of DXApplication::initialize()// We then load a texture as a source of data for our compute shaderif(!loadFullScreenQuad())return false;if(!loadTexture( L"data/fiesta.bmp", &m_srcTexture ))return false;if(!createInputBuffer())return false;if(!createOutputBuffer())return false;if(!runComputeShader( L"data/Desaturate.hlsl"))return false;return true;
}

运行程序,按F1和F2,我们在两个计算着色器之间切换。这在代码调用runComputeShader(L“数据/ Desaturate.hlsl”)和runComputeShader(L“data / circl . hlsl”)上分别在F1和F2键上完成。runComputeShader函数从HLSL文件加载计算着色器,并分派线程组。

在继续讲解之前,最好先谈谈GPU的工作原理,与CPU不同的是,gpu由多个称为流处理器的处理器组成,每个处理器都可以用来运行线程并执行我们的着色器的代码。流处理器被打包成块,称为SIMDs,它们有本地数据共享、缓存、纹理缓存和获取/解码单元。

这实际上是一种简化,像GeForce 6600这样的旧的视频卡,曾经有顶点、片段和组合单元(而且它们无法运行计算着色器!)无论如何,我们将简化GPU的视图。

继续介绍,在GPU上,我们有几个SIMDs(例如,在ATI HD6970上有24个单元),每一个都可以用来运行一组线程,如果不深入了解细节,我们需要知道的是,可以编写我们的计算着色器,可以创建具有一些共享内存的线程组,并且能够并发运行。这些线程能够从一些缓冲区读取数据并将其写入输出缓冲区。因此,除了像素着色器之外,设置计算着色器的两个主要内容是线程之间的共享内存和在输出缓冲区中写入的可能性。

从线程的角度考虑计算着色器是很重要的,而不是“像素”或“顶点”。处理数据的线程,我们可以为每个线程计算4个像素,或者我们可以做物理

运算来移动刚体,计算着色器允许我们使用GPU作为一个强大的矢量并行处理器!

现在我们知道,我们可以在GPU上生成线程,我们必须在组中组织这些线程。这如何转化为代码?我们指定直接在shader代码内生成的线程数。这是用以下语法完成的:

[numthreads(X, Y, Z)]
void ComputeShaderEntryPoint( /* compute shader parameters */ )
{// ... Compue shader code
}

程序代码中有X,Y,Z,其中X、Y和Z代表该组织的每条轴的大小。这意味着,如果我们指定X = 8,Y = 8,Z = 1,我们得到8 * 8 * 1 = 64个线程。但是为什么我们不能只指定一个数字,比如64呢?为什么要指定每个轴的大小?实际上,唯一的原因是有一种方便的方法来访问在矩阵(如图像)上工作的线程。实际上,我们可以从以下系统中获得当前线程id:

uint3 groupID:SV_GroupID

-在每个维度的调度范围内的分组索引

uint3 groupThreadID:SV_GroupThreadID

-每个维度的组内线程的索引

使用uint groupIndex:SV_GroupIndex

-组内的顺序索引,从左上角开始,一直到右下角

uint3 dispatchThreadID:SV_DispatchThreadID

-整个调度中的全局线程索引

我们需要这些值来区分哪些数据可以访问哪个线程。我们来看看,例如,我们如何编写一个简单的减饱和度计算着色器。

[numthreads(32, 16, 1)]
void CSMain( uint3 dispatchThreadID : SV_DispatchThreadID )
{float3 pixel = readPixel(dispatchThreadID.x, dispatchThreadID.y);pixel.rgb = pixel.r * 0.3 + pixel.g * 0.59 + pixel.b * 0.11;writeToPixel(dispatchThreadID.x, dispatchThreadID.y, pixel);
}

因此, 这个计算着色器有32 * 16 * 1个线程组(例如,每个组最大线程数为768,在cs_4_x中为最大Z = 1,在cs_5_0中为最大Z = 64)。唯一一个函数的参数是全局线程ID,恰好是,由于我们操作一个图像,像素的X和y的函数readPixel和writePixel,在这里我们想只关注逻辑,我们调用readPixel,它将从着色器提供的图像中读取相应的像素,然后我们取消了像素并将结果保存到可变像素本身,我们不利用函数writePixel将我们的新值输出到输出图像。

/**
*    Run a compute shader loaded by file
*/
bool DXApplication::runComputeShader( LPCWSTR shaderFilename )
{// Some service variablesID3D11UnorderedAccessView* ppUAViewNULL[1] = { NULL };ID3D11ShaderResourceView* ppSRVNULL[2] = { NULL, NULL };// We load and compile the shader. If we fail, we bail out here.if(!loadComputeShader( shaderFilename, &m_computeShader ))return false;// We now set up the shader and run itm_pImmediateContext->CSSetShader( m_computeShader, NULL, 0 );m_pImmediateContext->CSSetShaderResources( 0, 1, &m_srcDataGPUBufferView );m_pImmediateContext->CSSetUnorderedAccessViews( 0, 1, &m_destDataGPUBufferView, NULL );m_pImmediateContext->Dispatch( 32, 21, 1 );m_pImmediateContext->CSSetShader( NULL, NULL, 0 );m_pImmediateContext->CSSetUnorderedAccessViews( 0, 1, ppUAViewNULL, NULL );m_pImmediateContext->CSSetShaderResources( 0, 2, ppSRVNULL );...

在 这里没有什么复杂的,忽略我们用来清除输入和输出缓冲区的变量,第一个有趣的是加载着色器的行,自从我们每次点击F1或F2的时候,我们都运行这个着色器,我们每次都重新加载它并重新编译它。

这样,一旦着色器被加载,我们就运行它。方法CSSetShader设置我们的计算着色器。CSSetShaderResources和CSSetUnorderedAccessViews用于设置输入和输出缓冲区。我们将在后面详细介绍这些内容;这里重要的是m_srcDataGPUBufferView是计算着色器的输入缓冲区,它包含我们的输入图像数据,而m_destDataGPUBufferView是一个输出缓冲区,它将包含我们的输出图像数据。

这里最重要的是调度,这是我们对DX11进行着色的地方,也是我们指定创建组的位置,由于我们已经决定每个像素运行一个线程,因此需要分派足够的组来覆盖整个图像;picutre维度是1024x336x1,我们为每个组生成32x16x1线程,因此我们需要32x16x1组来完美地覆盖图像。在最后一行中,我们重新设置了DX11状态。

至此,我们已经了解了如何指定线程和线程组。这是我们需要知道的关于计算着色器的一半。另外一半是如何为计算着色器提供数据到GPU工作。

接下来继续介绍,计算着色器以两种方式接收输入数据:字节地址缓冲区(原始缓冲区)和结构化缓冲区。在本博客中,我们将使用结构化的缓冲区,但是像往常一样,记住缓存正在做什么是很重要的;这是阵列结构与结构阵列的典型问题。

我们的结构化缓冲区,定义为计算着色器,看起来是这样的:

struct Pixel
{int colour;
};StructuredBuffer<Pixel> Buffer0 : register(t0);

给读者解释一下,这个结构包含一个元素,因此我们可以很容易地使用原始缓冲区,但是我们可能想要指定r、g、b、a和4个浮点数,在这种情况下,将它们封装到结构中是很有用的。

为了计算方便,我已经决定使用int来编码颜色,只是为了在着色器内部进行比特移位操作。显然,这并不是GPU的最佳用途,它更喜欢使用浮点和向量,但我对使用这些新操作很有兴趣,它们与shaders 4和5一起使用。再说一次,这不是GPU最好的用途,我们只是为了方便。

现在,要使用DX11创建结构化缓冲区,我们使用以下代码:

/**
*    Once we have the texture data in RAM we create a GPU buffer to feed the
*    compute shader.
*/
bool DXApplication::createInputBuffer()
{if(m_srcDataGPUBuffer)m_srcDataGPUBuffer->Release();m_srcDataGPUBuffer = NULL;if(m_srcTextureData){// First we create a buffer in GPU memoryD3D11_BUFFER_DESC descGPUBuffer;ZeroMemory( &descGPUBuffer, sizeof(descGPUBuffer) );descGPUBuffer.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;descGPUBuffer.ByteWidth = m_textureDataSize;descGPUBuffer.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;descGPUBuffer.StructureByteStride = 4;    // We assume the data is in the// RGBA format, 8 bits per chanD3D11_SUBRESOURCE_DATA InitData;InitData.pSysMem = m_srcTextureData;if(FAILED(m_pd3dDevice->CreateBuffer( &descGPUBuffer, &InitData, &m_srcDataGPUBuffer )))return false;// Now we create a view on the resource. DX11 requires you to send the data// to shaders using a "shader view"D3D11_BUFFER_DESC descBuf;ZeroMemory( &descBuf, sizeof(descBuf) );m_srcDataGPUBuffer->GetDesc( &descBuf );D3D11_SHADER_RESOURCE_VIEW_DESC descView;ZeroMemory( &descView, sizeof(descView) );descView.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;descView.BufferEx.FirstElement = 0;descView.Format = DXGI_FORMAT_UNKNOWN;descView.BufferEx.NumElements=descBuf.ByteWidth/descBuf.StructureByteStride;if(FAILED(m_pd3dDevice->CreateShaderResourceView( m_srcDataGPUBuffer, &descView, &m_srcDataGPUBufferView )))return false;return true;}elsereturn false;
}

注意,这个函数假定我们已经从磁盘加载了图像,并将所有的像素保存到m_srcTextureData中。

我们所做的只是创建一个无序访问资源,它可以绑定到着色器。我们还指定了结构化的缓冲标志,并提供了两个元素之间的跨越(这是4个字节,每个通道一个)。

如果在没有问题的情况下创建缓冲区,我们也会创建一个shader视图,它是向着色器提供数据的DX11方法。

与我们创建输出缓冲区的方式非常相似:

/**
*    We know the compute shader will output on a buffer which is
*    as big as the texture. Therefore we need to create a
*    GPU buffer and an unordered resource view.
*/
bool DXApplication::createOutputBuffer()
{// The compute shader will need to output to some buffer so here // we create a GPU buffer for that.D3D11_BUFFER_DESC descGPUBuffer;ZeroMemory( &descGPUBuffer, sizeof(descGPUBuffer) );descGPUBuffer.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;descGPUBuffer.ByteWidth = m_textureDataSize;descGPUBuffer.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;descGPUBuffer.StructureByteStride = 4;    // We assume the output data is // in the RGBA format, 8 bits per channelif(FAILED(m_pd3dDevice->CreateBuffer( &descGPUBuffer, NULL, &m_destDataGPUBuffer )))return false;// The view we need for the output is an unordered access view. // This is to allow the compute shader to write anywhere in the buffer.D3D11_BUFFER_DESC descBuf;ZeroMemory( &descBuf, sizeof(descBuf) );m_destDataGPUBuffer->GetDesc( &descBuf );D3D11_UNORDERED_ACCESS_VIEW_DESC descView;ZeroMemory( &descView, sizeof(descView) );descView.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;descView.Buffer.FirstElement = 0;// Format must be must be DXGI_FORMAT_UNKNOWN, when creating // a View of a Structured BufferdescView.Format = DXGI_FORMAT_UNKNOWN;      descView.Buffer.NumElements = descBuf.ByteWidth / descBuf.StructureByteStride; if(FAILED(m_pd3dDevice->CreateUnorderedAccessView( m_destDataGPUBuffer, &descView, &m_destDataGPUBufferView )))return false;return true;
}

非常类似于shader视图,在本例中是unordere访问视图。

最后一件值得展示的东西是对饱和度效果的完全着色。请记住,这一切都是最理想的,主要是为了实验!

struct Pixel
{int colour;
};StructuredBuffer<Pixel> Buffer0 : register(t0);
RWStructuredBuffer<Pixel> BufferOut : register(u0);float3 readPixel(int x, int y)
{float3 output;uint index = (x + y * 1024);output.x = (float)(((Buffer0[index].colour ) & 0x000000ff)      ) / 255.0f; output.y = (float)(((Buffer0[index].colour ) & 0x0000ff00) >> 8 ) / 255.0f;output.z = (float)(((Buffer0[index].colour ) & 0x00ff0000) >> 16) / 255.0f;return output;
}void writeToPixel(int x, int y, float3 colour)
{uint index = (x + y * 1024);int ired   = (int)(clamp(colour.r,0,1) * 255);int igreen = (int)(clamp(colour.g,0,1) * 255) << 8;int iblue  = (int)(clamp(colour.b,0,1) * 255) << 16;BufferOut[index].colour = ired + igreen + iblue;
}[numthreads(32, 16, 1)]
void CSMain( uint3 dispatchThreadID : SV_DispatchThreadID )
{float3 pixel = readPixel(dispatchThreadID.x, dispatchThreadID.y);pixel.rgb = pixel.r * 0.3 + pixel.g * 0.59 + pixel.b * 0.11;writeToPixel(dispatchThreadID.x, dispatchThreadID.y, pixel);
}

提供的源代码有更多的功能,主要是读取纹理并将结果返回到屏幕上。由于这与计算着色无关,我在这里没有包含它,但是在代码中有一些注释,

以便您了解正在发生的事情。不过,这都是非常简单和线性的。

源代码用VS2010编译

链接:http://pan.baidu.com/s/1sl0Tc2x  密码:1kr6

DX11编程之计算着色过滤器相关推荐

  1. 《OpenGL编程指南(原书第8版)》——计算着色器

    原文  http://www.csdn.net/article/2014-11-21/2822754 主题 OpenGL 数学 概述 由于图形处理器每秒能够进行数以亿计次的计算,它已成为一种性能十分惊 ...

  2. OpenGL深入探索——《OpenGL编程指南(原书第8版)》——计算着色器

    转载自 <OpenGL编程指南(原书第8版)>--计算着色器 概述 由于图形处理器每秒能够进行数以亿计次的计算,它已成为一种性能十分惊人的器件.过去,这种处理器主要被设计用于承担实时图形渲 ...

  3. D3D11计算着色器配置与编程

    如果开始研究计算着色器了,说明读者已经有一定的D3D11基础,自己也跑过几个程序,那么我希望看完的人能够达到自己完成编写计算着色器文件,完成自己的项目任务.由于我学习D3D11是直接跳过其他着色器的( ...

  4. DirectX11 With Windows SDK--27 计算着色器:双调排序

    前言 上一章我们用一个比较简单的例子来尝试使用计算着色器,但是在看这一章内容之前,你还需要了解下面的内容: 章节 26 计算着色器:入门 深入理解与使用缓冲区资源(结构化缓冲区/有类型缓冲区) Vis ...

  5. Directx 计算着色器(compute shader)

    原文 :http://www.cnblogs.com/Ninputer/archive/2009/12/11/1622190.html 博者注:计算着色器调试(http://msdn.microsof ...

  6. 学习编写Unity计算着色器 Learn to Write Unity Compute Shaders

    利用图形处理器的力量 你会学到: 如何编写Unity计算着色器 如何在后处理图像过滤器中使用ComputeShaders 如何使用ComputeShaders进行粒子效果和群集 如何使用Structu ...

  7. 可编程渲染管线与着色器语言

    Programming pipeline & shading language 大家好,今天想给大家介绍一下可编程渲染管线和着色器语言的相关基础知识,使想上手SHADER编程的童鞋们可以快速揭 ...

  8. OpenGL超级宝典(第7版)笔记11 帧缓存运算 计算着色器 清单 3.13

    OpenGL超级宝典(第7版)笔记11 帧缓存运算 计算着色器 清单 3.13 文章目录 OpenGL超级宝典(第7版)笔记11 帧缓存运算 计算着色器 清单 3.13 1 帧缓存运算 1.1 裁剪测 ...

  9. Unity计算着色器 01

    序 计算着色器,是什么? 官方文档,启动! Unity - Manual: Compute shaders (unity3d.com) 大概是这个画风: 就看到了有<GPU>这个字眼,还说 ...

  10. 计算着色器(Compute Shader)

    图形处理器(Graphics Processing Unit,简称GPU)每秒能够进行数以亿次的计算,目前其已成为一种性能十分惊人的器件.通常,GPU主要用来承担实时图形渲染中的海量数学运算,然而,其 ...

最新文章

  1. 第十二周学习进度总结
  2. Java注释是一个大错误
  3. Java-逻辑运算符、位运算符
  4. pythonifnotnone_python中if not x: 和 if x is not None: 和 if not x is None的使用和区别
  5. Apache性能诊断与调优
  6. 陈希孺概率与数统:入门级自学佳作
  7. 全网可达,交换机和路由器的配置,vlan
  8. LED驱动电路设计及原理分析
  9. 技术系统进化法则包括_八大技术系统进化法则主要包括哪些
  10. linux reedme常用单词,【每天打卡记单词】高中英语必背单词3500(Q/R)
  11. 蓝桥杯-决赛B组第七届java
  12. html页面太大了怎么调小,html – 如何在调整浏览器窗口大小时保持绝对定位的元素...
  13. 开发润乾报表过程:因为内容过多分页导致的这条线
  14. <郝斌C语言自学教程>
  15. 基于Java的奖学金评定管理系统
  16. oracle11g ins208022,解决重装 Oracle 出现的 INS-32025 问题,完全卸载 Oracle11g
  17. NRC词语情绪词典和词语色彩词典
  18. 缓存通俗解释_在超市购买牛奶解释了网络缓存
  19. java 爬取快递100 快递信息
  20. Linux小技巧--提高cpu使用率

热门文章

  1. android4.04版本微信,微信旧版本6.3.27v6.3.27 老版本 Android
  2. 三维家可以导入别人的方案吗_酷家乐怎么用别人的模型(如何从酷家乐软件做的方案导入到另一个酷家乐账号上)...
  3. 散热风扇是吹风还是吸风,配电柜电气柜机柜散热风扇的原理。
  4. 积分墙为什么要做反作弊
  5. R语言TCGA数据下载及处理biolinks包的学习与使用(一)数据下载
  6. Linux为硬盘重建MBR,linux重建mbr
  7. 股权转让要交哪些税?增值税、企业所得税、个人所得税
  8. 第三方服务挂了,如何保证服务不受影响?
  9. paypal如何支付欧元_涨姿势!Paypal怎么用?
  10. 背景色及色彩搭配方案推荐