Skip to content

Commit 09a5b61

Browse files
authored
Merge pull request #10 from sat-mtl/direct-gpu-upload
Direct gpu upload
2 parents 1866ec6 + 1cfe5b8 commit 09a5b61

3 files changed

Lines changed: 245 additions & 66 deletions

File tree

src/orbbec.cpp

Lines changed: 143 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "orbbec.hpp"
22
#include <libobsensor/ObSensor.hpp>
3+
#include "godot_cpp/classes/rendering_server.hpp"
34
#include <godot_cpp/variant/packed_vector3_array.hpp>
45
#include <stdlib.h>
56

@@ -40,41 +41,7 @@ PackedStringArray OrbbecDevices::get_devices_serial_numbers() {
4041
return serials;
4142
};
4243

43-
OrbbecPointCloud::OrbbecPointCloud() {
44-
// create a random thinning mask.
45-
for (int i=0; i < thinning_mask_size; ++i) {
46-
thinning_mask[i] = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
47-
}
48-
}
49-
50-
void OrbbecPointCloud::set_thinning(float thin) {
51-
thinning = thin;
52-
}
53-
54-
float OrbbecPointCloud::get_thinning() {
55-
return thinning;
56-
}
57-
58-
PackedStringArray OrbbecPointCloud::get_device_stream_formats() {
59-
// TODO: make this dynamic when we support more camera types.
60-
// I tried to make this a static const inline and it made godot crash everytime.
61-
// This will have to be good enough.
62-
return PackedStringArray {"1024x1024 (WFOV Unbinned)", "512x512 (WFOV Binned)", "640x576 (NFOV Unbinned)", "320x288 (NFOV Binned)"};
63-
}
64-
65-
void OrbbecPointCloud::_bind_methods() {
66-
godot::ClassDB::bind_method(D_METHOD("start_stream", "xres", "yres", "framerate"), &OrbbecPointCloud::start_stream);
67-
godot::ClassDB::bind_method(D_METHOD("stop_stream"), &OrbbecPointCloud::stop_stream);
68-
godot::ClassDB::bind_method(D_METHOD("set_device_from_ip", "ip"), &OrbbecPointCloud::set_device_from_ip);
69-
godot::ClassDB::bind_method(D_METHOD("set_device_from_serial_number", "serial_number"), &OrbbecPointCloud::set_device_from_serial_number);
70-
godot::ClassDB::bind_method(D_METHOD("get_thinning"), &OrbbecPointCloud::get_thinning);
71-
godot::ClassDB::bind_method(D_METHOD("set_thinning", "p_thinning"), &OrbbecPointCloud::set_thinning);
72-
godot::ClassDB::bind_method(D_METHOD("get_device_stream_formats"), &OrbbecPointCloud::get_device_stream_formats);
73-
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "thinning"), "set_thinning", "get_thinning");
74-
ADD_SIGNAL(MethodInfo("point_cloud_frame", PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "points"), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "raw_buffer")));
75-
}
76-
77-
void OrbbecPointCloud::set_device_from_predicate(predicate_type predicate) {
44+
void OrbbecPointCloudBase::set_device_from_predicate(predicate_type predicate) {
7845
std::shared_ptr<ob::DeviceList> devices = ob_ctx.queryDeviceList();
7946
for (uint32_t i = 0; i < devices->getCount(); ++i) {
8047
if (predicate(devices, i)) {
@@ -90,33 +57,170 @@ void OrbbecPointCloud::set_device_from_predicate(predicate_type predicate) {
9057
}
9158
}
9259

93-
void OrbbecPointCloud::set_device_from_ip(String ip) {
60+
void OrbbecPointCloudBase::set_device_from_ip(String ip) {
9461
set_device_from_predicate([&](std::shared_ptr<ob::DeviceList> devices, uint32_t idx) {
9562
return devices->getIpAddress(idx) == ip;
9663
});
9764
}
9865

99-
void OrbbecPointCloud::set_device_from_serial_number(String serial_number) {
66+
void OrbbecPointCloudBase::set_device_from_serial_number(String serial_number) {
10067
set_device_from_predicate([&](std::shared_ptr<ob::DeviceList> devices, uint32_t idx) {
10168
return devices->getSerialNumber(idx) == serial_number;
10269
});
10370
}
10471

105-
void OrbbecPointCloud::stop_stream() {
72+
void OrbbecPointCloudBase::stop_stream() {
10673
// deleting the pipeline makes the stream stop.
10774
pipeline.reset();
10875
// probably don't need to do this but it can't hurt too much
10976
config.reset();
11077
}
11178

79+
PackedStringArray OrbbecPointCloudBase::get_device_stream_formats() {
80+
// TODO: make this dynamic when we support more camera types.
81+
// I tried to make this a static const inline and it made godot crash everytime.
82+
// This will have to be good enough.
83+
return PackedStringArray {"1024x1024 (WFOV Unbinned)", "512x512 (WFOV Binned)", "640x576 (NFOV Unbinned)", "320x288 (NFOV Binned)"};
84+
}
85+
86+
void OrbbecPointCloudBase::_bind_methods() {
87+
godot::ClassDB::bind_method(D_METHOD("stop_stream"), &OrbbecPointCloudBase::stop_stream);
88+
godot::ClassDB::bind_method(D_METHOD("set_device_from_ip", "ip"), &OrbbecPointCloudBase::set_device_from_ip);
89+
godot::ClassDB::bind_method(D_METHOD("set_device_from_serial_number", "serial_number"), &OrbbecPointCloudBase::set_device_from_serial_number);
90+
godot::ClassDB::bind_method(D_METHOD("get_device_stream_formats"), &OrbbecPointCloudBase::get_device_stream_formats);
91+
}
92+
93+
void OrbbecPointCloudGPU::allocate_point_cloud_buffer() {
94+
if (rd == nullptr) {
95+
print_line("please set a rendering device before starting a point cloud stream");
96+
return;
97+
}
98+
uint32_t bytes_needed = xres * yres * floats_per_points * bytes_per_float;
99+
std::cout << bytes_needed << "\n";
100+
101+
PackedByteArray empty_bytes{};
102+
empty_bytes.resize(bytes_needed);
103+
point_bytes.resize(bytes_needed);
104+
std::cout << "resized" << "\n";
105+
if (!rd->has_feature(RenderingDevice::Features::SUPPORTS_BUFFER_DEVICE_ADDRESS)) {
106+
std::cout << "supports" << "\n";
107+
point_buffer = rd->storage_buffer_create(bytes_needed, empty_bytes);
108+
print_line("your GPU doesn't support getting device address. You won't be able to access the point cloud buffer by its gpu address.");
109+
} else {
110+
point_buffer = rd->storage_buffer_create(bytes_needed, empty_bytes, 0, RenderingDevice::BufferCreationBits::BUFFER_CREATION_DEVICE_ADDRESS_BIT);
111+
}
112+
}
113+
114+
void OrbbecPointCloudGPU::_enter_tree() {
115+
if (rd != nullptr) {
116+
allocate_point_cloud_buffer();
117+
}
118+
}
119+
120+
void OrbbecPointCloudGPU::_exit_tree() {
121+
if (rd != nullptr) {
122+
// delete buffer with num_points points
123+
rd->free_rid(point_buffer);
124+
}
125+
}
126+
127+
void OrbbecPointCloudGPU::set_rendering_device(RenderingDevice* rendering_device) {
128+
rd = rendering_device;
129+
}
130+
131+
RenderingDevice* OrbbecPointCloudGPU::get_rendering_device() {
132+
return rd;
133+
}
134+
135+
void OrbbecPointCloudGPU::_bind_methods() {
136+
godot::ClassDB::bind_method(D_METHOD("start_stream", "xres", "yres", "framerate"), &OrbbecPointCloudBase::start_stream);
137+
godot::ClassDB::bind_method(D_METHOD("get_rendering_device"), &OrbbecPointCloudGPU::get_rendering_device);
138+
godot::ClassDB::bind_method(D_METHOD("set_rendering_device", "p_rendering_device"), &OrbbecPointCloudGPU::set_rendering_device);
139+
godot::ClassDB::bind_method(D_METHOD("get_num_points"), &OrbbecPointCloudGPU::get_num_points);
140+
godot::ClassDB::bind_method(D_METHOD("get_point_buffer_rid"), &OrbbecPointCloudGPU::get_point_buffer_rid);
141+
godot::ClassDB::bind_method(D_METHOD("update_point_cloud_buffer"), &OrbbecPointCloudGPU::update_point_cloud_buffer);
142+
ADD_SIGNAL(MethodInfo("point_cloud_frame", PropertyInfo(Variant::RID, "point_cloud_buffer"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "orbbec_bytes")));
143+
}
144+
145+
RID OrbbecPointCloudGPU::get_point_buffer_rid() {
146+
return point_buffer;
147+
}
148+
149+
uint32_t OrbbecPointCloudGPU::get_num_points() {
150+
return xres*yres;
151+
}
152+
153+
void OrbbecPointCloudGPU::update_point_cloud_buffer() {
154+
155+
rd->buffer_update(point_buffer, 0, point_bytes.size(), point_bytes);
156+
// not sure we need call_deferred here.
157+
emit_signal("point_cloud_frame", point_buffer, point_bytes);
158+
}
159+
160+
void OrbbecPointCloudGPU::start_stream(int xres, int yres, int framerate) {
161+
if (rd == nullptr) {
162+
print_line("please set a rendering device before starting a point cloud stream");
163+
}
164+
if (!device) {
165+
print_line("Not starting stream, please set a device.");
166+
return;
167+
}
168+
try {
169+
point_cloud_filter->setCreatePointFormat(OB_FORMAT_POINT);
170+
pipeline = std::make_unique<ob::Pipeline>(device);
171+
pipeline->enableFrameSync();
172+
config = std::make_shared<ob::Config>();
173+
config->enableVideoStream(OB_STREAM_DEPTH, xres, yres, framerate, OB_FORMAT_ANY);
174+
config->setFrameAggregateOutputMode(OB_FRAME_AGGREGATE_OUTPUT_ALL_TYPE_FRAME_REQUIRE);
175+
this->xres = xres;
176+
this->yres = yres;
177+
allocate_point_cloud_buffer();
178+
pipeline->start(config, [&, this](std::shared_ptr<ob::FrameSet> frameSet) {
179+
auto frame = point_cloud_filter->process(frameSet)->as<ob::PointsFrame>();
180+
// we unfortunately need one cpu copy. The function that creates a gpu buffer from a uint8_t* exists in godot but
181+
// is not exposed in godot-cpp...
182+
std::memcpy(point_bytes.ptrw(), frame->getData(), point_bytes.size());
183+
// we can't update the point cloud buffer from a thread other than the main thread or the render thread.
184+
RenderingServer::get_singleton()->call_on_render_thread(Callable(this, "update_point_cloud_buffer"));
185+
});
186+
}
187+
catch(const std::exception & ex) {
188+
print_line(ex.what());
189+
// free the gpu buffer. if we excepted before it was allocated, it will just print an error on the godot side, no big deal.
190+
rd->free_rid(point_buffer);
191+
}
192+
}
193+
194+
OrbbecPointCloud::OrbbecPointCloud() {
195+
// create a random thinning mask.
196+
for (int i=0; i < thinning_mask_size; ++i) {
197+
thinning_mask[i] = static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
198+
}
199+
}
200+
201+
void OrbbecPointCloud::set_thinning(float thin) {
202+
thinning = thin;
203+
}
204+
205+
float OrbbecPointCloud::get_thinning() {
206+
return thinning;
207+
}
208+
209+
void OrbbecPointCloud::_bind_methods() {
210+
godot::ClassDB::bind_method(D_METHOD("get_thinning"), &OrbbecPointCloud::get_thinning);
211+
godot::ClassDB::bind_method(D_METHOD("start_stream", "xres", "yres", "framerate"), &OrbbecPointCloud::start_stream);
212+
godot::ClassDB::bind_method(D_METHOD("set_thinning", "p_thinning"), &OrbbecPointCloud::set_thinning);
213+
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "thinning"), "set_thinning", "get_thinning");
214+
ADD_SIGNAL(MethodInfo("point_cloud_frame", PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "points"), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "raw_buffer")));
215+
}
216+
112217
void OrbbecPointCloud::start_stream(int xres, int yres, int framerate) {
113218
if (!device) {
114219
print_line("Not starting stream, please set a device.");
115220
return;
116221
}
117222
try {
118223
// stolen from the savePointCloudToPly function of the orbbec sdk. I assume this value filters out irrelevant points ?
119-
// populate_device_from_idx(idx);
120224
constexpr auto min_point_value = 1e-6f;
121225
point_cloud_filter->setCreatePointFormat(OB_FORMAT_POINT);
122226
pipeline = std::make_unique<ob::Pipeline>(device);

src/orbbec.hpp

Lines changed: 99 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
#include "godot_cpp/classes/node.hpp"
44
#include "godot_cpp/classes/wrapped.hpp"
5+
#include "godot_cpp/classes/rendering_device.hpp"
56
#include "godot_cpp/variant/variant.hpp"
7+
#include "godot_cpp/variant/rid.hpp"
68
#include "godot_cpp/variant/char_string.hpp"
79
#include "godot_cpp/variant/transform3d.hpp"
810
#include <libobsensor/ObSensor.hpp>
@@ -46,52 +48,123 @@ class OrbbecDevices : public Node {
4648
};
4749

4850
/**
49-
* Node that gets pointcloud data from an orbbec camera. Use the OrbbecDevices node to query available nodes.
51+
* unused for now.
5052
*/
51-
class OrbbecPointCloud : public Node {
52-
GDCLASS(OrbbecPointCloud, Node)
53-
54-
protected:
55-
static void _bind_methods();
53+
enum DepthCamType {
54+
ORBBEC_FEMTO_MEGA
55+
};
5656

57-
public:
57+
/**
58+
* Base orbbec device class. Manages connection to devices.
59+
*/
60+
class OrbbecPointCloudBase: public Node {
61+
GDCLASS(OrbbecPointCloudBase, Node)
62+
public:
5863
/**
59-
* unused for now.
64+
* we only support orbbec femto megas for now.
6065
*/
61-
enum DepthCamType {
62-
ORBBEC_FEMTO_MEGA
63-
};
64-
OrbbecPointCloud();
65-
~OrbbecPointCloud() override = default;
66-
void print_hello();
66+
DepthCamType device_type = DepthCamType::ORBBEC_FEMTO_MEGA;
6767
void get_sensor_from_idx(uint32_t idx);
68-
void start_stream(int xres, int yres, int framerate);
68+
virtual void start_stream(int xres, int yres, int framerate) {};
6969
void stop_stream();
7070
PackedStringArray get_device_stream_formats();
7171
void set_device_from_ip(String ip);
7272
void set_device_from_serial_number(String serial_number);
73+
static void _bind_methods();
74+
75+
protected:
76+
std::unique_ptr<ob::Pipeline> pipeline;
77+
std::unique_ptr<ob::PointCloudFilter> point_cloud_filter = std::make_unique<ob::PointCloudFilter>();
78+
std::shared_ptr<ob::Device> device;
79+
std::shared_ptr<ob::FrameSet> data;
80+
std::shared_ptr<ob::Config> config;
81+
using predicate_type = std::function<bool(std::shared_ptr<ob::DeviceList>, uint32_t)>;
82+
void set_device_from_predicate(predicate_type predicate);
83+
void populate_device_from_idx(uint32_t idx);
84+
};
85+
86+
/**
87+
* Node that gets pointcloud data from an orbbec camera. Use the OrbbecDevices node to query available nodes.
88+
* Preprocess the data and passes it as PackedFloat32Array and PackedVector3Array. The preprocessing is done on
89+
* the CPU and can be slow.
90+
*/
91+
class OrbbecPointCloud : public OrbbecPointCloudBase{
92+
GDCLASS(OrbbecPointCloud, OrbbecPointCloudBase)
93+
94+
protected:
95+
static void _bind_methods();
96+
97+
public:
98+
OrbbecPointCloud();
99+
~OrbbecPointCloud() override = default;
73100
void set_thinning(float thinning);
74101
float get_thinning();
102+
void start_stream(int xres, int yres, int framerate) override;
75103
private:
76104
float thinning = 0.5;
77105
/**
78-
* we only support orbbec femto megas for now.
106+
* used to create raw multimesh buffers from point cloud data
79107
*/
80-
DepthCamType device_type = DepthCamType::ORBBEC_FEMTO_MEGA;
108+
const Transform3D identity_transform{};
109+
static constexpr size_t thinning_mask_size = 100000;
110+
static constexpr size_t floats_per_raw_point = 12;
111+
std::array<float, thinning_mask_size> thinning_mask;
112+
113+
};
114+
115+
constexpr uint32_t floats_per_points = 3;
116+
constexpr uint32_t bytes_per_float = 4;
117+
118+
/**
119+
* Upload point cloud data to a GPU SSBO without any preprocessing. You can use this assigning the RID of
120+
* the gpu buffer to a compute shader and call that compute shader from the point_cloud_frame signal.
121+
* You are responsible for the thinning and preprocessing.
122+
*
123+
* Make sure to call
124+
*/
125+
class OrbbecPointCloudGPU : public OrbbecPointCloudBase {
126+
GDCLASS(OrbbecPointCloudGPU, OrbbecPointCloudBase)
127+
128+
protected:
129+
static void _bind_methods();
130+
131+
public:
132+
~OrbbecPointCloudGPU() override = default;
133+
/**
134+
* Allocate a new RID for the GPU buffer on _enter_tree
135+
*/
136+
void _enter_tree() override;
137+
/**
138+
* Frees the GPU buffer on _exit_tree
139+
*/
140+
void _exit_tree() override;
141+
/**
142+
* returns the current GPU buffer's RID.
143+
* Do not store the RID, call this every time. if your node exists and reenters the tree
144+
* the *RID* will have changed and its not like copying a RID is expensive.
145+
*/
146+
RID get_point_buffer_rid();
147+
/**
148+
* gives the number of points that are uploaded to the gpu in the point_cloud SSBO.
149+
*/
150+
uint32_t get_num_points();
151+
void set_rendering_device(RenderingDevice* rd);
152+
RenderingDevice* get_rendering_device();
153+
void start_stream(int xres, int yres, int framerate) override;
154+
void update_point_cloud_buffer();
155+
private:
156+
void allocate_point_cloud_buffer();
157+
RID point_buffer{};
158+
PackedByteArray point_bytes{};
159+
RenderingDevice* rd = nullptr;
160+
float thinning = 0.5;
161+
uint32_t xres = 1024;
162+
uint32_t yres = 1024;
81163
/**
82164
* used to create raw multimesh buffers from point cloud data
83165
*/
84166
const Transform3D identity_transform{};
85167
static constexpr size_t thinning_mask_size = 100000;
86168
static constexpr size_t floats_per_raw_point = 12;
87169
std::array<float, thinning_mask_size> thinning_mask;
88-
std::unique_ptr<ob::Pipeline> pipeline;
89-
std::unique_ptr<ob::PointCloudFilter> point_cloud_filter = std::make_unique<ob::PointCloudFilter>();
90-
std::shared_ptr<ob::Device> device;
91-
std::shared_ptr<ob::FrameSet> data;
92-
std::shared_ptr<ob::Config> config;
93-
using predicate_type = std::function<bool(std::shared_ptr<ob::DeviceList>, uint32_t)>;
94-
void set_device_from_predicate(predicate_type predicate);
95-
96-
void populate_device_from_idx(uint32_t idx);
97170
};

src/register_types.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
1515
return;
1616
}
1717
GDREGISTER_CLASS(OrbbecDevices);
18-
GDREGISTER_CLASS(OrbbecPointCloud);
18+
GDREGISTER_CLASS(OrbbecPointCloudBase);
19+
GDREGISTER_CLASS(OrbbecPointCloud);
20+
GDREGISTER_CLASS(OrbbecPointCloudGPU);
1921
}
2022

2123
void uninitialize_gdextension_types(ModuleInitializationLevel p_level) {

0 commit comments

Comments
 (0)