Skip to content

Commit 5c84218

Browse files
[SYCL][Bindless][Exp] Allow read_image/mipmap to return user types (#11961)
- `read_image` and `read_mipmap` now allow user-defined types to be returned. - The `write_image` test case for user-defined types is updated to include both reading and writing of user-defined types. - An additional test is added for reading user-defined types where the image type is a mipmap. - The extension document has been updated to reflect the above changes. - The `write_image` function now checks whether a user-defined type was passed to it, and converts the input data accordingly. If the type passed has a specific NVPTX backend intrinsic, it will use that, otherwise it will write the user data type bit-cast to a variant of unsigned integer. - This patch marks the bump to revision `5.0` of the bindless images specification document.
1 parent 96ce6ea commit 5c84218

File tree

6 files changed

+777
-125
lines changed

6 files changed

+777
-125
lines changed

sycl/doc/extensions/experimental/sycl_ext_oneapi_bindless_images.asciidoc

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -962,10 +962,10 @@ listed above caused the failure.
962962
```cpp
963963
namespace sycl::ext::oneapi::experimental {
964964

965-
template <typename DataT, typename CoordT>
965+
template <typename DataT, typename HintT = DataT, typename CoordT>
966966
DataT read_image(const unsampled_image_handle &ImageHandle,
967967
const CoordT &Coords);
968-
template <typename DataT, typename CoordT>
968+
template <typename DataT, typename HintT = DataT, typename CoordT>
969969
DataT read_image(const sampled_image_handle &ImageHandle,
970970
const CoordT &Coords);
971971

@@ -981,26 +981,28 @@ will be fetched exactly as is in device memory. For the form that takes a
981981
`sampled_image_handle`, the image will be sampled according to the
982982
`bindless_image_sampler` that was passed to the image upon construction.
983983

984-
The returned data will be of templated type `DataT`, which is specified by the
985-
user, and should map to the type that the image was created with (a combination
986-
of `image_channel_type` and `image_channel_order`). One exception to this are
987-
normalized integer channel types which are read back as either 32-bit or 16-bit
988-
floating point values.
989-
990-
For multi-channel types, the resultant `DataT` should be a `sycl::vec` type.
991-
E.g., for a channel order of `image_channel_order::rg` and channel type of
992-
`image_channel_type::fp16`, the resultant `DataT` should be
993-
`sycl::vec<sycl::half, 2>`.
994-
995-
An example of reading a normalized channel type with a channel order of
996-
`image_channel_order::rg` and channel type of `image_channel_type::unorm_int8`,
997-
the user can read the data as a `sycl::vec<float, 2>` or
998-
`sycl::vec<sycl::half, 2>`.
984+
The user is required to pass a `DataT` template parameter, which specifies the
985+
return type of the `read_image` function. If `DataT` is not a recognized
986+
standard type, as defined in <<recognized_standard_types>>, and instead a
987+
user-defined type, the user must provide a `HintT` template parameter to the
988+
`read_image` function, to allow the backend to select the correct device
989+
intrinsic to fetch or sample their data.
990+
`HintT` must be one of the the <<recognized_standard_types>>, and must be the
991+
same size as `DataT`.
992+
If `DataT` is a recognized standard type, and `HintT` is also passed, `HintT`
993+
will be ignored.
994+
995+
When reading a texture backed by a normalized integer channel type, either
996+
`DataT` must be a 32-bit or 16-bit floating point value, a `sycl::vec` of
997+
32-bit or 16-bit floating point values, or, in the case `DataT` is not one of
998+
the above, then `HintT` must be one of the above, and be of the same size as
999+
`DataT`.
9991000

10001001
It's possible to write to an unsampled image via `write_image` passing the
10011002
handle of the image to be written to, along with the coordinates to write to and
10021003
the data. User-defined types are allowed to be written provided that type is
1003-
trivially copyable.
1004+
trivially copyable. The user defined type must also be of the same size as any
1005+
of the <<recognized_standard_types>>.
10041006

10051007
Sampled images cannot be written to using `write_image`.
10061008

@@ -1029,6 +1031,35 @@ Attempting to read an image with `read_mipmap` or any other defined read
10291031
function will result in undefined behaviour.
10301032
====
10311033

1034+
=== Recognized standard types [[recognized_standard_types]]
1035+
1036+
For the purposes of this extension, the following are classified as recognized
1037+
standard types.
1038+
1039+
* All POD types (`char`, `short`, `int`, `float`, etc.) excluding `double`
1040+
* `sycl::half`
1041+
* Variants of `sycl::vec<T, N>` where `T` is one of the above, and `N` is `1`, `2`, or `4`
1042+
1043+
Any other types are classified as user-defined types.
1044+
1045+
==== User-defined types
1046+
1047+
Some examples of a user-defined types may be:
1048+
1049+
```c++
1050+
struct my_float4 {
1051+
float r, g, b, a;
1052+
};
1053+
1054+
struct my_short2 {
1055+
short r, g;
1056+
};
1057+
```
1058+
1059+
When providing the above types as `DataT` parameters to an image read function,
1060+
the corresponding `HintT` parameters to use would be `sycl::vec<float, 4>` and
1061+
`sycl::vec<short, 2>`, respectively.
1062+
10321063
== Mipmapped images
10331064

10341065
So far, we have described how to create and operate on standard bindless images.
@@ -1128,13 +1159,13 @@ mipmap.
11281159

11291160
```c++
11301161
// Nearest/linear filtering between mip levels
1131-
template <typename DataT, typename CoordT>
1162+
template <typename DataT, typename HintT = DataT, typename CoordT>
11321163
DataT read_mipmap(const sampled_image_handle &ImageHandle,
11331164
const CoordT &Coords,
11341165
const float Level);
11351166

11361167
// Anisotropic filtering
1137-
template <typename DataT, typename CoordT>
1168+
template <typename DataT, typename HintT = DataT, typename CoordT>
11381169
DataT read_mipmap(const sampled_image_handle &ImageHandle,
11391170
const CoordT &Coords,
11401171
const CoordT &Dx, const CoordT &Dy);
@@ -1144,6 +1175,9 @@ Reading a mipmap follows the same restrictions on what coordinate types may be
11441175
used as laid out in <<reading_writing_inside_kernel>>, and the viewing gradients
11451176
are bound to the same type as used for the coordinates.
11461177

1178+
Reading a mipmap by providing a user-defined return `DataT` type also follows
1179+
the restrictions as laid out in <<reading_writing_inside_kernel>>.
1180+
11471181
[NOTE]
11481182
====
11491183
Attempting to read a mipmap with `read_image` or any other defined read function
@@ -2017,4 +2051,8 @@ These features still need to be handled:
20172051
whether a `raw_sampler_handle` member is necessary.
20182052
- Renamed `image_handle` members in `sampled_image_handle` and
20192053
`unsampled_image_handle` structs to `raw_handle`.
2054+
|5.0|2023-11-21| - Added section "Recognized standard types", to simplify
2055+
wording around what types are allowed to be read or written.
2056+
- Allow `read_image` and `read_mipmap` to return a
2057+
user-defined type.
20202058
|======================

sycl/include/sycl/ext/oneapi/bindless_images.hpp

Lines changed: 118 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -742,12 +742,28 @@ template <typename CoordT> constexpr void assert_sampled_coords() {
742742
"Expected float coordinates data type");
743743
}
744744
}
745+
746+
template <typename DataT> constexpr bool is_data_size_valid() {
747+
return (sizeof(DataT) == 1) || (sizeof(DataT) == 2) || (sizeof(DataT) == 4) ||
748+
(sizeof(DataT) == 8) || (sizeof(DataT) == 16);
749+
}
750+
751+
template <typename DataT> constexpr bool is_recognized_standard_type() {
752+
return is_data_size_valid<DataT>() &&
753+
(is_vec_v<DataT> || std::is_scalar_v<DataT> ||
754+
std::is_floating_point_v<DataT> || std::is_same_v<DataT, sycl::half>);
755+
}
756+
745757
} // namespace detail
746758

