Buffers#
sycl::buffer#
template <typename T, int Dimensions = 1,
typename AllocatorT = sycl::buffer_allocator<std::remove_const_t<T>>>
class buffer;
Template parameters
|
Type of data in buffer. |
|
Dimensionality of data: 1, 2, or 3. |
|
Allocator for buffer data. |
The sycl::buffer class defines a shared array of one,
two or three dimensions that can be used by the SYCL kernel
and has to be accessed using accessor classes.
Buffers are templated on both the type of their data, and the number
of dimensions that the data is stored and accessed through.
A sycl::buffer does not map to only one underlying backend object,
and all SYCL backend memory objects may be temporary for use within a
command group on a specific device.
The underlying data type of a buffer T must be device copyable.
Some overloads of the sycl::buffer constructor initialize the buffer
contents by copying objects from host memory while other overloads
construct the buffer without copying objects from the host. For the
overloads that do not copy host objects, the initial state of the
objects in the buffer depends on whether T is an implicit-lifetime
type (as defined in the C++ core language).
If
Tis an implicit-lifetime type, objects of that type are implicitly created in the buffer with indeterminate values.For other types, these constructor overloads merely allocate uninitialized memory, and the application is responsible for constructing objects by calling placement-new and for destroying them later by manually calling the object's destructor.
The SYCL buffer class template provides the Common Reference Semantics.
The sycl::buffer class template takes a template parameter AllocatorT
for specifying an allocator which is used by the SYCL runtime when allocating
temporary memory on the host. If no template argument is provided,
then the default allocator for the sycl::buffer
class buffer_allocator<T> will be used.
See also
SYCL Specification Section 4.7.2
Member Types#
Type |
Description |
|---|---|
|
Type of buffer elements ( |
|
Reference type of buffer elements ( |
|
Constant reference type of buffer element ( |
|
Type of allocator for buffer data ( |
(constructors)#
Each constructor takes as the last parameter an optional
sycl::property_list to provide properties to the sycl::buffer.
Zero or more properties can be provided to the
constructed sycl::buffer via an instance of sycl::property_list.
For the overloads that do copy objects from host memory, the hostData
pointer must point to at least N bytes of memory where N is
sizeof(T) * bufferRange.size().
If N is zero, hostData is permitted to be a null pointer.
Constructors 1-2
buffer(const sycl::range<Dimensions>& bufferRange,
const sycl::property_list& propList = {});
buffer(const sycl::range<Dimensions>& bufferRange,
AllocatorT allocator,
const sycl::property_list& propList = {});
Construct a sycl::buffer instance with uninitialized memory.
The constructed sycl::buffer will use the allocator
parameter provided when allocating memory on the host.
If this parameter is not specified, the constructed
sycl::buffer will use a default constructed
AllocatorT when allocating memory on the host.
Data is not written back to the host on destruction of the buffer
unless the buffer has a valid non-null pointer specified via
the member function set_final_data().
Constructors 3-4
buffer(T* hostData,
const sycl::range<Dimensions>& bufferRange,
const sycl::property_list& propList = {});
buffer(T* hostData,
const sycl::range<Dimensions>& bufferRange,
AllocatorT allocator,
const sycl::property_list& propList = {});
Construct a sycl::buffer instance with the
hostData parameter provided.
The buffer is initialized with the memory specified by hostData,
and the buffer assumes exclusive access to this memory for the
duration of its lifetime.
The constructed sycl::buffer will use the allocator
parameter provided when allocating memory on the host.
If this parameter is not specified, the constructed
sycl::buffer will use a default constructed
AllocatorT when allocating memory on the host.
Constructors 5-6
buffer(const T* hostData,
const sycl::range<Dimensions>& bufferRange,
const sycl::property_list& propList = {});
buffer(const T* hostData,
const sycl::range<Dimensions>& bufferRange,
AllocatorT allocator,
const sycl::property_list& propList = {});
Construct a sycl::buffer instance with the hostData parameter
provided. The buffer assumes exclusive access to this memory for
the duration of its lifetime.
The constructed sycl::buffer will use the allocator
parameter provided when allocating memory on the host.
If this parameter is not specified, the constructed
sycl::buffer will use a default constructed
AllocatorT when allocating memory on the host.
The host address is const T, so the host accesses can be
read-only. However, the typename T is not const so the
device accesses can be both read and write accesses.
Since the hostData is const, this buffer is only initialized
with this memory and there is no write back after its destruction,
unless the sycl::buffer has another valid non-null final data
address specified via the member function set_final_data()
after construction of the sycl::buffer.
Constructors 7-8
template <typename Container>
buffer(Container& container,
const sycl::property_list& propList = {});
template <typename Container>
buffer(Container& container,
AllocatorT allocator,
const sycl::property_list& propList = {});
Construct a one dimensional sycl::buffer instance from the
elements starting at std::data(container) and containing
std::size(container) number of elements.
The buffer is initialized with the contents of container,
and the buffer assumes exclusive access to container for
the duration of its lifetime.
Data is written back to container before the completion of
sycl::buffer destruction if the return type of
std::data(container) is not const.
The constructed sycl::buffer will use the allocator
parameter provided when allocating memory on the host.
If this parameter is not specified, the constructed
sycl::buffer will use a default constructed
AllocatorT when allocating memory on the host.
This constructor is only defined for a buffer parameterized
with Dimensions == 1, and when std::data(container)
is convertible to T*.
Constructors 9-12
buffer(const std::shared_ptr<T>& hostData,
const sycl::range<Dimensions>& bufferRange,
const sycl::property_list& propList = {});
buffer(const std::shared_ptr<T[]>& hostData,
const sycl::range<Dimensions>& bufferRange,
const sycl::property_list& propList = {});
buffer(const std::shared_ptr<T>& hostData,
const sycl::range<Dimensions>& bufferRange,
AllocatorT allocator,
const sycl::property_list& propList = {});
buffer(const std::shared_ptr<T[]>& hostData,
const sycl::range<Dimensions>& bufferRange,
AllocatorT allocator,
const sycl::property_list& propList = {});
When hostData is not empty, construct a sycl::buffer
with the contents of its stored pointer. The buffer assumes
exclusive access to this memory for the duration of its lifetime.
The buffer also creates its own internal copy of the std::shared_ptr
that shares ownership of the hostData memory, which means the
application can safely release ownership of this std::shared_ptr
when the constructor returns.
When hostData is empty, construct a SYCL
buffer with uninitialized memory.
The constructed sycl::buffer will use the allocator
parameter provided when allocating memory on the host.
If this parameter is not specified, the constructed
sycl::buffer will use a default constructed
AllocatorT when allocating memory on the host.
Constructors 13-14
template <typename InputIterator>
buffer(InputIterator first, InputIterator last,
const sycl::property_list& propList = {});
template <typename InputIterator>
buffer(InputIterator first, InputIterator last,
AllocatorT allocator = {},
const sycl::property_list& propList = {});
Create a new allocated one dimension sycl::buffer initialized
from the given elements ranging from first up to one before last.
The data is copied to an intermediate memory position by the runtime.
Data is not written back to the same iterator set provided.
However, if the sycl::buffer has a valid non-constant iterator
specified via the member function set_final_data(),
data will be copied back to that iterator.
The constructed sycl::buffer will use the allocator
parameter provided when allocating memory on the host.
If this parameter is not specified, the constructed
sycl::buffer will use a default constructed
AllocatorT when allocating memory on the host.
Constructor 15
buffer(sycl::buffer& b,
const sycl::id<Dimensions>& baseIndex,
const sycl::range<Dimensions>& subRange)
Create a new sub-buffer without allocation to have separate accessors later.
b is the buffer with the real data, which must not
be a sub-buffer.
baseIndex specifies the origin of the sub-buffer
inside the buffer b.
subRange specifies the size of the sub-buffer.
The offset and range specified by baseIndex and subRange
together must represent a contiguous region of the
original sycl::buffer.
The origin (based on baseIndex) of the sub-buffer being
constructed must be a multiple of the memory base address
alignment of each sycl::device which accesses data from
the buffer. This value is retrievable via the sycl::device
class info query sycl::info::device::mem_base_addr_align.
Violating this requirement causes the implementation to throw
an exception with the errc::invalid error code from
the sycl::accessor constructor (if the accessor
is not a placeholder) or from sycl::handler::require()
(if the accessor is a placeholder).
If the accessor is bound to a command group with a secondary
queue, the sub-buffer's alignment must be compatible with
both the primary queue's device and the secondary queue's
device, otherwise this exception is thrown.
Template parameters
|
Type of the |
|
Type of iterator used to initialize the buffer. |
Parameters
|
sycl::range specifies the dimensions of the buffer. |
|
Allocator for the buffer data. In case this parameter
is absent, the |
|
See Buffer properties. |
|
Pointer to host memory to hold data. |
|
Beginning iterator to initialize the buffer. |
|
Ending iterator to initialize the buffer. |
|
Parent buffer used to initialize this buffer. |
|
Origin of the sub-buffer. |
|
Dimensions of the sub-buffer. |
Exceptions
errc::invalidAn exception with this error code will be thrown in the constructor 15 in such cases:
If the sum of
baseIndexandsubRangein any dimension exceeds the parent buffer (b) size (bufferRange) in that dimension.If a non-contiguous region of a buffer is requested when constructing a sub-buffer.
If
bis a sub-buffer.
Example
See Example 1.
Member functions#
get_range#
sycl::range<Dimensions> get_range() const;
Return a sycl::range object representing the size of the buffer in terms of number of elements in each dimension as passed to the constructor.
size#
size_t size() const noexcept;
Returns the total number of elements in the buffer.
Equal to get_range()[0] * ... * get_range()[Dimensions-1].
get_count#
size_t get_count() const;
Deprecated. Returns the same value as size().
byte_size#
size_t byte_size() const noexcept;
Returns the size of the buffer storage in bytes.
Equal to size()*sizeof(T).
get_size#
size_t get_size() const;
Deprecated. Returns the same value as byte_size().
get_allocator#
AllocatorT get_allocator() const
Returns the allocator provided to the buffer.
get_access#
template <sycl::access_mode Mode = sycl::access_mode::read_write,
sycl::target Targ = sycl::target::device>
sycl::accessor<T, Dimensions, Mode, Targ> get_access(sycl::handler& commandGroupHandler);
Returns a valid sycl::accessor to the buffer with the specified access mode and target in the command group buffer.
The value of target can be sycl::target::device or
sycl::target::constant_buffer.
template <sycl::access_mode Mode = sycl::access_mode::read_write,
sycl::target Targ = sycl::target::device>
sycl::accessor<T, Dimensions, Mode, Targ> get_access(sycl::handler& commandGroupHandler,
sycl::range<Dimensions> accessRange,
sycl::id<Dimensions> accessOffset = {});
Returns a valid sycl::accessor to the buffer with the specified access mode and target in the command group buffer. The accessor is a ranged accessor, where the range starts at the given offset from the beginning of the buffer.
The value of target can be sycl::target::device or
sycl::target::constant_buffer.
template <typename... Ts>
auto get_access(Ts... args);
Returns a valid sycl::accessor as if constructed via passing the
buffer and all provided arguments to the sycl::accessor constructor.
Possible implementation: return sycl::accessor{*this, args...};
Deprecated in SYCL 2020
template <sycl::access_mode Mode>
sycl::accessor<T, Dimensions, Mode, sycl::target::host_buffer> get_access();
Deprecated in SYCL 2020. Use get_host_access() instead.
Returns a valid host sycl::accessor to the buffer with the
specified access mode and target.
template <sycl::access_mode Mode>
sycl::accessor<T, Dimensions, Mode, sycl::target::host_buffer>
get_access(sycl::range<Dimensions> accessRange,
sycl::id<Dimensions> accessOffset = {});
Deprecated in SYCL 2020. Use get_host_access() instead.
Returns a valid host sycl::accessor to the buffer with the specified
access mode and target.
The accessor is a ranged accessor, where the range starts at the given
offset from the beginning of the buffer.
The value of target can only be sycl::target::host_buffer.
Template parameters
|
See sycl::access_mode. |
|
See Access targets. |
Parameters
|
Command group that uses the accessor. |
|
Dimensions of the sub-buffer that is accessed. |
|
Origin of the sub-buffer that is accessed. |
Exceptions
errc::invalidIf the sum of
accessRangeandaccessOffsetexceeds the range of the buffer in any dimension.
get_host_access#
template <typename... Ts>
auto get_host_access(Ts... args);
Returns a valid sycl::host_accessor as if constructed via passing the
buffer and all provided arguments to the sycl::host_accessor constructor.
Possible implementation: return sycl::host_accessor{*this, args...};
set_final_data#
template <typename Destination = std::nullptr_t>
void set_final_data(Destination finalData = nullptr);
The finalData points to where the outcome of all the buffer
processing is going to be copied to at destruction time, if the buffer
was involved with a write accessor
Note that a raw pointer is a special case of output iterator and thus defines the host memory to which the result is to be copied.
In the case of a weak pointer, the output is not updated if the weak pointer has expired.
If Destination is std::nullptr_t, then the copy back will not happen.
Template parameters
|
Output iterator or |
Parameters
|
Indicates where data is copied at destruction time. |
set_write_back#
void set_write_back(bool flag = true);
This member function allows dynamically forcing or canceling the
write-back of the data of a buffer on destruction according to
the value of flag.
Forcing the write-back is similar to what happens during a normal write-back.
If there is nowhere to write-back, using this function does not have any effect.
is_sub_buffer#
bool is_sub_buffer() const;
Returns true if this sycl::buffer is a sub-buffer,
otherwise returns false.
reinterpret#
A sycl::buffer can construct an instance of a sycl::buffer
that reinterprets the original sycl::buffer with a different
type, dimensionality and range using the member function reinterpret.
template <typename ReinterpretT, int ReinterpretDim>
sycl::buffer<ReinterpretT, ReinterpretDim,
typename std::allocator_traits<AllocatorT>::template rebind_alloc<
std::remove_const_t<ReinterpretT>>>
reinterpret(sycl::range<ReinterpretDim> reinterpretRange) const;
Creates and returns a reinterpreted sycl::buffer with the
type specified by ReinterpretT, dimensions specified by
ReinterpretDim and range specified by reinterpretRange.
The buffer object being reinterpreted can be a SYCL sub-buffer
that was created from a sycl::buffer.
Reinterpreting a sub-buffer provides a reinterpreted view of the
sub-buffer only, and does not change the offset or size of the
sub-buffer view (in bytes) relative to the parent sycl::buffer.
template <typename ReinterpretT, int ReinterpretDim = Dimensions>
sycl::buffer<ReinterpretT, ReinterpretDim,
typename std::allocator_traits<AllocatorT>::template rebind_alloc<
std::remove_const_t<ReinterpretT>>>
reinterpret() const;
Creates and returns a reinterpreted sycl::buffer with the type specified by
ReinterpretT and dimensions specified by ReinterpretDim.
Only valid when (ReinterpretDim == 1) or when
((ReinterpretDim == Dimensions) && (sizeof(ReinterpretT) == sizeof(T))).
The buffer object being reinterpreted can be a SYCL sub-buffer that was created from a SYCL buffer.
Reinterpreting a sub-buffer provides a reinterpreted view of the
sub-buffer only, and does not change the offset or size of the
sub-buffer view (in bytes) relative to the parent sycl::buffer.
Template parameters
|
Type of the new buffer element. |
|
Dimensions of the new buffer. |
Parameters
|
Dimensionality of the new buffer. |
Exceptions
errc::invalidIf the total size in bytes represented by the type and range of the reinterpreted
sycl::buffer(or sub-buffer) does not equal the total size in bytes represented by the type and range of thissycl::buffer(or sub-buffer).If the total size in bytes represented by this
sycl::buffer(or sub-buffer) is not evenly divisible bysizeof(ReinterpretT).
Buffer properties#
The properties that can be provided when
constructing the sycl::buffer.
namespace sycl::property {
namespace buffer {
class use_host_ptr;
class use_mutex;
class context_bound;
} // namespace buffer
} // namespace sycl::property
sycl::property::buffer::use_host_ptr#
namespace sycl::property::buffer {
class use_host_ptr {
public:
use_host_ptr() = default;
};
} // namespace sycl::property::buffer
The sycl::property::buffer::use_host_ptr property adds the requirement
that the SYCL runtime must not allocate any memory for the sycl::buffer
and instead uses the provided host pointer directly. This prevents the SYCL
runtime from allocating additional temporary storage on the host.
This property has a special guarantee for buffers that are constructed
from a hostData pointer. If a sycl::host_accessor is constructed from
such a buffer, then the address of the reference type returned from the
accessor's member functions such as operator[](id<>) will be the same
as the corresponding hostData address.
(constructors)#
sycl::property::buffer::use_host_ptr::use_host_ptr();
Constructs a sycl::property::buffer::use_host_ptr property instance.
sycl::property::buffer::use_mutex#
namespace sycl::property::buffer {
class use_mutex {
public:
use_mutex(std::mutex& mutexRef);
std::mutex* get_mutex_ptr() const;
};
} // namespace sycl::property::buffer
The sycl::property::buffer::use_mutex property is valid for the
sycl::buffer, unsampled_image and sampled_image classes.
The property adds the requirement that the memory which is owned by
the sycl::buffer can be shared with the application via a
std::mutex provided to the property.
The mutex is locked by the runtime whenever the data is in use and
unlocked otherwise. Data is synchronized with hostData, when
the mutex is unlocked by the runtime.
(constructors)#
sycl::property::buffer::use_mutex::use_mutex(std::mutex& mutexRef);
Constructs a sycl::property::buffer::use_mutex property instance
with a reference to mutexRef parameter provided.
get_mutex_ptr#
std::mutex* sycl::property::buffer::use_mutex::get_mutex_ptr() const;
Returns the std::mutex which was specified when constructing
this sycl::property::buffer::use_mutex property.
sycl::property::buffer::context_bound#
namespace sycl::property::buffer {
class context_bound {
public:
context_bound(context boundContext);
context get_context() const;
};
} // namespace sycl::property::buffer
The sycl::property::buffer::context_bound property adds the
requirement that the sycl::buffer can only be associated
with a single sycl::context that is provided to the property.
(constructors)#
sycl::property::buffer::context_bound(sycl::context boundContext);
Constructs a sycl::property::buffer::context_bound property
instance with a copy of a sycl::context.
get_context#
sycl::context sycl::property::buffer::context_bound::get_context() const;
Returns the sycl::context which was specified when constructing
this sycl::property::buffer::context_bound property.
Buffer synchronization rules#
Buffer lifetime and destruction#
Buffers are reference-counted. When a buffer value is constructed from another buffer, the two values reference the same buffer and a reference count is incremented. When a buffer value is destroyed, the reference count is decremented. Only when there are no more buffer values that reference a specific buffer is the actual buffer destroyed and the buffer destruction behavior defined below is followed.
If any error occurs on buffer destruction, it is reported via the associated queue's asynchronous error handling mechanism.
The basic rule for the blocking behavior of a buffer destructor is that it blocks if there is some data to write back because a write accessor on it has been created, or if the buffer was constructed with attached host memory and is still in use.
More precisely:
A buffer can be constructed from a sycl::range (and without a
hostDatapointer). The memory management for this type of buffer is entirely handled by the SYCL system. The destructor for this type of buffer does not need to block, even if work on the buffer has not completed. Instead, the SYCL system frees any storage required for the buffer asynchronously when it is no longer in use in queues. The initial contents of the buffer are unspecified.A buffer can be constructed from a
hostDatapointer. The buffer will use this host memory for its full lifetime, but the contents of this host memory are unspecified for the lifetime of the buffer. If the host memory is modified on the host or if it is used to construct another buffer or image during the lifetime of this buffer, then the results are undefined. The initial contents of the buffer will be the contents of the host memory at the time of construction.When the buffer is destroyed, the destructor will block until all work in queues on the buffer have completed, then copy the contents of the buffer back to the host memory (if required) and then return.
If the type of the host data is
const, then the buffer is read-only; only read accessors are allowed on the buffer and no-copy-back to host memory is performed (although the host memory must still be kept available for use by SYCL). When using the default buffer allocator, the constantness of the type will be removed in order to allow host allocation of memory, which will allow temporary host copies of the data by the SYCL runtime, for example for speeding up host accesses.If the type of the host data is not
constbut the pointer to host data isconst, then the read-only restriction applies only on host and not on device accesses.When the buffer is destroyed, the destructor will block until all work in queues on the buffer have completed.
A buffer can be constructed using a
std::shared_ptrto host data. This pointer is shared between the SYCL application and the runtime. In order to allow synchronization between the application and the runtime astd::mutexis used which will be locked by the runtime whenever the data is in use, and unlocked when it is no longer needed.The
std::shared_ptrreference counting is used in order to prevent destroying the buffer host data prematurely. If thestd::shared_ptris deleted from the user application before buffer destruction, the buffer can continue securely because the pointer hasn't been destroyed yet. It will not copy data back to the host before destruction, however, as the application side has already deleted its copy.Note
Since there is an implicit conversion of a
std::unique_ptrto astd::shared_ptr, astd::unique_ptrcan also be used to pass the ownership to the SYCL runtime.A buffer can be constructed from a pair of iterator values. In this case, the buffer construction will copy the data from the data range defined by the iterator pair. The destructor will not copy back any data and does not need to block.
A buffer can be constructed from a container on which
std::data(container)andstd::size(container)are well-formed. The initial contents of the buffer will be the contents of the container at the time of construction.The buffer may use the memory within the container for its full lifetime, and the contents of this memory are unspecified for the lifetime of the buffer. If the container memory is modified by the host during the lifetime of this buffer, then the results are undefined.
When the buffer is destroyed, the destructor will block until all work in queues on the buffer have completed. If the return type of
std::data(container)is notconstthen the destructor will also copy the contents of the buffer to the container (if required).
If set_final_data() is used to change where to write the data
back to, then the destructor of the buffer will block if a write
accessor on it has been created.
Sub-buffers#
A sub-buffer object can be created, which is a sub-range reference to a base buffer. This sub-buffer can be used to create accessors to the base buffer, which have access to the range specified at time of construction of the sub-buffer. Sub-buffers cannot be created from sub-buffers, but only from a base buffer which is not already a sub-buffer.
Sub-buffers must be constructed from a contiguous region of memory in a buffer. This requirement is potentially non-intuitive when working with buffers that have dimensionality larger than one, but maps to one-dimensional SYCL backend native allocations without performance cost due to index mapping computation.
Example
See Example 1.
Example 1#
An example of creating sub-buffers from a parent buffer:
1#include <sycl/sycl.hpp>
2
3#include <iostream>
4
5int main() {
6
7 // Create 2-d buffer with 8x8 ints
8 sycl::buffer<int, 2> parent_buffer{sycl::range<2>{8, 8}};
9
10 try {
11 // OK: Contiguous region from middle of buffer
12 sycl::buffer<int, 2> sub_buf1{parent_buffer,
13 /*offset*/ sycl::range<2>{2, 0},
14 /*size*/ sycl::range<2>{2, 8}};
15
16 std::cout << "sub_buf1 created successfully" << std::endl;
17 } catch (const sycl::exception &e) {
18 std::cerr << "Exception while creating sub_buf1: " << e.what() << std::endl;
19 }
20
21 try {
22 // invalid exception: Non-contiguous regions of 2-d buffer
23 sycl::buffer<int, 2> sub_buf2{parent_buffer,
24 /*offset*/ sycl::range<2>{2, 0},
25 /*size*/ sycl::range<2>{2, 2}};
26
27 std::cout << "sub_buf2 created successfully" << std::endl;
28 } catch (const sycl::exception &e) {
29 std::cerr << "Exception while creating sub_buf2: " << e.what() << std::endl;
30 }
31
32 try {
33 // invalid exception: Non-contiguous regions of 2-d buffer
34 sycl::buffer<int, 2> sub_buf3{parent_buffer,
35 /*offset*/ sycl::range<2>{2, 2},
36 /*size*/ sycl::range<2>{2, 6}};
37
38 std::cout << "sub_buf3 created successfully" << std::endl;
39 } catch (const sycl::exception &e) {
40 std::cerr << "Exception while creating sub_buf3: " << e.what() << std::endl;
41 }
42
43 try {
44 // invalid exception: Out-of-bounds size
45 sycl::buffer<int, 2> sub_buf4{parent_buffer,
46 /*offset*/ sycl::range<2>{2, 2},
47 /*size*/ sycl::range<2>{2, 8}};
48
49 std::cout << "sub_buf4 created successfully" << std::endl;
50 } catch (const sycl::exception &e) {
51 std::cerr << "Exception while creating sub_buf4: " << e.what() << std::endl;
52 }
53
54 return 0;
55}
Output:
sub_buf1 created successfully
Exception while creating sub_buf2: Requested sub-buffer region is not contiguous -30 (PI_ERROR_INVALID_VALUE)
Exception while creating sub_buf3: Requested sub-buffer region is not contiguous -30 (PI_ERROR_INVALID_VALUE)
Exception while creating sub_buf4: Requested sub-buffer size exceeds the size of the parent buffer -30 (PI_ERROR_INVALID_VALUE)