SYCL中swizzle操作无法自动转vector适配内置函数的解决方法
问题复现
在parallel_for作用域下开发SYCL应用时,编写如下队列提交代码:
queue.submit( [&map, &output](cl::sycl::handler& cgh) { auto d_image = output.get_access<access::mode::read_write>(cgh); auto d_map_probabilities = map.probabilities.get_access<access::mode::read>(cgh); cgh.parallel_for<class CreateImage>( map.probabilities.get_range(), [=](id<3> work_position) { // ... 其余业务代码 float3 relative{}; float elevation = atan2(relative.z(), length(relative.xy())) * 30.0f * M_2_PI_F; } } );
编译时会触发no matching function for call to 'length'报错,提示所有length候选模板都不匹配参数类型。
根因分析
relative.xy() swizzle操作返回的不是float2类型,而是SYCL实现的内部代理类型:
sycl::detail::SwizzleOp<sycl::vec<float, 3>, sycl::detail::GetOp<float>, sycl::detail::GetOp<float>, sycl::detail::GetOp, 0, 1>
老版本SYCL实现的内置数学函数模板没有匹配这个内部代理类型,同时该类型也没有定义到对应长度vec类型的隐式转换规则,因此无法直接传参,这和OpenCL中swizzle结果可直接传入内置函数的行为不一致。
临时规避方案
目前已知两种可以绕开编译错误的写法,但都需要额外添加显式类型转换,破坏了swizzle语法的简洁性:
- 提前赋值给对应vec类型变量再传参:
float2 rel_part = relative.xy(); float elevation = atan2(relative.z(), length(rel_part)) * 30.0f * M_2_PI_F;
- 传参时显式构造vec类型包裹swizzle结果:
float elevation = atan2(relative.z(), length(float2{relative.xy()})) * 30.0f * M_2_PI_F;
更优解决方案
优先升级SYCL编译器版本
这是最一劳永逸的解决方法。这个问题是旧版本SYCL实现的已知缺陷,目前新版Intel DPC++、Open SYCL(原hipSYCL)等主流实现都已经修复:一方面给SwizzleOp类型添加了到对应维度vec类型的隐式转换运算符,另一方面也给内置数学函数模板补充了对swizzle代理类型的直接匹配支持,升级后可以直接写length(relative.xy()),不需要任何额外转换,和OpenCL的使用体验完全一致。无法升级编译器时添加全局适配代码
如果受项目环境限制不能升级编译器,可以在全局头文件中添加一小段通用适配逻辑,一次性让所有内置函数支持swizzle类型,不需要每次调用都写显式转换。以适配length函数为例:
#include <type_traits> namespace cl::sycl { template <typename SwizzleT> std::enable_if_t<!std::is_base_of_v<vec<typename SwizzleT::element_t, SwizzleT::width>, std::remove_cvref_t<SwizzleT>>, typename SwizzleT::element_t> length(SwizzleT&& swizzle_val) { return length(typename std::remove_cvref_t<SwizzleT>::vec_t(std::forward<SwizzleT>(swizzle_val))); } }
其他内置数学函数(比如normalize、dot、distance等)都可以按照完全相同的逻辑添加适配,一次编写全局生效,不会破坏原有swizzle写法的简洁性。
内容的提问来源于stack exchange,提问作者Fantastic Mr Fox