747759
/**
748760
* @brief Read an unsampled image using its handle
749761
*
750762
* @tparam DataT The return type
763+
* @tparam HintT A hint type that can be used to select for a specialized
764+
* backend intrinsic when a user-defined type is passed as `DataT`.
765+
* HintT should be a `sycl::vec` type, `sycl::half` type, or POD type.
766+
* HintT must also have the same size as DataT.
751767
* @tparam CoordT The input coordinate type. e.g. int, int2, or int4 for
752768
* 1D, 2D, and 3D, respectively
753769
* @param imageHandle The image handle
@@ -760,7 +776,7 @@ template <typename CoordT> constexpr void assert_sampled_coords() {
760776
* The name mangling should therefore not interfere with one
761777
* another
762778
*/
763-
template <typename DataT, typename CoordT>
779+
template <typename DataT, typename HintT = DataT, typename CoordT>
764780
DataT read_image(const unsampled_image_handle &imageHandle [[maybe_unused]],
765781
const CoordT &coords [[maybe_unused]]) {
766782
detail::assert_unsampled_coords<CoordT>();
@@ -770,7 +786,17 @@ DataT read_image(const unsampled_image_handle &imageHandle [[maybe_unused]],
770786
"for 1D, 2D and 3D images, respectively.");
771787

772788
#ifdef __SYCL_DEVICE_ONLY__
773-
return __invoke__ImageRead<DataT>(imageHandle.raw_handle, coords);
789+
if constexpr (detail::is_recognized_standard_type<DataT>()) {
790+
return __invoke__ImageRead<DataT>(imageHandle.raw_handle, coords);
791+
} else {
792+
static_assert(sizeof(HintT) == sizeof(DataT),
793+
"When trying to read a user-defined type, HintT must be of "
794+
"the same size as the user-defined DataT.");
795+
static_assert(detail::is_recognized_standard_type<HintT>(),
796+
"HintT must always be a recognized standard type");
797+
return sycl::bit_cast<DataT>(
798+
__invoke__ImageRead<HintT>(imageHandle.raw_handle, coords));
799+
}
774800
#else
775801
assert(false); // Bindless images not yet implemented on host
776802
#endif
@@ -780,6 +806,10 @@ DataT read_image(const unsampled_image_handle &imageHandle [[maybe_unused]],
780806
* @brief Read a sampled image using its handle
781807
*
782808
* @tparam DataT The return type
809+
* @tparam HintT A hint type that can be used to select for a specialized
810+
* backend intrinsic when a user-defined type is passed as `DataT`.
811+
* HintT should be a `sycl::vec` type, `sycl::half` type, or POD type.
812+
* HintT must also have the same size as DataT.
783813
* @tparam CoordT The input coordinate type. e.g. float, float2, or float4 for
784814
* 1D, 2D, and 3D, respectively
785815
* @param imageHandle The image handle
@@ -792,7 +822,7 @@ DataT read_image(const unsampled_image_handle &imageHandle [[maybe_unused]],
792822
* The name mangling should therefore not interfere with one
793823
* another
794824
*/
795-
template <typename DataT, typename CoordT>
825+
template <typename DataT, typename HintT = DataT, typename CoordT>
796826
DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
797827
const CoordT &coords [[maybe_unused]]) {
798828
detail::assert_sampled_coords<CoordT>();
@@ -802,7 +832,17 @@ DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
802832
"for 1D, 2D and 3D images, respectively.");
803833

804834
#ifdef __SYCL_DEVICE_ONLY__
805-
return __invoke__ImageRead<DataT>(imageHandle.raw_handle, coords);
835+
if constexpr (detail::is_recognized_standard_type<DataT>()) {
836+
return __invoke__ImageRead<DataT>(imageHandle.raw_handle, coords);
837+
} else {
838+
static_assert(sizeof(HintT) == sizeof(DataT),
839+
"When trying to read a user-defined type, HintT must be of "
840+
"the same size as the user-defined DataT.");
841+
static_assert(detail::is_recognized_standard_type<HintT>(),
842+
"HintT must always be a recognized standard type");
843+
return sycl::bit_cast<DataT>(
844+
__invoke__ImageRead<HintT>(imageHandle.raw_handle, coords));
845+
}
806846
#else
807847
assert(false); // Bindless images not yet implemented on host.
808848
#endif
@@ -812,14 +852,18 @@ DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
812852
* @brief Read a mipmap image using its handle with LOD filtering
813853
*
814854
* @tparam DataT The return type
855+
* @tparam HintT A hint type that can be used to select for a specialized
856+
* backend intrinsic when a user-defined type is passed as `DataT`.
857+
* HintT should be a `sycl::vec` type, `sycl::half` type, or POD type.
858+
* HintT must also have the same size as DataT.
815859
* @tparam CoordT The input coordinate type. e.g. float, float2, or float4 for
816860
* 1D, 2D, and 3D, respectively
817861
* @param imageHandle The mipmap image handle
818862
* @param coords The coordinates at which to fetch mipmap image data
819863
* @param level The mipmap level at which to sample
820864
* @return Mipmap image data with LOD filtering
821865
*/
822-
template <typename DataT, typename CoordT>
866+
template <typename DataT, typename HintT = DataT, typename CoordT>
823867
DataT read_mipmap(const sampled_image_handle &imageHandle [[maybe_unused]],
824868
const CoordT &coords [[maybe_unused]],
825869
const float level [[maybe_unused]]) {
@@ -830,7 +874,17 @@ DataT read_mipmap(const sampled_image_handle &imageHandle [[maybe_unused]],
830874
"for 1D, 2D and 3D images, respectively.");
831875

832876
#ifdef __SYCL_DEVICE_ONLY__
833-
return __invoke__ImageReadLod<DataT>(imageHandle.raw_handle, coords, level);
877+
if constexpr (detail::is_recognized_standard_type<DataT>()) {
878+
return __invoke__ImageReadLod<DataT>(imageHandle.raw_handle, coords, level);
879+
} else {
880+
static_assert(sizeof(HintT) == sizeof(DataT),
881+
"When trying to read a user-defined type, HintT must be of "
882+
"the same size as the user-defined DataT.");
883+
static_assert(detail::is_recognized_standard_type<HintT>(),
884+
"HintT must always be a recognized standard type");
885+
return sycl::bit_cast<DataT>(
886+
__invoke__ImageReadLod<HintT>(imageHandle.raw_handle, coords, level));
887+
}
834888
#else
835889
assert(false); // Bindless images not yet implemented on host
836890
#endif
@@ -840,6 +894,10 @@ DataT read_mipmap(const sampled_image_handle &imageHandle [[maybe_unused]],
840894
* @brief Read a mipmap image using its handle with anisotropic filtering
841895
*
842896
* @tparam DataT The return type
897+
* @tparam HintT A hint type that can be used to select for a specialized
898+
* backend intrinsic when a user-defined type is passed as `DataT`.
899+
* HintT should be a `sycl::vec` type, `sycl::half` type, or POD type.
900+
* HintT must also have the same size as DataT.
843901
* @tparam CoordT The input coordinate type. e.g. float, float2, or float4 for
844902
* 1D, 2D, and 3D, respectively
845903
* @param imageHandle The mipmap image handle
@@ -848,7 +906,7 @@ DataT read_mipmap(const sampled_image_handle &imageHandle [[maybe_unused]],
848906
* @param dY Screen space gradient in the y dimension
849907
* @return Mipmap image data with anisotropic filtering
850908
*/
851-
template <typename DataT, typename CoordT>
909+
template <typename DataT, typename HintT = DataT, typename CoordT>
852910
DataT read_mipmap(const sampled_image_handle &imageHandle [[maybe_unused]],
853911
const CoordT &coords [[maybe_unused]],
854912
const CoordT &dX [[maybe_unused]],
@@ -860,7 +918,18 @@ DataT read_mipmap(const sampled_image_handle &imageHandle [[maybe_unused]],
860918
"components for 1D, 2D, and 3D images, respectively.");
861919

862920
#ifdef __SYCL_DEVICE_ONLY__
863-
return __invoke__ImageReadGrad<DataT>(imageHandle.raw_handle, coords, dX, dY);
921+
if constexpr (detail::is_recognized_standard_type<DataT>()) {
922+
return __invoke__ImageReadGrad<DataT>(imageHandle.raw_handle, coords, dX,
923+
dY);
924+
} else {
925+
static_assert(sizeof(HintT) == sizeof(DataT),
926+
"When trying to read a user-defined type, HintT must be of "
927+
"the same size as the user-defined DataT.");
928+
static_assert(detail::is_recognized_standard_type<HintT>(),
929+
"HintT must always be a recognized standard type");
930+
return sycl::bit_cast<DataT>(
931+
__invoke__ImageReadGrad<HintT>(imageHandle.raw_handle, coords, dX, dY));
932+
}
864933
#else
865934
assert(false); // Bindless images not yet implemented on host
866935
#endif
@@ -871,14 +940,18 @@ DataT read_mipmap(const sampled_image_handle &imageHandle [[maybe_unused]],
871940
* filtering
872941
*
873942
* @tparam DataT The return type
943+
* @tparam HintT A hint type that can be used to select for a specialized
944+
* backend intrinsic when a user-defined type is passed as `DataT`.
945+
* HintT should be a `sycl::vec` type, `sycl::half` type, or POD type.
946+
* HintT must also have the same size as DataT.
874947
* @tparam CoordT The input coordinate type. e.g. float, float2, or float4 for
875948
* 1D, 2D, and 3D, respectively
876949
* @param imageHandle The mipmap image handle
877950
* @param coords The coordinates at which to fetch mipmap image data
878951
* @param level The mipmap level at which to sample
879952
* @return Mipmap image data with LOD filtering
880953
*/
881-
template <typename DataT, typename CoordT>
954+
template <typename DataT, typename HintT = DataT, typename CoordT>
882955
__SYCL_DEPRECATED("read_image for mipmaps is deprecated. "
883956
"Instead use read_mipmap.")
884957
DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
@@ -891,7 +964,17 @@ DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
891964
"for 1D, 2D and 3D images, respectively.");
892965

893966
#ifdef __SYCL_DEVICE_ONLY__
894-
return __invoke__ImageReadLod<DataT>(imageHandle.raw_handle, coords, level);
967+
if constexpr (detail::is_recognized_standard_type<DataT>()) {
968+
return __invoke__ImageReadLod<DataT>(imageHandle.raw_handle, coords, level);
969+
} else {
970+
static_assert(sizeof(HintT) == sizeof(DataT),
971+
"When trying to read a user-defined type, HintT must be of "
972+
"the same size as the user-defined DataT.");
973+
static_assert(detail::is_recognized_standard_type<HintT>(),
974+
"HintT must always be a recognized standard type");
975+
return sycl::bit_cast<DataT>(
976+
__invoke__ImageReadLod<HintT>(imageHandle.raw_handle, coords, level));
977+
}
895978
#else
896979
assert(false); // Bindless images not yet implemented on host
897980
#endif
@@ -902,6 +985,10 @@ DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
902985
* filtering
903986
*
904987
* @tparam DataT The return type
988+
* @tparam HintT A hint type that can be used to select for a specialized
989+
* backend intrinsic when a user-defined type is passed as `DataT`.
990+
* HintT should be a `sycl::vec` type, `sycl::half` type, or POD type.
991+
* HintT must also have the same size as DataT.
905992
* @tparam CoordT The input coordinate type. e.g. float, float2, or float4 for
906993
* 1D, 2D, and 3D, respectively
907994
* @param imageHandle The mipmap image handle
@@ -910,7 +997,7 @@ DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
910997
* @param dY Screen space gradient in the y dimension
911998
* @return Mipmap image data with anisotropic filtering
912999
*/
913-
template <typename DataT, typename CoordT>
1000+
template <typename DataT, typename HintT = DataT, typename CoordT>
9141001
__SYCL_DEPRECATED("read_image for mipmaps is deprecated. "
9151002
"Instead use read_mipmap.")
9161003
DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
@@ -924,7 +1011,18 @@ DataT read_image(const sampled_image_handle &imageHandle [[maybe_unused]],
9241011
"components for 1D, 2D, and 3D images, respectively.");
9251012

9261013
#ifdef __SYCL_DEVICE_ONLY__
927-
return __invoke__ImageReadGrad<DataT>(imageHandle.raw_handle, coords, dX, dY);
1014+
if constexpr (detail::is_recognized_standard_type<DataT>()) {
1015+
return __invoke__ImageReadGrad<DataT>(imageHandle.raw_handle, coords, dX,
1016+
dY);
1017+
} else {
1018+
static_assert(sizeof(HintT) == sizeof(DataT),
1019+
"When trying to read a user-defined type, HintT must be of "
1020+
"the same size as the user-defined DataT.");
1021+
static_assert(detail::is_recognized_standard_type<HintT>(),
1022+
"HintT must always be a recognized standard type");
1023+
return sycl::bit_cast<DataT>(
1024+
__invoke__ImageReadGrad<HintT>(imageHandle.raw_handle, coords, dX, dY));
1025+
}
9281026
#else
9291027
assert(false); // Bindless images not yet implemented on host
9301028
#endif
@@ -951,8 +1049,14 @@ void write_image(const unsampled_image_handle &imageHandle [[maybe_unused]],
9511049

9521050
#ifdef __SYCL_DEVICE_ONLY__
9531051
#if defined(__NVPTX__)
954-
__invoke__ImageWrite((uint64_t)imageHandle.raw_handle, coords,
955-
detail::convert_color_nvptx(color));
1052+
if constexpr (detail::is_recognized_standard_type<DataT>()) {
1053+
__invoke__ImageWrite((uint64_t)imageHandle.raw_handle, coords, color);
1054+
} else {
1055+
// Convert DataT to a supported backend write type when user-defined type is
1056+
// passed
1057+
__invoke__ImageWrite((uint64_t)imageHandle.raw_handle, coords,
1058+
detail::convert_color_nvptx(color));
1059+
}
9561060
#else
9571061
__invoke__ImageWrite((uint64_t)imageHandle.raw_handle, coords, color);
9581062
#endif

0 commit comments

Comments
 (0)