Ringbuffer vs Flow Set Pool

<< Click to Display Table of Contents >>

Navigation:  Migration Guide for the Acquisition Stacks > CVB++ >

Ringbuffer vs Flow Set Pool

What has changed?

With the 3rd generation stack it is possible to use buffers (organized in a structure named "flow set pool") managed either by the driver or allocate and passed to the driver by the user/caller. The option to use user-allocated memory is useful if, for example, the image data buffers need to satisfy conditions like e.g. address alignment (if extensions like SSE or AVX are going to be used on the data) or a particular block of memory (if the image is to be used by a GPU).

If the objective is simply to change the size of the memory pool but it is irrelevant, where on the heap the flow sets are created, then the function to register a managed flow set pool provides an easy alternative to the fully user-allocated pool.

 

Code Examples

2nd generation stack

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

auto devices = DeviceFactory::Discover(DiscoverFlags::IgnoreVins);

auto device = DeviceFactory::Open(devices[0].AccessToken(),

 AcquisitionStack::Vin);

auto stream = device->Stream();

if (stream->RingBuffer())

{

stream->RingBuffer()->ChangeCount(5, DeviceUpdateMode::UpdateDeviceImage);

 stream->RingBuffer()->SetLockMode(RingBufferLockMode::On);

}

stream->Start();

std::vector<RingBufferImagePtr> images;

for (int i = 0; i < 10; ++i)

{

 if (stream->RingBuffer())

 {

  auto waitResult = stream->WaitFor<RingBufferImage>(std::chrono::seconds(5));

  switch (waitResult.Status)

   {

    case WaitStatus::Ok:

      images.push_back(waitResult.Image);

      continue;

    case WaitStatus::Timeout:

      if (imgList.size() > 0)

          imgList.front()->Unlock();

      continue;

   }

 }

}

stream->Abort();

(5) The presence or absence of ringbuffer capability  can (and should) be verified by checking the return value of the ringbuffer access function.

(7) Changes the number of buffers in the stream's ring buffer. Calling the change count function in mode update device image will discard all buffers, with which the device was created with and free the memory before reallocating the new buffers.

(8) Activates the lock mode of the ring buffer. The buffer in this case is unlocked automatically when running out of scope.

(16) Like in the simple Single Stream case, the Wait() function returns with the wait result that combines the stream image with the wait status.

(24) Calling the Unlock() method for freeing the ring buffer element is necessary in the RingBufferLockMode::On so that the buffer may be re-used for newly acquired images.

 

3rd generation stack

1

2

3

4

5

6

7

8

9

10

11

auto devices = DeviceFactory::Discover(DiscoverFlags::IgnoreVins);

auto device = DeviceFactory::Open<GenICamDevice>(devices[0].AccessToken(),

 AcquisitionStack::GenTL);

auto stream = device->Stream<ImageStream>();

stream->RegisterManagedFlowSetPool(100);

stream->Start();

for (int i = 0; i < 10; ++i)

{

auto [image, status, nodeMaps] = stream->Wait();

}

stream->Abort();

(5) With the function to register a managed flow set pool, it is possible to create an internal flow set pool with the specified size. A previously registered flow set pool will be detached from the acquisition engine after the new flow set pool was created.

 

Large Buffer Number Change

3rd generation stack

1

2

3

4

5

6

7

8

9

10

11

12

13

auto devices = DeviceFactory::Discover(DiscoverFlags::IgnoreVins);

auto device = DeviceFactory::Open<GenICamDevice>(discoveryInfo[0].AccessToken(),

 AcquisitionStack::GenTL);

auto stream = device->Stream<ImageStream>();

stream->RegisterManagedFlowSetPool(100);

stream->DeregisterFlowSetPool();

stream->RegisterManagedFlowSetPool(200);

stream->Start();

for (int i = 0; i < 10; ++i)

{

auto [image, result, nodeMaps] = stream->Wait();

}

stream->Abort();

(6) Calling the DeregisterFlowSetPool() function between flow set pool registrations helps keep the memory consumption of the software low (otherwise memory usage would spike for a short moment to the sum of the currently used pool and the new pool). Note that a running stream must be stopped prior to registering or deregistering a flow set pool.

 

