gsplat 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +202 -0
  3. data/README.md +236 -0
  4. data/docs/ACCEPTANCE.md +60 -0
  5. data/docs/BENCHMARKS.md +79 -0
  6. data/docs/DECISIONS.md +52 -0
  7. data/docs/MIGRATION.md +117 -0
  8. data/docs/PROFILE.md +75 -0
  9. data/docs/PROGRESS.md +199 -0
  10. data/docs/decisions/0000-template.md +17 -0
  11. data/docs/decisions/0001-pin-golden-reference-and-cpu-validation.md +27 -0
  12. data/docs/decisions/0002-retain-ruby-fallbacks-for-native-backend.md +26 -0
  13. data/docs/decisions/0003-share-projection-for-distorted-cameras.md +26 -0
  14. data/docs/decisions/0004-use-portable-world-space-reference-paths.md +26 -0
  15. data/docs/decisions/0005-reuse-ewa-core-for-2dgs.md +24 -0
  16. data/docs/decisions/0006-keep-eval3d-as-portable-reference.md +25 -0
  17. data/docs/decisions/0007-share-compositor-semantics-for-contribution-indices.md +25 -0
  18. data/examples/data/README.md +14 -0
  19. data/examples/data/colmap/images/view_000.png +0 -0
  20. data/examples/data/colmap/images/view_001.png +0 -0
  21. data/examples/data/colmap/images/view_002.png +0 -0
  22. data/examples/data/colmap/sparse/0/cameras.txt +2 -0
  23. data/examples/data/colmap/sparse/0/images.txt +6 -0
  24. data/examples/data/colmap/sparse/0/points3D.txt +17 -0
  25. data/examples/data/splats.ply +0 -0
  26. data/examples/fit_image.rb +35 -0
  27. data/examples/generate_sample_data.rb +147 -0
  28. data/examples/render_path.rb +117 -0
  29. data/examples/simple_trainer.rb +76 -0
  30. data/ext/gsplat_native/common.h +54 -0
  31. data/ext/gsplat_native/extconf.rb +39 -0
  32. data/ext/gsplat_native/gsplat_native.c +60 -0
  33. data/ext/gsplat_native/intersections.c +211 -0
  34. data/ext/gsplat_native/projection.c +199 -0
  35. data/ext/gsplat_native/raster_backward.c +129 -0
  36. data/ext/gsplat_native/raster_backward_bridge.c +69 -0
  37. data/ext/gsplat_native/raster_forward.c +150 -0
  38. data/ext/gsplat_native/rasterization.h +51 -0
  39. data/ext/gsplat_native/spherical_harmonics.c +129 -0
  40. data/gsplat.gemspec +36 -0
  41. data/lib/gsplat/autograd/context.rb +60 -0
  42. data/lib/gsplat/autograd/function.rb +68 -0
  43. data/lib/gsplat/autograd/variable.rb +159 -0
  44. data/lib/gsplat/backend/ruby/accumulate.rb +139 -0
  45. data/lib/gsplat/backend/ruby/accumulate_backward.rb +40 -0
  46. data/lib/gsplat/backend/ruby/eval3d_rasterizer.rb +175 -0
  47. data/lib/gsplat/backend/ruby/isect_tiles.rb +198 -0
  48. data/lib/gsplat/backend/ruby/projection.rb +251 -0
  49. data/lib/gsplat/backend/ruby/projection_backward.rb +190 -0
  50. data/lib/gsplat/backend/ruby/projection_covariance_vjp.rb +72 -0
  51. data/lib/gsplat/backend/ruby/projection_input_vjp.rb +252 -0
  52. data/lib/gsplat/backend/ruby/quat_scale_to_covar_preci.rb +139 -0
  53. data/lib/gsplat/backend/ruby/rasterize_to_indices_in_range.rb +121 -0
  54. data/lib/gsplat/backend/ruby/rasterize_to_pixels.rb +199 -0
  55. data/lib/gsplat/backend/ruby/rasterize_to_pixels_backward.rb +121 -0
  56. data/lib/gsplat/backend/ruby/spherical_harmonics.rb +135 -0
  57. data/lib/gsplat/backend/ruby/tile_compositor.rb +50 -0
  58. data/lib/gsplat/backend/ruby/tile_compositor_backward.rb +84 -0
  59. data/lib/gsplat/backend.rb +79 -0
  60. data/lib/gsplat/compression/grid_sort.rb +79 -0
  61. data/lib/gsplat/compression/kmeans.rb +121 -0
  62. data/lib/gsplat/compression/png.rb +147 -0
  63. data/lib/gsplat/compression/png_codec.rb +133 -0
  64. data/lib/gsplat/compression/quantizer.rb +79 -0
  65. data/lib/gsplat/io/checkpoint.rb +145 -0
  66. data/lib/gsplat/io/colmap.rb +175 -0
  67. data/lib/gsplat/io/colmap_binary.rb +98 -0
  68. data/lib/gsplat/io/colmap_text.rb +84 -0
  69. data/lib/gsplat/io/image.rb +63 -0
  70. data/lib/gsplat/io/image_backends.rb +81 -0
  71. data/lib/gsplat/io/npy.rb +189 -0
  72. data/lib/gsplat/io/ply.rb +185 -0
  73. data/lib/gsplat/io/ply_reader.rb +142 -0
  74. data/lib/gsplat/io/zip_archive.rb +183 -0
  75. data/lib/gsplat/math/camera_distortion.rb +123 -0
  76. data/lib/gsplat/math/camera_projection.rb +202 -0
  77. data/lib/gsplat/math/mat.rb +114 -0
  78. data/lib/gsplat/math/quaternion.rb +175 -0
  79. data/lib/gsplat/math/small_matrix_primitives.rb +112 -0
  80. data/lib/gsplat/math/spherical_harmonic_basis.rb +148 -0
  81. data/lib/gsplat/math/ssim.rb +153 -0
  82. data/lib/gsplat/native.rb +30 -0
  83. data/lib/gsplat/native_ops.rb +148 -0
  84. data/lib/gsplat/native_raster_ops.rb +76 -0
  85. data/lib/gsplat/ops/accumulate.rb +68 -0
  86. data/lib/gsplat/ops/eval3d_rasterize.rb +62 -0
  87. data/lib/gsplat/ops/isect_tiles.rb +38 -0
  88. data/lib/gsplat/ops/projection.rb +154 -0
  89. data/lib/gsplat/ops/quat_scale_to_covar_preci.rb +118 -0
  90. data/lib/gsplat/ops/rasterize_to_indices_in_range.rb +38 -0
  91. data/lib/gsplat/ops/rasterize_to_pixels.rb +105 -0
  92. data/lib/gsplat/ops/relocation.rb +94 -0
  93. data/lib/gsplat/ops/spherical_harmonics.rb +71 -0
  94. data/lib/gsplat/ops/tensor_shape_ops.rb +173 -0
  95. data/lib/gsplat/ops/tensor_value_ops.rb +150 -0
  96. data/lib/gsplat/optim/adam.rb +212 -0
  97. data/lib/gsplat/optim/lr_scheduler.rb +36 -0
  98. data/lib/gsplat/optim/selective_adam.rb +68 -0
  99. data/lib/gsplat/rasterization.rb +130 -0
  100. data/lib/gsplat/rasterization_2dgs.rb +140 -0
  101. data/lib/gsplat/rasterization_helpers.rb +162 -0
  102. data/lib/gsplat/rasterization_validation.rb +99 -0
  103. data/lib/gsplat/strategy/base.rb +49 -0
  104. data/lib/gsplat/strategy/default.rb +188 -0
  105. data/lib/gsplat/strategy/mcmc.rb +103 -0
  106. data/lib/gsplat/strategy/mcmc_ops.rb +143 -0
  107. data/lib/gsplat/strategy/ops.rb +165 -0
  108. data/lib/gsplat/training/config.rb +91 -0
  109. data/lib/gsplat/training/image_fitter.rb +158 -0
  110. data/lib/gsplat/training/losses.rb +191 -0
  111. data/lib/gsplat/training/scene.rb +116 -0
  112. data/lib/gsplat/training/trainer.rb +236 -0
  113. data/lib/gsplat/utils.rb +110 -0
  114. data/lib/gsplat/version.rb +6 -0
  115. data/lib/gsplat.rb +98 -0
  116. metadata +181 -0
