介紹OpenCL與D3D 10之間的互操作。 1.初始化OpenCL上下文實現Direct3D互操作 OpenCL共用由pragma cl_khr_d3d10_sharing啟用: #pragma OPENCL EXTENSION cl_khr_d3d10_sharing: enable 啟用D3D ...
介紹OpenCL與D3D 10之間的互操作。
1.初始化OpenCL上下文實現Direct3D互操作
OpenCL共用由pragma cl_khr_d3d10_sharing啟用:
#pragma OPENCL EXTENSION cl_khr_d3d10_sharing: enable
啟用D3D共用時,很多OpenCL函數會有所擴展,將接受一些處理D3D10共用的參數類型和值。
可以用D3D互操作屬性來創建OpenCL上下文:
·CL_CONTEXT_D3D10_DEVICE_KHR 在clCreateContext和clCreateContextFromtype的屬性參數中作為一個屬性名。
函數可以查詢D3D互操作特定的對象參數:
·CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 作為clGetContextInfo的param_name參數值。
·CL_MEM_D3D10_RESOURCE_KHR 作為clGetMemObjectInfo的param_name參數值。
·CL_IMAGE_D3D10_SUBRESOURCE_KHR 作為clGetImageInfo的param_name參數值。
·CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 和 CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 當param_name為CL_ENCENT_COMMAND_TYPE時,在clGetEventInfo的參數param_value中返回。
OpenCL D3D10互操作函數在頭文件cl_d3d10.h中。D3D10的Khronos擴展可以從Khronos網站得到。對於某些發佈版本,可能需要下載這個擴展。
初始化OpenCL的過程與平常基本相同,只有幾點細小差別。首先平臺可以使用clGetPlatformIDs函數列出。由於我們在搜索一個支持D3D共用的平臺,要在各個平臺上使用clGetPlatformInfo()調用來查詢它支持的擴展。如果擴展串中包含cl_khr_d3d10_sharing,說明可以選用這個平臺來實現D3D共用。
給定一個支持D3D共用的cl_platform_id,可以在這個平臺上使用clGetDeviceIDsFromD3D10KHR()查詢相應的OpenCL設備ID:
cl_int clGetDeviceIDsFromD3D10KHR( cl_platform_id platform, cl_d3d10_device_source_khr d3d_device_source, void * d3d_object, cl_d3d10_device_set_khr d3d_device_set, cl_uint num_entries, cl_device_id * devices, cl_uint * num_devices)
例如:
errNum = clGetDeviceIDsFromD3D10KHR( platformIds[index_platform], CL_D3D10_DEVICE_KHR, g_pD3DDevice, CL_PREFERRED_DEVICES_FOR_D3D10_KHR, 1, &cdDevice, &num_devices); if (errNum == CL_INVALID_PLATFORM) { printf("Invalid Platform: Specified platform is not valid\n"); } else if( errNum == CL_INVALID_VALUE) { printf("Invalid Value: d3d_device_source, d3d_device_set is not valid or num_entries = 0 and devices != NULL or num_devices == devices == NULL\n"); } else if( errNum == CL_DEVICE_NOT_FOUND) { printf("No OpenCL devices corresponding to the d3d_object were found\n"); }
代碼為選擇的OpenCL平臺(platformIds[index_platform])獲取一個OpenCL設備ID(cdDevice)。常量CL_D3D10_DEVICE_KHR指示發送的D3D10對象(g_pD3DDevice)是一個D3D10設備,通過CL_PREFERRED_DEVICES_FOR_D3D10_KHR來選擇該平臺的期望設備。這會返回與平臺和D3D10設備關聯的期望OpenCL設備。
這個函數返回的設備ID可以用來創建一個支持D3D共用的上下文。創建OpenCL上下文時,clCreateContext*()調用中的cl_context_properties域應當包括要共用的D3D10設備的指針。例如:
cl_context_properties contextProperties[] = { CL_CONTEXT_D3D10_DEVICE_KHR, (cl_context_properties)g_pD3DDevice, CL_CONTEXT_PLATFORM, (cl_context_properties)platformIds[index_platform], 0 }; context = clCreateContextFromType( contextProperties, CL_DEVICE_TYPE_GPU, NULL, NULL, &errNum ) ;
這個示例代碼中,會從D3D10CreateDeviceAndSwapChain()調用返回D3D10設備g_pD3DDevice的指針。
2.從D3D緩衝區和紋理創建OpenCL記憶體對象
可以使用clCreateFromD3D10*KHR() OpenCL函數由現有的D3D緩衝區對象和紋理創建OpenCL緩衝區和圖像對象。
可以使用clCreateFromD3D10BufferKHR()由現有的D3D緩衝區創建OpenCL記憶體對象:
cl_mem clCreateFromD3D10BufferKHR( cl_context context, cl_mem_flags flags, ID3D10Buffer * resource, cl_int * errcode_ret)
所返回的OpenCL緩衝區對象的大小與resource的大小相同。這個調用將使resource上的內部Direct3D引用計數增1.所返回OpenCL記憶體對象上的OpenCL引用計數減至0時,resource上的內部Direct3D引用計數會減1.
緩衝區與紋理都可以與OpenCL共用。
在D3D10中,紋理可以如下創建:
// 2D texture D3D10_TEXTURE2D_DESC desc; ZeroMemory( &desc, sizeof(D3D10_TEXTURE2D_DESC) ); desc.Width = g_WindowWidth; desc.Height = g_WindowHeight; desc.MipLevels = 1; desc.ArraySize = 1; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1; desc.Usage = D3D10_USAGE_DEFAULT; desc.BindFlags = D3D10_BIND_SHADER_RESOURCE; if (FAILED(g_pD3DDevice->CreateTexture2D( &desc, NULL, &g_pTexture2D))) return E_FAIL;
這個共用的紋理格式為DXGI_FORMAT_R8G8B8A8_UNORM。然後可以使用
cl_mem clCreateFromD3D10Texture2DKHR( cl_context context, cl_mem_flags flags, ID3D10Texture2D * resource, UINT subresource, cl_int * errcode_ret)
創建一個OpenCL圖像對象。所返回的OpenCL圖像對象的寬度、高度和深度由resource得子資源subresource的寬度、高度、深度決定。所返回的OpenCL圖像對象的通道類型和次序由resource的格式確定。
這個調用將使resource上的內部Direct3D引用計數增1.所返回的OpenCL記憶體對象上的OpenCL引用計數減至0時,resource上的內部Direct3D引用計數減1.
類似有3D的,
cl_mem clCreateFromD3D10Texture3DKHR( cl_context context, cl_mem_flags flags, ID3D10Texture3D * resource, UINT subresource, cl_int * errcode_ret)
3.OpenCL中獲取和釋放Direct3D對象
在opencl中處理之前必須先獲取direct3d對象,在由direct3d使用之前必須先釋放direct3d對象。
cl_int clEnqueueAcquireD3D10ObjectsKHR( cl_command_queue command_queue, cl_uint num_objects, const cl_mem * mem_objects, cl_uint num_events_in_wait_list, const cl_event * event_wait_list, cl_event * event)
這會獲得由D3D10資源創建的的OpenCL記憶體對象。
cl_int clEnqueueAcquireD3D10ObjectsKHR( cl_command_queue command_queue, cl_uint num_objects, const cl_mem * mem_objects, cl_uint num_events_in_wait_list, const cl_event * event_wait_list, cl_event * event)
這會獲得由Direct3D 10資源創建OpenCL記憶體對象。clEnqueueAcquireD3D10ObjectsKHR()提供了同步保證,在調用clEnqueueAcquireD3D10ObjectsKHR()之前做出的所有D3D 10調用都必須先完全執行,之後event才能報告完成,command_queue中的所有後續OpenCL工作才能開始執行。
釋放函數為:
cl_int clEnqueueReleaseD3D10ObjectsKHR( cl_command_queue command_queue, cl_uint num_objects, const cl_mem * mem_objects, cl_uint num_events_in_wait_list, const cl_event * event_wait_list, cl_event * event)
這會獲得由Direct3D 10資源創建OpenCL記憶體對象。clEnqueueReleaseD3D10ObjectsKHR()提供了同步保證,在調用clEnqueueReleaseD3D10ObjectsKHR()之後做出的所有D3D 10調用不會立即開始執行,直到event_wait_list中所有事件都已經完成,而且提交到command_queue中的所有工作都已經完成執行之後這些D3D 10調用才會開始。
另外,與D3D10不同,OpenGL獲取函數不會提供同步保證。另外,獲取和釋放紋理時,最高效的做法是同時獲取和釋放所有共用的紋理和資源。另外,最好在切換回D3D處理之前處理完所有opencl內核。採用這種方式,獲取和釋放調用可以用來構成opencl和D3D處理的邊界。
4.OpenCL中處理D3D紋理
opencl修改紋理內容:
cl_int computeTexture() { cl_int errNum; static cl_int seq =0; seq = (seq+1)%(g_WindowWidth*2); errNum = clSetKernelArg(tex_kernel, 0, sizeof(cl_mem), &g_clTexture2D); errNum = clSetKernelArg(tex_kernel, 1, sizeof(cl_int), &g_WindowWidth); errNum = clSetKernelArg(tex_kernel, 2, sizeof(cl_int), &g_WindowHeight); errNum = clSetKernelArg(tex_kernel, 3, sizeof(cl_int), &seq); size_t tex_globalWorkSize[2] = { g_WindowWidth, g_WindowHeight }; size_t tex_localWorkSize[2] = { 32, 4 } ; errNum = clEnqueueAcquireD3D10ObjectsKHR(commandQueue, 1, &g_clTexture2D, 0, NULL, NULL ); errNum = clEnqueueNDRangeKernel(commandQueue, tex_kernel, 2, NULL, tex_globalWorkSize, tex_localWorkSize, 0, NULL, NULL); if (errNum != CL_SUCCESS) { std::cerr << "Error queuing kernel for execution." << std::endl; } errNum = clEnqueueReleaseD3D10ObjectsKHR(commandQueue, 1, &g_clTexture2D, 0, NULL, NULL ); clFinish(commandQueue); return 0; }
用opencl內核計算生成一個D3D紋理對象的內容:
__kernel void xyz_init_texture_kernel(__write_only image2d_t im, int w, int h, int seq ) { int2 coord = { get_global_id(0), get_global_id(1) }; float4 color = { (float)coord.x/(float)w, (float)coord.y/(float)h, (float)abs(seq-w)/(float)w, 1.0f}; write_imagef( im, coord, color ); }
這個紋理使用write_imagef()函數寫至內核。這裡seq是一個序列號變數,在宿主機上每一幀會迴圈遞增,併發送至內核。在內核中,seq變數用於生成紋理顏色值。seq遞增時,顏色會改變來實現紋理動畫。
另外,代碼中使用了一種渲染技術g_pTechnique。這是一個基本處理管線,會用到一個簡單的頂點著色器,將頂點和紋理坐標傳遞到一個像素著色器:
// // Vertex Shader // PS_INPUT VS( VS_INPUT input ) { PS_INPUT output = (PS_INPUT)0; output.Pos = input.Pos; output.Tex = input.Tex; return output; } technique10 Render { pass P0 { SetVertexShader( CompileShader( vs_4_0, VS() ) ); SetGeometryShader( NULL ); SetPixelShader( CompileShader( ps_4_0, PS() ) ); } }
這個技術使用常規的D3D10調用載入。像素著色器再對OpenCL內核修改的紋理完成紋理查找,比提供顯示:
SamplerState samLinear { Filter = MIN_MAG_MIP_LINEAR; AddressU = Wrap; AddressV = Wrap; }; float4 PS( PS_INPUT input) : SV_Target { return txDiffuse.Sample( samLinear, input.Tex ); }
在像素著色器中,samLinear是輸入紋理的一個線性採樣器。對於渲染迴圈的每次迭代,OpenCL在computeTexture()中更新紋理內容,有D3D10顯示更新的紋理。
5.OpenCL中處理D3D頂點數據
現考慮 使用一個包含頂點數據的D3D緩衝區在屏幕上繪製一個正弦曲線。首先為D3D中的頂點緩衝區定義一個簡單的結構:
struct SimpleSineVertex { D3DXVECTOR4 Pos; };
可以為這個結構創建一個D3D10緩衝區,這裡緩衝區中包含256個元素:
bd.Usage = D3D10_USAGE_DEFAULT; bd.ByteWidth = sizeof( SimpleSineVertex ) * 256; bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; bd.MiscFlags = 0; hr = g_pD3DDevice->CreateBuffer( &bd, NULL, &g_pSineVertexBuffer );
因為要使用OpenCL設置緩衝區中的數據,所以為第二個參數pInitialData傳入NULL,只分配空間。一旦創建了D3D緩衝區 g_pSineVertexBuffer,可以使用clCreateFromD3D10BufferKHR()函數從g_pSineVertexBuffer創建一個OpenCL緩衝區:
g_clBuffer = clCreateFromD3D10BufferKHR( context, CL_MEM_READ_WRITE, g_pSineVertexBuffer, &errNum ); if( errNum != CL_SUCCESS) { std::cerr << "Error creating buffer from D3D10" << std::endl; return E_FAIL; }
與前類似,g_clBuffer可以作為一個內核參數發送到一個生產數據的OpenCL內核。 在示例代碼中,正弦曲線的頂點位置在內核中生成:
__kernel void init_vbo_kernel(__global float4 *vbo, int w, int h, int seq) { int gid = get_global_id(0); float4 linepts; float f = 1.0f; float a = 0.4f; float b = 0.0f; linepts.x = gid/(w/2.0f)-1.0f; linepts.y = b + a*sin(3.14*2.0*((float)gid/(float)w*f + (float)seq/(float)w)); linepts.z = 0.5f; linepts.w = 0.0f; vbo[gid] = linepts; }
渲染時,設置佈局和緩衝區,並指定一個線條帶。接下來,computeBuffer()調用前面的內核更新緩衝區。激活一個簡單的渲染管線,並繪製256個數據點:
// Set the input layout g_pD3DDevice->IASetInputLayout( g_pSineVertexLayout ); // Set vertex buffer stride = sizeof( SimpleSineVertex ); offset = 0; g_pD3DDevice->IASetVertexBuffers( 0, 1, &g_pSineVertexBuffer, &stride, &offset ); // Set primitive topology g_pD3DDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP ); computeBuffer(); g_pTechnique->GetPassByIndex( 1 )->Apply( 0 ); g_pD3DDevice->Draw( 256, 0 );
運行時,程式會應用這個內核生成紋理內容,然後運行D3D管線對紋理採樣,併在屏幕上顯示。然後還會繪製頂點緩衝區,在屏幕上得到一個正弦曲線。
示例工程源碼:http://download.csdn.net/download/qq_33892166/9867159