User-Allocated Memory (External Flow Set Pool)

Buffer Layout

When passing user-allocated buffers to a stream to be used as the destination memory for image acquisition, these buffers are organized in flow set pools, one of which is to be set per stream by means of the registration function for external flow set pools.

Each flow set pool is effectively a list of flow sets. The required minimum number of flow sets per flow set pool can be queried with the function for min required flow set count. This minimum pool size must be observed when constructing a flow set pool. A maximum pool size is not explicitly defined and is normally up to the user and the amount of available suitable memory in a given system.

The flow sets in turn are lists of flows. Flows can simply be thought of as buffers. However it is to a certain extent up to the camera, how this buffer will be used and therefore the simple equation 1 flow = 1 image buffer is not necessarily true. The size of these flows is a device-specific information that needs to be queried with the function size on flow set info.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class UserFlowSetPool final

 : public Driver::FlowSetPool

{

using UserFlowSetPoolPtr = std::shared_ptr<UserFlowSetPool>;

public:

UserFlowSetPool(const std::vector<FlowInfo>& flowInfo) noexcept

   : Cvb::FlowSetPool(flowInfo, FlowSetPool::ProtectedTag{})

 {

 }

virtual ~UserFlowSetPool()

 {

  for (auto& flowSet : *this)

    for (auto& flow : flowSet)

      Tutorial::aligned_free(flow.Buffer);

 }

static UserFlowSetPoolPtr Create(const std::vector<FlowInfo>& flowInfos)

 {

  return std::make_shared<UserFlowSetPool>(flowInfos);

 }

};

This example is a helper class for flow sets and derived from FlowSetPool. At the core of the class is a vector with flow sets. That vector simply holds the buffers that have been allocated for the flow set pool.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

auto flowSetInfo = stream->FlowSetInfo();

auto flowSetPoolPtr = Tutorial::UserFlowSetPool::Create(flowSetInfo);

std::generate_n(std::back_inserter(*flowSetPoolPtr), NUM_BUFFERS,

 [&flowSetInfo]()

 {

  auto flows = std::vector<void*>(flowSetInfo.size());

  std::transform(flowSetInfo.begin(), flowSetInfo.end(),

    flows.begin(), [](Driver::FlowInfo info)

     {

      return Tutorial::aligned_malloc(info.Size, info.Alignment);

     });

  return flows;

 });

stream->RegisterExternalFlowSetPool(std::move(flowSetPoolPtr));

// acquire images

stream->DeregisterFlowSetPool();

(1) The first step is to query the required layout of the flow sets from the current stream.

(2) Then create the custom flow set pool.

(3) The flow buffers are allocated with the size and alignment information of the flow set info and stored into the flow set pool. The buffers will later be released in the destructor of the user flow set pool (line 14 in previous snippet)

(14) Then the flow set pool to be passed to the stream. Note that this transfers ownership of the flow set pool from the local scope to the stream on which the pool is registered. Once the stream ceases to exist, it will no longer need the registered flow set pool and the destructor is called. The stream also takes care of flow sets still in use by the user. Thus the user flow set pool will be deleted only when the last multipart image, point cloud or composite is deleted.

(16) Deregister the user flow set pool to free the buffers.

 

Summary

Using user-allocated memory for data acquisition is possible, but properly generating the pool of memory, that a stream can draw upon, is somewhat complex as it will be necessary to...

... make sure the stream accepts user-allocated memory in the first place

... make sure that the flow set pool contains enough flow sets

... make sure that each flow set contains the right amount of flows

... make sure that each flow in a set is appropriately sized

... make sure that the memory is valid when passed to the stream

... make sure that the entire data structure is disposed of correctly under all circumstances

To that end, the creation of an object hierarchy, that takes care of these intricacies, is recommended.

 

If the objective is simply to change the size of the memory pool, but it is irrelevant where on the heap the flows are created, then the function of registering managed flow set pools provides an easy alternative to the fully user-allocated pool.