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.
- checksums.yaml +7 -0
- data/LICENSE.txt +202 -0
- data/README.md +236 -0
- data/docs/ACCEPTANCE.md +60 -0
- data/docs/BENCHMARKS.md +79 -0
- data/docs/DECISIONS.md +52 -0
- data/docs/MIGRATION.md +117 -0
- data/docs/PROFILE.md +75 -0
- data/docs/PROGRESS.md +199 -0
- data/docs/decisions/0000-template.md +17 -0
- data/docs/decisions/0001-pin-golden-reference-and-cpu-validation.md +27 -0
- data/docs/decisions/0002-retain-ruby-fallbacks-for-native-backend.md +26 -0
- data/docs/decisions/0003-share-projection-for-distorted-cameras.md +26 -0
- data/docs/decisions/0004-use-portable-world-space-reference-paths.md +26 -0
- data/docs/decisions/0005-reuse-ewa-core-for-2dgs.md +24 -0
- data/docs/decisions/0006-keep-eval3d-as-portable-reference.md +25 -0
- data/docs/decisions/0007-share-compositor-semantics-for-contribution-indices.md +25 -0
- data/examples/data/README.md +14 -0
- data/examples/data/colmap/images/view_000.png +0 -0
- data/examples/data/colmap/images/view_001.png +0 -0
- data/examples/data/colmap/images/view_002.png +0 -0
- data/examples/data/colmap/sparse/0/cameras.txt +2 -0
- data/examples/data/colmap/sparse/0/images.txt +6 -0
- data/examples/data/colmap/sparse/0/points3D.txt +17 -0
- data/examples/data/splats.ply +0 -0
- data/examples/fit_image.rb +35 -0
- data/examples/generate_sample_data.rb +147 -0
- data/examples/render_path.rb +117 -0
- data/examples/simple_trainer.rb +76 -0
- data/ext/gsplat_native/common.h +54 -0
- data/ext/gsplat_native/extconf.rb +39 -0
- data/ext/gsplat_native/gsplat_native.c +60 -0
- data/ext/gsplat_native/intersections.c +211 -0
- data/ext/gsplat_native/projection.c +199 -0
- data/ext/gsplat_native/raster_backward.c +129 -0
- data/ext/gsplat_native/raster_backward_bridge.c +69 -0
- data/ext/gsplat_native/raster_forward.c +150 -0
- data/ext/gsplat_native/rasterization.h +51 -0
- data/ext/gsplat_native/spherical_harmonics.c +129 -0
- data/gsplat.gemspec +36 -0
- data/lib/gsplat/autograd/context.rb +60 -0
- data/lib/gsplat/autograd/function.rb +68 -0
- data/lib/gsplat/autograd/variable.rb +159 -0
- data/lib/gsplat/backend/ruby/accumulate.rb +139 -0
- data/lib/gsplat/backend/ruby/accumulate_backward.rb +40 -0
- data/lib/gsplat/backend/ruby/eval3d_rasterizer.rb +175 -0
- data/lib/gsplat/backend/ruby/isect_tiles.rb +198 -0
- data/lib/gsplat/backend/ruby/projection.rb +251 -0
- data/lib/gsplat/backend/ruby/projection_backward.rb +190 -0
- data/lib/gsplat/backend/ruby/projection_covariance_vjp.rb +72 -0
- data/lib/gsplat/backend/ruby/projection_input_vjp.rb +252 -0
- data/lib/gsplat/backend/ruby/quat_scale_to_covar_preci.rb +139 -0
- data/lib/gsplat/backend/ruby/rasterize_to_indices_in_range.rb +121 -0
- data/lib/gsplat/backend/ruby/rasterize_to_pixels.rb +199 -0
- data/lib/gsplat/backend/ruby/rasterize_to_pixels_backward.rb +121 -0
- data/lib/gsplat/backend/ruby/spherical_harmonics.rb +135 -0
- data/lib/gsplat/backend/ruby/tile_compositor.rb +50 -0
- data/lib/gsplat/backend/ruby/tile_compositor_backward.rb +84 -0
- data/lib/gsplat/backend.rb +79 -0
- data/lib/gsplat/compression/grid_sort.rb +79 -0
- data/lib/gsplat/compression/kmeans.rb +121 -0
- data/lib/gsplat/compression/png.rb +147 -0
- data/lib/gsplat/compression/png_codec.rb +133 -0
- data/lib/gsplat/compression/quantizer.rb +79 -0
- data/lib/gsplat/io/checkpoint.rb +145 -0
- data/lib/gsplat/io/colmap.rb +175 -0
- data/lib/gsplat/io/colmap_binary.rb +98 -0
- data/lib/gsplat/io/colmap_text.rb +84 -0
- data/lib/gsplat/io/image.rb +63 -0
- data/lib/gsplat/io/image_backends.rb +81 -0
- data/lib/gsplat/io/npy.rb +189 -0
- data/lib/gsplat/io/ply.rb +185 -0
- data/lib/gsplat/io/ply_reader.rb +142 -0
- data/lib/gsplat/io/zip_archive.rb +183 -0
- data/lib/gsplat/math/camera_distortion.rb +123 -0
- data/lib/gsplat/math/camera_projection.rb +202 -0
- data/lib/gsplat/math/mat.rb +114 -0
- data/lib/gsplat/math/quaternion.rb +175 -0
- data/lib/gsplat/math/small_matrix_primitives.rb +112 -0
- data/lib/gsplat/math/spherical_harmonic_basis.rb +148 -0
- data/lib/gsplat/math/ssim.rb +153 -0
- data/lib/gsplat/native.rb +30 -0
- data/lib/gsplat/native_ops.rb +148 -0
- data/lib/gsplat/native_raster_ops.rb +76 -0
- data/lib/gsplat/ops/accumulate.rb +68 -0
- data/lib/gsplat/ops/eval3d_rasterize.rb +62 -0
- data/lib/gsplat/ops/isect_tiles.rb +38 -0
- data/lib/gsplat/ops/projection.rb +154 -0
- data/lib/gsplat/ops/quat_scale_to_covar_preci.rb +118 -0
- data/lib/gsplat/ops/rasterize_to_indices_in_range.rb +38 -0
- data/lib/gsplat/ops/rasterize_to_pixels.rb +105 -0
- data/lib/gsplat/ops/relocation.rb +94 -0
- data/lib/gsplat/ops/spherical_harmonics.rb +71 -0
- data/lib/gsplat/ops/tensor_shape_ops.rb +173 -0
- data/lib/gsplat/ops/tensor_value_ops.rb +150 -0
- data/lib/gsplat/optim/adam.rb +212 -0
- data/lib/gsplat/optim/lr_scheduler.rb +36 -0
- data/lib/gsplat/optim/selective_adam.rb +68 -0
- data/lib/gsplat/rasterization.rb +130 -0
- data/lib/gsplat/rasterization_2dgs.rb +140 -0
- data/lib/gsplat/rasterization_helpers.rb +162 -0
- data/lib/gsplat/rasterization_validation.rb +99 -0
- data/lib/gsplat/strategy/base.rb +49 -0
- data/lib/gsplat/strategy/default.rb +188 -0
- data/lib/gsplat/strategy/mcmc.rb +103 -0
- data/lib/gsplat/strategy/mcmc_ops.rb +143 -0
- data/lib/gsplat/strategy/ops.rb +165 -0
- data/lib/gsplat/training/config.rb +91 -0
- data/lib/gsplat/training/image_fitter.rb +158 -0
- data/lib/gsplat/training/losses.rb +191 -0
- data/lib/gsplat/training/scene.rb +116 -0
- data/lib/gsplat/training/trainer.rb +236 -0
- data/lib/gsplat/utils.rb +110 -0
- data/lib/gsplat/version.rb +6 -0
- data/lib/gsplat.rb +98 -0
- 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
|
+
}
|