-
Notifications
You must be signed in to change notification settings - Fork 132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop zerocopy #1053
Develop zerocopy #1053
Changes from 19 commits
4a1f818
c36ab3b
cc44859
557abfd
185186d
c4ff206
ad66010
3c1d976
cf35d8c
a5016b6
5019cca
fd710a6
1effc78
88e86f8
0d4b57d
979573a
242d6ec
316c073
74441bd
587f9a2
21e4649
bc17b50
d8b81e8
2efe453
cb9ac60
9c4f05c
950dbd4
48f664d
26fb39c
f99c0d8
4560bb3
1a249f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can remove this one before the merge. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
#include <depthai/depthai.hpp> | ||
#include <opencv2/highgui.hpp> | ||
#include "depthai/pipeline/ThreadedHostNode.hpp" | ||
#include "depthai/pipeline/node/ImageManip.hpp" | ||
|
||
class TestSource : public dai::NodeCRTP<dai::node::ThreadedHostNode, TestSource> { | ||
public: | ||
Output output = dai::Node::Output{*this, {}}; | ||
|
||
void run() override { | ||
int64_t seqNum = 0; | ||
|
||
// Generate a cv::Mat frame with a gradient | ||
cv::Mat frame(768, 1920, CV_8UC3); | ||
for(int i = 0; i < frame.rows; i++) { | ||
for(int j = 0; j < frame.cols; j++) { | ||
frame.at<cv::Vec3b>(i, j) = cv::Vec3b(i % 256, j % 256, (i + j) % 256); | ||
} | ||
} | ||
if(frame.empty()) { | ||
throw std::runtime_error("Couldn't capture frame"); | ||
} | ||
|
||
cv::Mat frameResized; | ||
cv::resize(frame, frameResized, cv::Size(1920, 768)); | ||
|
||
while(isRunning()) { | ||
auto imgFrame = std::make_shared<dai::ImgFrame>(); | ||
imgFrame->setFrame(frameResized); | ||
imgFrame->setSequenceNum(seqNum++); | ||
imgFrame->setType(dai::ImgFrame::Type::NV12); | ||
imgFrame->setWidth(frameResized.cols); | ||
imgFrame->setHeight(frameResized.rows); | ||
imgFrame->setTimestamp(std::chrono::steady_clock::now()); | ||
output.send(imgFrame); | ||
} | ||
} | ||
}; | ||
|
||
int main() { | ||
// Create pipeline | ||
dai::Pipeline pipeline(true); | ||
auto camRgb = pipeline.create<TestSource>(); | ||
auto manip = pipeline.create<dai::node::ImageManip>(); | ||
camRgb->output.link(manip->inputImage); | ||
manip->initialConfig.setResize(1920, 768); | ||
manip->setMaxOutputFrameSize(1920 * 768 * 3); | ||
auto queue = manip->out.createOutputQueue(); | ||
pipeline.start(); | ||
|
||
std::cout << "Pipeline running" << std::endl; | ||
std::size_t framesRecieved = 0; | ||
std::size_t totalTime = 0; | ||
while(pipeline.isRunning()) { | ||
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); | ||
|
||
auto img = queue->get<dai::ImgFrame>(); | ||
auto cvFrame = img->getCvFrame(); | ||
|
||
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); | ||
++framesRecieved; | ||
totalTime += std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count(); | ||
|
||
if (framesRecieved % 1024 == 0) { | ||
std::cout << "Mean time: " << totalTime / framesRecieved << "µs" << std::endl; | ||
} | ||
} | ||
|
||
pipeline.wait(); | ||
return 0; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,8 @@ | |
#include "depthai/utility/Memory.hpp" | ||
#include "depthai/utility/Serialization.hpp" | ||
#include "depthai/utility/VectorMemory.hpp" | ||
#include "depthai/utility/SharedMemory.hpp" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, removed |
||
|
||
namespace dai { | ||
|
||
/// Abstract message | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ class Buffer : public ADatatype { | |
public: | ||
Buffer() = default; | ||
Buffer(size_t size); | ||
Buffer(long fd); | ||
virtual ~Buffer() = default; | ||
|
||
virtual void serialize(std::vector<std::uint8_t>& metadata, DatatypeEnum& datatype) const { | ||
|
@@ -35,11 +36,13 @@ class Buffer : public ADatatype { | |
* @param data Copies data to internal buffer | ||
*/ | ||
void setData(const std::vector<std::uint8_t>& data); | ||
void setData(const long fd); | ||
|
||
/** | ||
* @param data Moves data to internal buffer | ||
*/ | ||
void setData(std::vector<std::uint8_t>&& data); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Run clangformat target |
||
|
||
/** | ||
* Retrieves timestamp related to dai::Clock::now() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
#pragma once | ||
|
||
// std | ||
#include <cstdint> | ||
#include <cstring> | ||
#include <functional> | ||
#include <iostream> | ||
#ifdef __unix__ | ||
#include <fcntl.h> | ||
#include <sys/mman.h> | ||
#include <sys/stat.h> | ||
#include <sys/un.h> | ||
#include <unistd.h> | ||
#endif | ||
|
||
// project | ||
#include "depthai/utility/Memory.hpp" | ||
|
||
namespace dai { | ||
|
||
// memory as interface | ||
class SharedMemory : public Memory { | ||
protected: | ||
long fd = -1; | ||
void* mapping; | ||
void mapFd() { | ||
mapping = mmap(NULL, getSize(), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); | ||
if (mapping == NULL) { | ||
/* Error handling here */ | ||
} | ||
} | ||
void unmapFd() { | ||
if (mapping == NULL) { | ||
return; | ||
} | ||
|
||
munmap(mapping, getSize()); | ||
} | ||
public: | ||
SharedMemory() { | ||
kind = MemoryKinds::MEMORY_KIND_SHARED_MEMORY; | ||
fd = -1; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to allow this? I think it would make more sense to only allow to pass in a size and allocate the shared memory. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah probably useless, i removed it.
Well, yes, that makes sense. So both passing the FD externally or automatically allocating with a shared memory buffer. Also, i think it would be worth it looking into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please look into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is pretty nice. It's like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh btw, that constructor is actually needed because of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we set it in the constructor, can we just initialize shared memory in the initializer list?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see what you mean (checked the source). Let's leave it then. |
||
SharedMemory(long argFd) : fd(argFd) { | ||
kind = MemoryKinds::MEMORY_KIND_SHARED_MEMORY; | ||
mapFd(); | ||
} | ||
|
||
~SharedMemory() { | ||
unmapFd(); | ||
close(fd); | ||
} | ||
|
||
SharedMemory& operator=(long argFd) { | ||
unmapFd(); | ||
fd = argFd; | ||
mapFd(); | ||
|
||
return *this; | ||
} | ||
|
||
span<std::uint8_t> getData() override { | ||
if (mapping == NULL) { | ||
mapFd(); | ||
} | ||
return {(uint8_t*)mapping, getSize()}; | ||
} | ||
span<const std::uint8_t> getData() const override { | ||
return {(const uint8_t*)mapping, getSize()}; | ||
} | ||
std::size_t getMaxSize() const override { | ||
struct stat fileStats; | ||
fstat(fd, &fileStats); | ||
|
||
return fileStats.st_size; | ||
} | ||
std::size_t getOffset() const override { | ||
return ftell(fdopen(fd, "r")); | ||
} | ||
void setSize(std::size_t size) override { | ||
ftruncate(fd, size); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if size is bigger than current size? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, technically ftruncate extends it, but how that would interact with the various kinds of shared memory depends on the implementation If it's for example memory created with shm_open, it will just expand it. If it's a DMA buffer, it depends on the implementation. For Gst, it seems to be the same as shm_open. However, we probably need to unmap and map the FD so that we can actually have a mapping of the correct size. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense, I though truncate only downsizes. |
||
|
||
std::size_t getSize() const { | ||
return getMaxSize(); | ||
} | ||
|
||
long getFd() const { | ||
return fd; | ||
} | ||
}; | ||
|
||
} // namespace dai |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,9 +14,9 @@ namespace dai { | |
class VectorMemory : public std::vector<std::uint8_t>, public Memory { | ||
public: | ||
// using std::vector<std::uint8_t>::vector; | ||
VectorMemory() = default; | ||
VectorMemory(const std::vector<std::uint8_t>& d) : vector(std::move(d)) {} | ||
VectorMemory(std::vector<std::uint8_t>&& d) : vector(std::move(d)) {} | ||
VectorMemory() { kind = MemoryKinds::MEMORY_KIND_VECTOR_MEMORY; } | ||
VectorMemory(const std::vector<std::uint8_t>& d) : vector(std::move(d)) { kind = MemoryKinds::MEMORY_KIND_VECTOR_MEMORY; } | ||
VectorMemory(std::vector<std::uint8_t>&& d) : vector(std::move(d)) { kind = MemoryKinds::MEMORY_KIND_VECTOR_MEMORY; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can remove the kind I think and use casts instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the kind is important, as for example in XLinkOut in DepthAI device, we need it to discern how to output. Unless we want to understand which it is by using sizeof(), which is imperfect, this system works better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't think about that... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like it doesn't work. I've reinstated the kind variables and they work now. |
||
VectorMemory& operator=(std::vector<std::uint8_t>&& d) { | ||
std::vector<std::uint8_t>::operator=(std::move(d)); | ||
return *this; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be removed.