@@ -0,0 +1,211 @@
1
+ #include <math.h>
2
+ #include <stdint.h>
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+ #include "common.h"
6
+
7
+ typedef struct {
8
+ uint64_t *keys;
9
+ int32_t *flatten_ids;
10
+ size_t count;
11
+ } gs_sort_args;
12
+
13
+ static int
14
+ gs_tile_bits(int width, int height)
15
+ {
16
+ uint64_t count = (uint64_t)width * (uint64_t)height;
17
+ int bits = 0;
18
+ uint64_t capacity = 1;
19
+ while (capacity < count) {
20
+ capacity <<= 1;
21
+ bits += 1;
22
+ }
23
+ return bits;
24
+ }
25
+
26
+ static int
27
+ gs_clamp_tile(int value, int maximum)
28
+ {
29
+ if (value < 0) return 0;
30
+ if (value > maximum) return maximum;
31
+ return value;
32
+ }
33
+
34
+ static void
35
+ gs_bounds(
36
+ const float *means,
37
+ const float *radii,
38
+ int elliptical,
39
+ int tile_size,
40
+ int tile_width,
41
+ int tile_height,
42
+ int *bounds
43
+ )
44
+ {
45
+ float radius_x = radii[0];
46
+ float radius_y = elliptical ? radii[1] : radius_x;
47
+ if (radius_x <= 0.0f || radius_y <= 0.0f) {
48
+ memset(bounds, 0, 4 * sizeof(int));
49
+ return;
50
+ }
51
+ bounds[0] = gs_clamp_tile((int)floorf((means[0] - radius_x) / tile_size), tile_width);
52
+ bounds[1] = gs_clamp_tile((int)floorf((means[1] - radius_y) / tile_size), tile_height);
53
+ bounds[2] = gs_clamp_tile((int)ceilf((means[0] + radius_x) / tile_size), tile_width);
54
+ bounds[3] = gs_clamp_tile((int)ceilf((means[1] + radius_y) / tile_size), tile_height);
55
+ }
56
+
57
+ static void *
58
+ gs_radix_sort_without_gvl(void *opaque)
59
+ {
60
+ gs_sort_args *args = (gs_sort_args *)opaque;
61
+ uint64_t *temporary_keys = malloc(args->count * sizeof(uint64_t));
62
+ int32_t *temporary_ids = malloc(args->count * sizeof(int32_t));
63
+ if (temporary_keys == NULL || temporary_ids == NULL) {
64
+ free(temporary_keys);
65
+ free(temporary_ids);
66
+ return (void *)1;
67
+ }
68
+ uint64_t *source_keys = args->keys;
69
+ uint64_t *destination_keys = temporary_keys;
70
+ int32_t *source_ids = args->flatten_ids;
71
+ int32_t *destination_ids = temporary_ids;
72
+ for (int pass = 0; pass < 8; ++pass) {
73
+ size_t counts[256] = {0};
74
+ int shift = pass * 8;
75
+ for (size_t index = 0; index < args->count; ++index) {
76
+ counts[(source_keys[index] >> shift) & 0xffu] += 1;
77
+ }
78
+ size_t running = 0;
79
+ for (int bucket = 0; bucket < 256; ++bucket) {
80
+ size_t value = counts[bucket];
81
+ counts[bucket] = running;
82
+ running += value;
83
+ }
84
+ for (size_t index = 0; index < args->count; ++index) {
85
+ int bucket = (source_keys[index] >> shift) & 0xffu;
86
+ size_t output_index = counts[bucket]++;
87
+ destination_keys[output_index] = source_keys[index];
88
+ destination_ids[output_index] = source_ids[index];
89
+ }
90
+ uint64_t *key_swap = source_keys;
91
+ source_keys = destination_keys;
92
+ destination_keys = key_swap;
93
+ int32_t *id_swap = source_ids;
94
+ source_ids = destination_ids;
95
+ destination_ids = id_swap;
96
+ }
97
+ free(temporary_keys);
98
+ free(temporary_ids);
99
+ return NULL;
100
+ }
101
+
102
+ static VALUE
103
+ gs_isect_tiles(int argc, VALUE *argv, VALUE self)
104
+ {
105
+ (void)self;
106
+ if (argc != 7) rb_raise(rb_eArgError, "expected 7 intersection arguments");
107
+ VALUE means2d = argv[0], radii = argv[1], depths = argv[2];
108
+ gs_require_sfloat(means2d, "means2d");
109
+ gs_require_sfloat(radii, "radii");
110
+ gs_require_sfloat(depths, "depths");
111
+ int tile_size = NUM2INT(argv[3]), tile_width = NUM2INT(argv[4]), tile_height = NUM2INT(argv[5]);
112
+ int sort = RTEST(argv[6]);
113
+ narray_t *mean_array = gs_narray(means2d);
114
+ narray_t *radius_array = gs_narray(radii);
115
+ size_t cameras = mean_array->shape[0], gaussians = mean_array->shape[1];
116
+ int elliptical = radius_array->ndim == 3;
117
+ size_t shape_cn[2] = {cameras, gaussians};
118
+ VALUE tiles_per = nary_new(numo_cInt32, 2, shape_cn);
119
+ int32_t *tile_counts = (int32_t *)na_get_pointer_for_write(tiles_per);
120
+ const float *mean_values = GS_SFLOAT_READ(means2d);
121
+ const float *radius_values = GS_SFLOAT_READ(radii);
122
+ size_t total = 0;
123
+ for (size_t index = 0; index < cameras * gaussians; ++index) {
124
+ int bounds[4];
125
+ gs_bounds(
126
+ mean_values + index * 2,
127
+ radius_values + index * (elliptical ? 2 : 1),
128
+ elliptical, tile_size, tile_width, tile_height, bounds
129
+ );
130
+ int count = (bounds[2] - bounds[0]) * (bounds[3] - bounds[1]);
131
+ tile_counts[index] = count;
132
+ total += count;
133
+ }
134
+ size_t shape_total[1] = {total};
135
+ VALUE keys = nary_new(numo_cInt64, 1, shape_total);
136
+ VALUE flatten_ids = nary_new(numo_cInt32, 1, shape_total);
137
+ uint64_t *key_values = (uint64_t *)na_get_pointer_for_write(keys);
138
+ int32_t *id_values = (int32_t *)na_get_pointer_for_write(flatten_ids);
139
+ const float *depth_values = GS_SFLOAT_READ(depths);
140
+ int tile_bits = gs_tile_bits(tile_width, tile_height);
141
+ size_t write_index = 0;
142
+ for (size_t camera = 0; camera < cameras; ++camera) {
143
+ for (size_t gaussian = 0; gaussian < gaussians; ++gaussian) {
144
+ size_t index = camera * gaussians + gaussian;
145
+ if (tile_counts[index] == 0) continue;
146
+ int bounds[4];
147
+ gs_bounds(
148
+ mean_values + index * 2,
149
+ radius_values + index * (elliptical ? 2 : 1),
150
+ elliptical, tile_size, tile_width, tile_height, bounds
151
+ );
152
+ union { float value; uint32_t bits; } depth = {depth_values[index]};
153
+ uint64_t upper_camera = camera << tile_bits;
154
+ for (int tile_y = bounds[1]; tile_y < bounds[3]; ++tile_y) {
155
+ for (int tile_x = bounds[0]; tile_x < bounds[2]; ++tile_x) {
156
+ uint64_t tile_id = (uint64_t)tile_y * tile_width + tile_x;
157
+ key_values[write_index] = ((upper_camera | tile_id) << 32) | depth.bits;
158
+ id_values[write_index] = (int32_t)index;
159
+ write_index += 1;
160
+ }
161
+ }
162
+ }
163
+ }
164
+ if (sort && total > 1) {
165
+ gs_sort_args args = {key_values, id_values, total};
166
+ void *failed = rb_thread_call_without_gvl(gs_radix_sort_without_gvl, &args, RUBY_UBF_IO, NULL);
167
+ if (failed != NULL) rb_raise(rb_eNoMemError, "native radix sort allocation failed");
168
+ }
169
+ return rb_ary_new_from_args(3, tiles_per, keys, flatten_ids);
170
+ }
171
+
172
+ static VALUE
173
+ gs_offset_encode(VALUE self, VALUE keys, VALUE cameras_value, VALUE width_value, VALUE height_value)
174
+ {
175
+ (void)self;
176
+ int cameras = NUM2INT(cameras_value), width = NUM2INT(width_value), height = NUM2INT(height_value);
177
+ int tile_bits = gs_tile_bits(width, height);
178
+ uint64_t tile_mask = tile_bits == 0 ? 0 : ((1ULL << tile_bits) - 1);
179
+ size_t tile_count = (size_t)cameras * width * height;
180
+ size_t *counts = ALLOC_N(size_t, tile_count);
181
+ memset(counts, 0, tile_count * sizeof(size_t));
182
+ narray_t *key_array = gs_narray(keys);
183
+ const uint64_t *key_values = (const uint64_t *)na_get_pointer_for_read(keys);
184
+ for (size_t index = 0; index < key_array->size; ++index) {
185
+ uint64_t upper = key_values[index] >> 32;
186
+ uint64_t camera = upper >> tile_bits;
187
+ uint64_t tile = upper & tile_mask;
188
+ if (camera >= (uint64_t)cameras || tile >= (uint64_t)(width * height)) {
189
+ xfree(counts);
190
+ rb_raise(rb_eArgError, "intersection key is outside the tile grid");
191
+ }
192
+ counts[camera * width * height + tile] += 1;
193
+ }
194
+ size_t shape[3] = {(size_t)cameras, (size_t)height, (size_t)width};
195
+ VALUE offsets = nary_new(numo_cInt32, 3, shape);
196
+ int32_t *offset_values = (int32_t *)na_get_pointer_for_write(offsets);
197
+ size_t running = 0;
198
+ for (size_t index = 0; index < tile_count; ++index) {
199
+ offset_values[index] = (int32_t)running;
200
+ running += counts[index];
201
+ }
202
+ xfree(counts);
203
+ return offsets;
204
+ }
205
+
206
+ void
207
+ gs_init_intersections(VALUE native)
208
+ {
209
+ rb_define_singleton_method(native, "isect_tiles_sfloat", gs_isect_tiles, -1);
210
+ rb_define_singleton_method(native, "isect_offset_encode_int64", gs_offset_encode, 4);
211
+ }
@@ -0,0 +1,199 @@
1
+ #include <math.h>
2
+ #include <string.h>
3
+ #include "common.h"
4
+
5
+ typedef struct {
6
+ size_t cameras;
7
+ size_t gaussians;
8
+ int width;
9
+ int height;
10
+ int orthographic;
11
+ int compensation;
12
+ float eps2d;
13
+ float near_plane;
14
+ float far_plane;
15
+ float radius_clip;
16
+ const float *means;
17
+ const float *covars;
18
+ const float *views;
19
+ const float *intrinsics;
20
+ int32_t *radii;
21
+ float *means2d;
22
+ float *depths;
23
+ float *conics;
24
+ float *compensations;
25
+ } gs_projection_args;
26
+
27
+ static void
28
+ gs_mat3_product(const float *left, const float *right, float *output)
29
+ {
30
+ for (int row = 0; row < 3; ++row) {
31
+ for (int column = 0; column < 3; ++column) {
32
+ float value = 0.0f;
33
+ for (int inner = 0; inner < 3; ++inner) {
34
+ value += left[row * 3 + inner] * right[inner * 3 + column];
35
+ }
36
+ output[row * 3 + column] = value;
37
+ }
38
+ }
39
+ }
40
+
41
+ static void
42
+ gs_camera_covar(const float *rotation, const float *covar, float *output)
43
+ {
44
+ float intermediate[9], transpose[9];
45
+ gs_mat3_product(rotation, covar, intermediate);
46
+ for (int row = 0; row < 3; ++row) {
47
+ for (int column = 0; column < 3; ++column) {
48
+ transpose[row * 3 + column] = rotation[column * 3 + row];
49
+ }
50
+ }
51
+ gs_mat3_product(intermediate, transpose, output);
52
+ }
53
+
54
+ static float
55
+ gs_clip(float value, float minimum, float maximum)
56
+ {
57
+ return fminf(fmaxf(value, minimum), maximum);
58
+ }
59
+
60
+ static void
61
+ gs_project_one(gs_projection_args *args, size_t camera, size_t gaussian)
62
+ {
63
+ const float *view = args->views + camera * 16;
64
+ const float *intrinsic = args->intrinsics + camera * 9;
65
+ const float *mean = args->means + gaussian * 3;
66
+ const float *covar = args->covars + gaussian * 9;
67
+ float rotation[9] = {
68
+ view[0], view[1], view[2],
69
+ view[4], view[5], view[6],
70
+ view[8], view[9], view[10]
71
+ };
72
+ float camera_mean[3];
73
+ for (int row = 0; row < 3; ++row) {
74
+ camera_mean[row] =
75
+ rotation[row * 3] * mean[0] +
76
+ rotation[row * 3 + 1] * mean[1] +
77
+ rotation[row * 3 + 2] * mean[2] +
78
+ view[row * 4 + 3];
79
+ }
80
+ float camera_covar[9];
81
+ gs_camera_covar(rotation, covar, camera_covar);
82
+ float depth = camera_mean[2];
83
+ float safe_z = (depth <= args->near_plane || depth >= args->far_plane) ? 1.0f : depth;
84
+ float jacobian[6] = {0};
85
+ float projected[2];
86
+ if (args->orthographic) {
87
+ jacobian[0] = intrinsic[0];
88
+ jacobian[4] = intrinsic[4];
89
+ projected[0] = camera_mean[0] * intrinsic[0] + intrinsic[2];
90
+ projected[1] = camera_mean[1] * intrinsic[4] + intrinsic[5];
91
+ } else {
92
+ float minimum_x = -(intrinsic[2] / intrinsic[0] + 0.15f * args->width / intrinsic[0]);
93
+ float maximum_x = (args->width - intrinsic[2]) / intrinsic[0] + 0.15f * args->width / intrinsic[0];
94
+ float minimum_y = -(intrinsic[5] / intrinsic[4] + 0.15f * args->height / intrinsic[4]);
95
+ float maximum_y = (args->height - intrinsic[5]) / intrinsic[4] + 0.15f * args->height / intrinsic[4];
96
+ float clipped_x = gs_clip(camera_mean[0] / safe_z, minimum_x, maximum_x) * safe_z;
97
+ float clipped_y = gs_clip(camera_mean[1] / safe_z, minimum_y, maximum_y) * safe_z;
98
+ jacobian[0] = intrinsic[0] / safe_z;
99
+ jacobian[2] = -intrinsic[0] * clipped_x / (safe_z * safe_z);
100
+ jacobian[4] = intrinsic[4] / safe_z;
101
+ jacobian[5] = -intrinsic[4] * clipped_y / (safe_z * safe_z);
102
+ projected[0] =
103
+ (intrinsic[0] * camera_mean[0] + intrinsic[1] * camera_mean[1] + intrinsic[2] * safe_z) / safe_z;
104
+ projected[1] =
105
+ (intrinsic[3] * camera_mean[0] + intrinsic[4] * camera_mean[1] + intrinsic[5] * safe_z) / safe_z;
106
+ }
107
+ float covar2d[4] = {0};
108
+ for (int row = 0; row < 2; ++row) {
109
+ for (int column = 0; column < 2; ++column) {
110
+ for (int left = 0; left < 3; ++left) {
111
+ for (int right = 0; right < 3; ++right) {
112
+ covar2d[row * 2 + column] +=
113
+ jacobian[row * 3 + left] * camera_covar[left * 3 + right] *
114
+ jacobian[column * 3 + right];
115
+ }
116
+ }
117
+ }
118
+ }
119
+ float original_det = covar2d[0] * covar2d[3] - covar2d[1] * covar2d[2];
120
+ covar2d[0] += args->eps2d;
121
+ covar2d[3] += args->eps2d;
122
+ float determinant = covar2d[0] * covar2d[3] - covar2d[1] * covar2d[2];
123
+ float safe_det = determinant <= 0.0f ? 1e-10f : determinant;
124
+ int32_t radius_x = (int32_t)ceilf(3.33f * sqrtf(fmaxf(covar2d[0], 0.0f)));
125
+ int32_t radius_y = (int32_t)ceilf(3.33f * sqrtf(fmaxf(covar2d[3], 0.0f)));
126
+ int visible = determinant > 0.0f && depth > args->near_plane && depth < args->far_plane &&
127
+ (radius_x > args->radius_clip || radius_y > args->radius_clip) &&
128
+ projected[0] + radius_x > 0.0f && projected[0] - radius_x < args->width &&
129
+ projected[1] + radius_y > 0.0f && projected[1] - radius_y < args->height;
130
+ size_t index = camera * args->gaussians + gaussian;
131
+ args->radii[index * 2] = visible ? radius_x : 0;
132
+ args->radii[index * 2 + 1] = visible ? radius_y : 0;
133
+ args->means2d[index * 2] = projected[0];
134
+ args->means2d[index * 2 + 1] = projected[1];
135
+ args->depths[index] = depth;
136
+ args->conics[index * 3] = covar2d[3] / safe_det;
137
+ args->conics[index * 3 + 1] = -(covar2d[1] + covar2d[2]) / (2.0f * safe_det);
138
+ args->conics[index * 3 + 2] = covar2d[0] / safe_det;
139
+ if (args->compensation) {
140
+ args->compensations[index] = sqrtf(fmaxf(original_det / safe_det, 0.0f));
141
+ }
142
+ }
143
+
144
+ static void *
145
+ gs_projection_without_gvl(void *opaque)
146
+ {
147
+ gs_projection_args *args = (gs_projection_args *)opaque;
148
+ size_t count = args->cameras * args->gaussians;
149
+ #ifdef GSPLAT_OPENMP
150
+ #pragma omp parallel for
151
+ #endif
152
+ for (size_t index = 0; index < count; ++index) {
153
+ gs_project_one(args, index / args->gaussians, index % args->gaussians);
154
+ }
155
+ return NULL;
156
+ }
157
+
158
+ static VALUE
159
+ gs_projection_forward(int argc, VALUE *argv, VALUE self)
160
+ {
161
+ (void)self;
162
+ if (argc != 12) rb_raise(rb_eArgError, "expected 12 projection arguments");
163
+ VALUE means = argv[0], covars = argv[1], views = argv[2], intrinsics = argv[3];
164
+ VALUE width = argv[4], height = argv[5], eps2d = argv[6], near_plane = argv[7];
165
+ VALUE far_plane = argv[8], radius_clip = argv[9], compensation = argv[10], orthographic = argv[11];
166
+ gs_require_sfloat(means, "means");
167
+ gs_require_sfloat(covars, "covars");
168
+ gs_require_sfloat(views, "viewmats");
169
+ gs_require_sfloat(intrinsics, "intrinsics");
170
+ narray_t *mean_array = gs_narray(means);
171
+ narray_t *view_array = gs_narray(views);
172
+ size_t cameras = view_array->shape[0], gaussians = mean_array->shape[0];
173
+ size_t shape_cn[2] = {cameras, gaussians};
174
+ size_t shape_cn2[3] = {cameras, gaussians, 2};
175
+ size_t shape_cn3[3] = {cameras, gaussians, 3};
176
+ VALUE radii = nary_new(numo_cInt32, 3, shape_cn2);
177
+ VALUE means2d = nary_new(numo_cSFloat, 3, shape_cn2);
178
+ VALUE depths = nary_new(numo_cSFloat, 2, shape_cn);
179
+ VALUE conics = nary_new(numo_cSFloat, 3, shape_cn3);
180
+ VALUE compensations = RTEST(compensation) ? nary_new(numo_cSFloat, 2, shape_cn) : Qnil;
181
+ gs_projection_args args = {
182
+ cameras, gaussians, NUM2INT(width), NUM2INT(height),
183
+ RTEST(orthographic), RTEST(compensation),
184
+ NUM2DBL(eps2d), NUM2DBL(near_plane), NUM2DBL(far_plane), NUM2DBL(radius_clip),
185
+ GS_SFLOAT_READ(means), GS_SFLOAT_READ(covars), GS_SFLOAT_READ(views),
186
+ GS_SFLOAT_READ(intrinsics),
187
+ (int32_t *)na_get_pointer_for_write(radii),
188
+ GS_SFLOAT_WRITE(means2d), GS_SFLOAT_WRITE(depths), GS_SFLOAT_WRITE(conics),
189
+ NIL_P(compensations) ? NULL : GS_SFLOAT_WRITE(compensations)
190
+ };
191
+ rb_thread_call_without_gvl(gs_projection_without_gvl, &args, RUBY_UBF_IO, NULL);
192
+ return rb_ary_new_from_args(5, radii, means2d, depths, conics, compensations);
193
+ }
194
+
195
+ void
196
+ gs_init_projection(VALUE native)
197
+ {
198
+ rb_define_singleton_method(native, "projection_forward_sfloat", gs_projection_forward, -1);
199
+ }
@@ -0,0 +1,129 @@
1
+ #include <math.h>
2
+ #include "rasterization.h"
3
+
4
+ #ifdef GSPLAT_OPENMP
5
+ #define GS_ATOMIC_ADD(target, value) do { \
6
+ _Pragma("omp atomic update") \
7
+ (target) += (value); \
8
+ } while (0)
9
+ #else
10
+ #define GS_ATOMIC_ADD(target, value) ((target) += (value))
11
+ #endif
12
+
13
+ static void
14
+ gs_raster_backward_pixel(
15
+ gs_raster_backward_args *args,
16
+ size_t camera,
17
+ int x,
18
+ int y,
19
+ size_t range_start,
20
+ size_t range_end
21
+ )
22
+ {
23
+ gs_raster_forward_args *forward = &args->forward;
24
+ size_t pixel = (camera * forward->height + (size_t)y) * forward->width + (size_t)x;
25
+ const float *pixel_color_grad = args->grad_render_colors + pixel * forward->channels;
26
+ float current_transmittance = 1.0f - args->render_alphas[pixel];
27
+ float after_transmittance = 1.0f;
28
+ float remaining_color[forward->channels];
29
+ for (size_t channel = 0; channel < forward->channels; ++channel) {
30
+ remaining_color[channel] = forward->backgrounds == NULL ?
31
+ 0.0f : forward->backgrounds[camera * forward->channels + channel];
32
+ if (args->grad_backgrounds != NULL) {
33
+ GS_ATOMIC_ADD(
34
+ args->grad_backgrounds[camera * forward->channels + channel],
35
+ pixel_color_grad[channel] * current_transmittance
36
+ );
37
+ }
38
+ }
39
+ size_t tile = (camera * forward->tile_height + y / forward->tile_size) *
40
+ forward->tile_width + x / forward->tile_size;
41
+ if (forward->masks != NULL && forward->masks[tile] == 0) return;
42
+ for (size_t cursor = range_end; cursor > range_start; --cursor) {
43
+ size_t intersection = cursor - 1;
44
+ int32_t gaussian = forward->flatten_ids[intersection];
45
+ float delta_x = forward->means[gaussian * 2] - ((float)x + 0.5f);
46
+ float delta_y = forward->means[gaussian * 2 + 1] - ((float)y + 0.5f);
47
+ const float *conic = forward->conics + gaussian * 3;
48
+ float sigma = 0.5f * (
49
+ conic[0] * delta_x * delta_x + conic[2] * delta_y * delta_y
50
+ ) + conic[1] * delta_x * delta_y;
51
+ float response = expf(-sigma);
52
+ float raw_alpha = forward->opacities[gaussian] * response;
53
+ float alpha = raw_alpha > GS_ALPHA_CLAMP ? GS_ALPHA_CLAMP : raw_alpha;
54
+ int valid = args->render_alphas[pixel] > 0.0f && sigma >= 0.0f &&
55
+ alpha >= GS_ALPHA_SKIP && args->last_ids[pixel] >= (int32_t)intersection &&
56
+ current_transmittance < 1.0f && alpha < 1.0f;
57
+ if (!valid) continue;
58
+ float transmittance_before = current_transmittance / (1.0f - alpha);
59
+ float visibility = transmittance_before * alpha;
60
+ const float *color = forward->colors + gaussian * forward->channels;
61
+ float color_dot = 0.0f;
62
+ for (size_t channel = 0; channel < forward->channels; ++channel) {
63
+ GS_ATOMIC_ADD(
64
+ args->grad_colors[gaussian * forward->channels + channel],
65
+ pixel_color_grad[channel] * visibility
66
+ );
67
+ color_dot += pixel_color_grad[channel] * (color[channel] - remaining_color[channel]);
68
+ }
69
+ float grad_alpha = transmittance_before * (
70
+ color_dot + args->grad_render_alphas[pixel] * after_transmittance
71
+ );
72
+ float grad_raw_alpha = raw_alpha < GS_ALPHA_CLAMP ? grad_alpha : 0.0f;
73
+ GS_ATOMIC_ADD(args->grad_opacities[gaussian], grad_raw_alpha * response);
74
+ float grad_sigma = -grad_raw_alpha * raw_alpha;
75
+ float mean_x = grad_sigma * (conic[0] * delta_x + conic[1] * delta_y);
76
+ float mean_y = grad_sigma * (conic[2] * delta_y + conic[1] * delta_x);
77
+ GS_ATOMIC_ADD(args->grad_means[gaussian * 2], mean_x);
78
+ GS_ATOMIC_ADD(args->grad_means[gaussian * 2 + 1], mean_y);
79
+ if (args->grad_means_abs != NULL) {
80
+ GS_ATOMIC_ADD(args->grad_means_abs[gaussian * 2], fabsf(mean_x));
81
+ GS_ATOMIC_ADD(args->grad_means_abs[gaussian * 2 + 1], fabsf(mean_y));
82
+ }
83
+ GS_ATOMIC_ADD(args->grad_conics[gaussian * 3], grad_sigma * 0.5f * delta_x * delta_x);
84
+ GS_ATOMIC_ADD(args->grad_conics[gaussian * 3 + 1], grad_sigma * delta_x * delta_y);
85
+ GS_ATOMIC_ADD(args->grad_conics[gaussian * 3 + 2], grad_sigma * 0.5f * delta_y * delta_y);
86
+ for (size_t channel = 0; channel < forward->channels; ++channel) {
87
+ remaining_color[channel] =
88
+ alpha * color[channel] + (1.0f - alpha) * remaining_color[channel];
89
+ }
90
+ after_transmittance *= 1.0f - alpha;
91
+ current_transmittance = transmittance_before;
92
+ }
93
+ }
94
+
95
+ void *
96
+ gs_raster_backward_without_gvl(void *opaque)
97
+ {
98
+ gs_raster_backward_args *args = (gs_raster_backward_args *)opaque;
99
+ gs_raster_forward_args *forward = &args->forward;
100
+ size_t tiles_per_camera = (size_t)forward->tile_width * forward->tile_height;
101
+ size_t total_tiles = forward->cameras * tiles_per_camera;
102
+ #ifdef GSPLAT_OPENMP
103
+ #pragma omp parallel for schedule(dynamic, 1)
104
+ #endif
105
+ for (size_t tile = 0; tile < total_tiles; ++tile) {
106
+ size_t camera = tile / tiles_per_camera;
107
+ size_t local_tile = tile % tiles_per_camera;
108
+ int tile_y = (int)(local_tile / forward->tile_width);
109
+ int tile_x = (int)(local_tile % forward->tile_width);
110
+ int x_start = tile_x * forward->tile_size;
111
+ int y_start = tile_y * forward->tile_size;
112
+ int x_end = x_start + forward->tile_size;
113
+ int y_end = y_start + forward->tile_size;
114
+ if (x_end > forward->width) x_end = forward->width;
115
+ if (y_end > forward->height) y_end = forward->height;
116
+ if (x_start >= forward->width || y_start >= forward->height) continue;
117
+ size_t range_start = (size_t)forward->offsets[tile];
118
+ size_t range_end = tile + 1 < total_tiles ?
119
+ (size_t)forward->offsets[tile + 1] : forward->intersection_count;
120
+ int masked = forward->masks != NULL && forward->masks[tile] == 0;
121
+ if ((masked || range_start == range_end) && args->grad_backgrounds == NULL) continue;
122
+ for (int y = y_start; y < y_end; ++y) {
123
+ for (int x = x_start; x < x_end; ++x) {
124
+ gs_raster_backward_pixel(args, camera, x, y, range_start, range_end);
125
+ }
126
+ }
127
+ }
128
+ return NULL;
129
+ }
@@ -0,0 +1,69 @@
1
+ #include <string.h>
2
+ #include "common.h"
3
+ #include "rasterization.h"
4
+
5
+ static VALUE
6
+ gs_zero_sfloat(int dimensions, size_t *shape)
7
+ {
8
+ VALUE output = nary_new(numo_cSFloat, dimensions, shape);
9
+ memset(GS_SFLOAT_WRITE(output), 0, gs_narray(output)->size * sizeof(float));
10
+ return output;
11
+ }
12
+
13
+ static VALUE
14
+ gs_raster_backward(int argc, VALUE *argv, VALUE self)
15
+ {
16
+ (void)self;
17
+ if (argc != 16) rb_raise(rb_eArgError, "expected 16 raster backward arguments");
18
+ int sfloat_indices[] = {0, 1, 2, 3, 11, 13, 14};
19
+ for (size_t index = 0; index < sizeof(sfloat_indices) / sizeof(int); ++index) {
20
+ gs_require_sfloat(argv[sfloat_indices[index]], "raster input");
21
+ }
22
+ if (!NIL_P(argv[4])) gs_require_sfloat(argv[4], "backgrounds");
23
+ if (!NIL_P(argv[5])) gs_require_int32(argv[5], "masks");
24
+ gs_require_int32(argv[9], "isect_offsets");
25
+ gs_require_int32(argv[10], "flatten_ids");
26
+ gs_require_int32(argv[12], "last_ids");
27
+ narray_t *means = gs_narray(argv[0]);
28
+ narray_t *colors = gs_narray(argv[2]);
29
+ narray_t *offsets = gs_narray(argv[9]);
30
+ size_t means_shape[3] = {means->shape[0], means->shape[1], 2};
31
+ size_t conic_shape[3] = {means->shape[0], means->shape[1], 3};
32
+ size_t color_shape[3] = {means->shape[0], means->shape[1], colors->shape[2]};
33
+ size_t opacity_shape[2] = {means->shape[0], means->shape[1]};
34
+ size_t background_shape[2] = {means->shape[0], colors->shape[2]};
35
+ VALUE grad_means = gs_zero_sfloat(3, means_shape);
36
+ VALUE grad_conics = gs_zero_sfloat(3, conic_shape);
37
+ VALUE grad_colors = gs_zero_sfloat(3, color_shape);
38
+ VALUE grad_opacities = gs_zero_sfloat(2, opacity_shape);
39
+ VALUE grad_backgrounds = NIL_P(argv[4]) ? Qnil : gs_zero_sfloat(2, background_shape);
40
+ VALUE grad_means_abs = RTEST(argv[15]) ? gs_zero_sfloat(3, means_shape) : Qnil;
41
+ gs_raster_backward_args args = {
42
+ {
43
+ GS_SFLOAT_READ(argv[0]), GS_SFLOAT_READ(argv[1]), GS_SFLOAT_READ(argv[2]),
44
+ GS_SFLOAT_READ(argv[3]), NIL_P(argv[4]) ? NULL : GS_SFLOAT_READ(argv[4]),
45
+ NIL_P(argv[5]) ? NULL : GS_INT32_READ(argv[5]), GS_INT32_READ(argv[9]),
46
+ GS_INT32_READ(argv[10]), NULL, NULL, NULL, means->shape[0], means->shape[1],
47
+ colors->shape[2], gs_narray(argv[10])->size, NUM2INT(argv[6]), NUM2INT(argv[7]),
48
+ NUM2INT(argv[8]), (int)offsets->shape[2], (int)offsets->shape[1]
49
+ },
50
+ GS_SFLOAT_READ(argv[11]), GS_INT32_READ(argv[12]), GS_SFLOAT_READ(argv[13]),
51
+ GS_SFLOAT_READ(argv[14]), GS_SFLOAT_WRITE(grad_means), GS_SFLOAT_WRITE(grad_conics),
52
+ GS_SFLOAT_WRITE(grad_colors), GS_SFLOAT_WRITE(grad_opacities),
53
+ NIL_P(grad_backgrounds) ? NULL : GS_SFLOAT_WRITE(grad_backgrounds),
54
+ NIL_P(grad_means_abs) ? NULL : GS_SFLOAT_WRITE(grad_means_abs)
55
+ };
56
+ rb_thread_call_without_gvl(gs_raster_backward_without_gvl, &args, RUBY_UBF_IO, NULL);
57
+ VALUE gradients = rb_ary_new_from_args(
58
+ 5, grad_means, grad_conics, grad_colors, grad_opacities, grad_backgrounds
59
+ );
60
+ RB_GC_GUARD(argv[0]);
61
+ RB_GC_GUARD(gradients);
62
+ return rb_ary_new_from_args(2, gradients, grad_means_abs);
63
+ }
64
+
65
+ void
66
+ gs_init_rasterization_backward(VALUE native)
67
+ {
68
+ rb_define_singleton_method(native, "rasterize_backward_sfloat", gs_raster_backward, -1);
69
+ }