From bc3613f21ecc738f92408619e44e52eef0b2cd38 Mon Sep 17 00:00:00 2001 From: Alan Liddell Date: Mon, 19 Aug 2024 17:08:14 -0400 Subject: [PATCH 1/2] Update the reader's tail to its position if no data is mapped --- acquire-video-runtime/src/runtime/channel.c | 32 ++-- acquire-video-runtime/tests/CMakeLists.txt | 1 + .../tests/sleep-while-inspecting.cpp | 178 ++++++++++++++++++ 3 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 acquire-video-runtime/tests/sleep-while-inspecting.cpp diff --git a/acquire-video-runtime/src/runtime/channel.c b/acquire-video-runtime/src/runtime/channel.c index 7b36674..4c95464 100644 --- a/acquire-video-runtime/src/runtime/channel.c +++ b/acquire-video-runtime/src/runtime/channel.c @@ -165,28 +165,28 @@ channel_read_map(struct channel* self, struct channel_reader* reader) reader_initialize(self, reader); size_t* const cycle = self->holds.cycles + reader->id - 1; - size_t* const pos = self->holds.pos + reader->id - 1; - uint8_t* out = self->data + *pos; + size_t* const tail = self->holds.pos + reader->id - 1; + uint8_t* out = self->data + *tail; if (reader->state == ChannelState_Mapped) { reader->status = Channel_Expected_Unmapped_Reader; - goto AdvanceToWriterHead; + goto AdvanceTailToReaderPos; } - if (*pos == self->head && *cycle == self->cycle) { + if (*tail == self->head && *cycle == self->cycle) { goto Finalize; } - if (*pos < self->head) { + if (*tail < self->head) { if (*cycle != self->cycle) goto Overflow; - nbytes = self->head - *pos; // this will never be 0 + nbytes = self->head - *tail; // this will never be 0 reader->pos = self->head; reader->cycle = self->cycle; } else { if (self->cycle != *cycle + 1) goto Overflow; - nbytes = self->high - *pos; + nbytes = self->high - *tail; reader->pos = 0; reader->cycle = *cycle + 1; } @@ -198,7 +198,7 @@ channel_read_map(struct channel* self, struct channel_reader* reader) // here. A call to channel_read_unmap() would return early and not advance // the reader's bookmarks in that case, so we need to do it here. if (!nbytes) { - goto AdvanceToWriterHead; + goto AdvanceTailToReaderPos; } reader->state = ChannelState_Mapped; @@ -208,10 +208,10 @@ channel_read_map(struct channel* self, struct channel_reader* reader) return (struct slice){ .beg = out, .end = out + nbytes }; Overflow: reader->status = Channel_Error; -AdvanceToWriterHead: +AdvanceTailToReaderPos: out = 0; nbytes = 0; - *pos = self->head; + *tail = reader->pos; *cycle = self->cycle; goto Finalize; } @@ -226,18 +226,18 @@ channel_read_unmap(struct channel* self, lock_acquire(&self->lock); size_t* const cycle = self->holds.cycles + reader->id - 1; - size_t* const pos = self->holds.pos + reader->id - 1; + size_t* const tail = self->holds.pos + reader->id - 1; - size_t length = get_available_byte_count(reader, *pos, *cycle, self->high); + size_t length = get_available_byte_count(reader, *tail, *cycle, self->high); consumed_bytes = min(length, consumed_bytes); if (consumed_bytes >= length) { *cycle = reader->cycle; - *pos = reader->pos; + *tail = reader->pos; } else { - *pos += consumed_bytes; + *tail += consumed_bytes; } - if (self->head < *pos && *pos == self->high) { - *pos = 0; + if (self->head < *tail && *tail == self->high) { + *tail = 0; *cycle += 1; } reader->state = ChannelState_Unmapped; diff --git a/acquire-video-runtime/tests/CMakeLists.txt b/acquire-video-runtime/tests/CMakeLists.txt index efd7969..3569c7e 100644 --- a/acquire-video-runtime/tests/CMakeLists.txt +++ b/acquire-video-runtime/tests/CMakeLists.txt @@ -29,6 +29,7 @@ else () filter-video-average repeat-start-no-monitor aligned-videoframe-pointers + sleep-while-inspecting ) foreach (name ${tests}) diff --git a/acquire-video-runtime/tests/sleep-while-inspecting.cpp b/acquire-video-runtime/tests/sleep-while-inspecting.cpp new file mode 100644 index 0000000..54589f8 --- /dev/null +++ b/acquire-video-runtime/tests/sleep-while-inspecting.cpp @@ -0,0 +1,178 @@ +/// @file sleep-while-inspecting.cpp +/// Test that uninspected regions will not be overwritten by the runtime. + +#include "acquire.h" +#include "device/hal/device.manager.h" +#include "device/props/components.h" +#include "platform.h" +#include "logger.h" + +#include +#include + +void +reporter(int is_error, + const char* file, + int line, + const char* function, + const char* msg) +{ + fprintf(is_error ? stderr : stdout, + "%s%s(%d) - %s: %s\n", + is_error ? "ERROR " : "", + file, + line, + function, + msg); +} + +static size_t +bytes_of_frame(const VideoFrame* frame) +{ + return sizeof(*frame) + bytes_of_image(&frame->shape); +} + +/// Helper for passing size static strings as function args. +/// For a function: `f(char*,size_t)` use `f(SIZED("hello"))`. +/// Expands to `f("hello",5)`. +#define SIZED(str) str, sizeof(str) + +#define L (aq_logger) +#define LOG(...) L(0, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define ERR(...) L(1, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#define EXPECT(e, ...) \ + do { \ + if (!(e)) { \ + char buf[1 << 8] = { 0 }; \ + ERR(__VA_ARGS__); \ + snprintf(buf, sizeof(buf) - 1, __VA_ARGS__); \ + throw std::runtime_error(buf); \ + } \ + } while (0) +#define CHECK(e) EXPECT(e, "Expression evaluated as false: %s", #e) +#define DEVOK(e) CHECK(Device_Ok == (e)) +#define OK(e) CHECK(AcquireStatus_Ok == (e)) + +void +configure(AcquireRuntime* runtime) +{ + CHECK(runtime); + + const DeviceManager* dm = acquire_device_manager(runtime); + CHECK(dm); + + AcquireProperties props = {}; + OK(acquire_get_configuration(runtime, &props)); + + DEVOK(device_manager_select(dm, + DeviceKind_Camera, + SIZED("simulated.*empty.*") - 1, + &props.video[0].camera.identifier)); + DEVOK(device_manager_select(dm, + DeviceKind_Storage, + SIZED("tiff") - 1, + &props.video[0].storage.identifier)); + + storage_properties_init( + &props.video[0].storage.settings, 0, SIZED("out.tif"), 0, 0, { 0 }, 0); + + OK(acquire_configure(runtime, &props)); + + AcquirePropertyMetadata metadata = { 0 }; + OK(acquire_get_configuration_metadata(runtime, &metadata)); + + props.video[0].camera.settings.binning = 1; + props.video[0].camera.settings.pixel_type = SampleType_u12; + props.video[0].camera.settings.shape = { + .x = 8192, + .y = 8192, + }; + props.video[0].max_frame_count = 20; + props.video[0].camera.settings.exposure_time_us = 1e5; + + OK(acquire_configure(runtime, &props)); + storage_properties_destroy(&props.video[0].storage.settings); +} + +void +acquire(AcquireRuntime* runtime) +{ + CHECK(runtime); + + AcquireProperties props = {}; + OK(acquire_get_configuration(runtime, &props)); + + const auto next = [](VideoFrame* cur) -> VideoFrame* { + return (VideoFrame*)(((uint8_t*)cur) + bytes_of_frame(cur)); + }; + + const auto consumed_bytes = [](const VideoFrame* const cur, + const VideoFrame* const end) -> size_t { + return (uint8_t*)end - (uint8_t*)cur; + }; + + struct clock clock = {}; + // expected time to acquire frames + 100% + static double time_limit_ms = + (props.video[0].max_frame_count / 6.0) * 1000.0 * 2.0; + clock_init(&clock); + clock_shift_ms(&clock, time_limit_ms); + OK(acquire_start(runtime)); + { + uint64_t nframes = 0; + while (nframes < props.video[0].max_frame_count) { + struct clock throttle + {}; + clock_init(&throttle); + EXPECT(clock_cmp_now(&clock) < 0, + "Timeout at %f ms", + clock_toc_ms(&clock) + time_limit_ms); + VideoFrame *beg, *end, *cur; + OK(acquire_map_read(runtime, 0, &beg, &end)); + for (cur = beg; cur < end; cur = next(cur)) { + LOG("stream %d counting frame w id %d", 0, cur->frame_id); + CHECK(cur->shape.dims.width == + props.video[0].camera.settings.shape.x); + CHECK(cur->shape.dims.height == + props.video[0].camera.settings.shape.y); + ++nframes; + } + { + uint32_t n = (uint32_t)consumed_bytes(beg, end); + OK(acquire_unmap_read(runtime, 0, n)); + if (n) + LOG("stream %d consumed bytes %d", 0, n); + } + clock_sleep_ms(&throttle, 1000.0f); + + LOG("stream %d nframes %d. remaining time %f s", + 0, + nframes, + -1e-3 * clock_toc_ms(&clock)); + } + + CHECK(nframes == props.video[0].max_frame_count); + } + + OK(acquire_stop(runtime)); +} + +int +main() +{ + int retval = 1; + AcquireRuntime* runtime = acquire_init(reporter); + + try { + configure(runtime); + acquire(runtime); + retval = 0; + } catch (const std::exception& e) { + ERR("%s", e.what()); + } catch (...) { + ERR("unknown error"); + } + + acquire_shutdown(runtime); + return retval; +} From 7d2bcc764b92e04824c049c20cb77f9f24d83709 Mon Sep 17 00:00:00 2001 From: Alan Liddell Date: Tue, 20 Aug 2024 15:27:46 -0700 Subject: [PATCH 2/2] Respond to PR feedback. --- acquire-video-runtime/src/runtime/channel.c | 47 ++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/acquire-video-runtime/src/runtime/channel.c b/acquire-video-runtime/src/runtime/channel.c index 4c95464..caf798e 100644 --- a/acquire-video-runtime/src/runtime/channel.c +++ b/acquire-video-runtime/src/runtime/channel.c @@ -165,53 +165,52 @@ channel_read_map(struct channel* self, struct channel_reader* reader) reader_initialize(self, reader); size_t* const cycle = self->holds.cycles + reader->id - 1; - size_t* const tail = self->holds.pos + reader->id - 1; - uint8_t* out = self->data + *tail; + size_t* const pos = self->holds.pos + reader->id - 1; + uint8_t* out = self->data + *pos; if (reader->state == ChannelState_Mapped) { reader->status = Channel_Expected_Unmapped_Reader; - goto AdvanceTailToReaderPos; + goto AdvanceToWriterHead; } - if (*tail == self->head && *cycle == self->cycle) { + if (*pos == self->head && *cycle == self->cycle) { goto Finalize; } - if (*tail < self->head) { + if (*pos < self->head) { if (*cycle != self->cycle) goto Overflow; - nbytes = self->head - *tail; // this will never be 0 + nbytes = self->head - *pos; // this will never be 0 reader->pos = self->head; reader->cycle = self->cycle; } else { if (self->cycle != *cycle + 1) goto Overflow; - nbytes = self->high - *tail; + nbytes = self->high - *pos; reader->pos = 0; reader->cycle = *cycle + 1; } - // Even if nothing is available on the channel, we still need to advance - // this reader's position & cycle bookmarks to the position & cycle of the - // writer's head. Normally this would happen in channel_read_unmap(), but - // because no data is available, we do not set the reader's state to Mapped - // here. A call to channel_read_unmap() would return early and not advance - // the reader's bookmarks in that case, so we need to do it here. if (!nbytes) { - goto AdvanceTailToReaderPos; + // If nothing is available to read, we still need to advance this + // reader's position & cycle bookmarks to the beginning of the queue and + // the writer's cycle, respectively. + out = 0; + *pos = 0; + *cycle = self->cycle; + } else { + reader->state = ChannelState_Mapped; } - reader->state = ChannelState_Mapped; - Finalize: lock_release(&self->lock); return (struct slice){ .beg = out, .end = out + nbytes }; Overflow: reader->status = Channel_Error; -AdvanceTailToReaderPos: +AdvanceToWriterHead: out = 0; nbytes = 0; - *tail = reader->pos; + *pos = self->head; *cycle = self->cycle; goto Finalize; } @@ -226,18 +225,18 @@ channel_read_unmap(struct channel* self, lock_acquire(&self->lock); size_t* const cycle = self->holds.cycles + reader->id - 1; - size_t* const tail = self->holds.pos + reader->id - 1; + size_t* const pos = self->holds.pos + reader->id - 1; - size_t length = get_available_byte_count(reader, *tail, *cycle, self->high); + size_t length = get_available_byte_count(reader, *pos, *cycle, self->high); consumed_bytes = min(length, consumed_bytes); if (consumed_bytes >= length) { *cycle = reader->cycle; - *tail = reader->pos; + *pos = reader->pos; } else { - *tail += consumed_bytes; + *pos += consumed_bytes; } - if (self->head < *tail && *tail == self->high) { - *tail = 0; + if (self->head < *pos && *pos == self->high) { + *pos = 0; *cycle += 1; } reader->state = ChannelState_Unmapped;