CVPR/LIVE
34
1#include "diffvg.h"2#include "aabb.h"3#include "shape.h"4#include "sample_boundary.h"5#include "atomic.h"6#include "cdf.h"7#include "compute_distance.h"8#include "cuda_utils.h"9#include "edge_query.h"10#include "filter.h"11#include "matrix.h"12#include "parallel.h"13#include "pcg.h"14#include "ptr.h"15#include "scene.h"16#include "vector.h"17#include "winding_number.h"18#include "within_distance.h"19#include <cassert>20#include <pybind11/pybind11.h>21#include <pybind11/stl.h>22#include <thrust/execution_policy.h>23#include <thrust/sort.h>24 25namespace py = pybind11;26 27struct Command {28 int shape_group_id;29 int shape_id;30 int point_id; // Only used by path31};32 33DEVICE34bool is_inside(const SceneData &scene_data,35 int shape_group_id,36 const Vector2f &pt,37 EdgeQuery *edge_query) {38 const ShapeGroup &shape_group = scene_data.shape_groups[shape_group_id];39 // pt is in canvas space, transform it to shape's local space40 auto local_pt = xform_pt(shape_group.canvas_to_shape, pt);41 const auto &bvh_nodes = scene_data.shape_groups_bvh_nodes[shape_group_id];42 const AABB &bbox = bvh_nodes[2 * shape_group.num_shapes - 2].box;43 if (!inside(bbox, local_pt)) {44 return false;45 }46 auto winding_number = 0;47 // Traverse the shape group BVH48 constexpr auto max_bvh_stack_size = 64;49 int bvh_stack[max_bvh_stack_size];50 auto stack_size = 0;51 bvh_stack[stack_size++] = 2 * shape_group.num_shapes - 2;52 while (stack_size > 0) {53 const BVHNode &node = bvh_nodes[bvh_stack[--stack_size]];54 if (node.child1 < 0) {55 // leaf56 auto shape_id = node.child0;57 auto w = compute_winding_number(58 scene_data.shapes[shape_id], scene_data.path_bvhs[shape_id], local_pt);59 winding_number += w;60 if (edge_query != nullptr) {61 if (edge_query->shape_group_id == shape_group_id &&62 edge_query->shape_id == shape_id) {63 if ((shape_group.use_even_odd_rule && abs(w) % 2 == 1) ||64 (!shape_group.use_even_odd_rule && w != 0)) {65 edge_query->hit = true;66 }67 }68 }69 } else {70 assert(node.child0 >= 0 && node.child1 >= 0);71 const AABB &b0 = bvh_nodes[node.child0].box;72 if (inside(b0, local_pt)) {73 bvh_stack[stack_size++] = node.child0;74 }75 const AABB &b1 = bvh_nodes[node.child1].box;76 if (inside(b1, local_pt)) {77 bvh_stack[stack_size++] = node.child1;78 }79 assert(stack_size <= max_bvh_stack_size);80 }81 }82 if (shape_group.use_even_odd_rule) {83 return abs(winding_number) % 2 == 1;84 } else {85 return winding_number != 0;86 }87}88 89DEVICE void accumulate_boundary_gradient(const Shape &shape,90 float contrib,91 float t,92 const Vector2f &normal,93 const BoundaryData &boundary_data,94 Shape &d_shape,95 const Matrix3x3f &shape_to_canvas,96 const Vector2f &local_boundary_pt,97 Matrix3x3f &d_shape_to_canvas) {98 assert(isfinite(contrib));99 assert(isfinite(normal));100 // According to Reynold transport theorem,101 // the Jacobian of the boundary integral is dot(velocity, normal),102 // where the velocity depends on the variable being differentiated with.103 if (boundary_data.is_stroke) {104 auto has_path_thickness = false;105 if (shape.type == ShapeType::Path) {106 const Path &path = *(const Path *)shape.ptr;107 has_path_thickness = path.thickness != nullptr;108 }109 // differentiate stroke width: velocity is the same as normal110 if (has_path_thickness) {111 Path *d_p = (Path*)d_shape.ptr;112 auto base_point_id = boundary_data.path.base_point_id;113 auto point_id = boundary_data.path.point_id;114 auto t = boundary_data.path.t;115 const Path &path = *(const Path *)shape.ptr;116 if (path.num_control_points[base_point_id] == 0) {117 // Straight line118 auto i0 = point_id;119 auto i1 = (point_id + 1) % path.num_points;120 // r = r0 + t * (r1 - r0)121 atomic_add(&d_p->thickness[i0], (1 - t) * contrib);122 atomic_add(&d_p->thickness[i1], ( t) * contrib);123 } else if (path.num_control_points[base_point_id] == 1) {124 // Quadratic Bezier curve125 auto i0 = point_id;126 auto i1 = point_id + 1;127 auto i2 = (point_id + 2) % path.num_points;128 // r = (1-t)^2r0 + 2(1-t)t r1 + t^2 r2129 atomic_add(&d_p->thickness[i0], square(1 - t) * contrib);130 atomic_add(&d_p->thickness[i1], (2*(1-t)*t) * contrib);131 atomic_add(&d_p->thickness[i2], (t*t) * contrib);132 } else if (path.num_control_points[base_point_id] == 2) {133 auto i0 = point_id;134 auto i1 = point_id + 1;135 auto i2 = point_id + 2;136 auto i3 = (point_id + 3) % path.num_points;137 // r = (1-t)^3r0 + 3*(1-t)^2tr1 + 3*(1-t)t^2r2 + t^3r3138 atomic_add(&d_p->thickness[i0], cubic(1 - t) * contrib);139 atomic_add(&d_p->thickness[i1], 3 * square(1 - t) * t * contrib);140 atomic_add(&d_p->thickness[i2], 3 * (1 - t) * t * t * contrib);141 atomic_add(&d_p->thickness[i3], t * t * t * contrib);142 } else {143 assert(false);144 }145 } else {146 atomic_add(&d_shape.stroke_width, contrib);147 }148 }149 switch (shape.type) {150 case ShapeType::Circle: {151 Circle *d_p = (Circle*)d_shape.ptr;152 // velocity for the center is (1, 0) for x and (0, 1) for y153 atomic_add(&d_p->center[0], normal * contrib);154 // velocity for the radius is the same as the normal155 atomic_add(&d_p->radius, contrib);156 break;157 } case ShapeType::Ellipse: {158 Ellipse *d_p = (Ellipse*)d_shape.ptr;159 // velocity for the center is (1, 0) for x and (0, 1) for y160 atomic_add(&d_p->center[0], normal * contrib);161 // velocity for the radius:162 // x = center.x + r.x * cos(2pi * t)163 // y = center.y + r.y * sin(2pi * t)164 // for r.x: (cos(2pi * t), 0)165 // for r.y: (0, sin(2pi * t))166 atomic_add(&d_p->radius.x, cos(2 * float(M_PI) * t) * normal.x * contrib);167 atomic_add(&d_p->radius.y, sin(2 * float(M_PI) * t) * normal.y * contrib);168 break;169 } case ShapeType::Path: {170 Path *d_p = (Path*)d_shape.ptr;171 auto base_point_id = boundary_data.path.base_point_id;172 auto point_id = boundary_data.path.point_id;173 auto t = boundary_data.path.t;174 const Path &path = *(const Path *)shape.ptr;175 if (path.num_control_points[base_point_id] == 0) {176 // Straight line177 auto i0 = point_id;178 auto i1 = (point_id + 1) % path.num_points;179 // pt = p0 + t * (p1 - p0)180 // velocity for p0.x: (1 - t, 0)181 // p0.y: ( 0, 1 - t)182 // p1.x: ( t, 0)183 // p1.y: ( 0, t)184 atomic_add(&d_p->points[2 * i0 + 0], (1 - t) * normal.x * contrib);185 atomic_add(&d_p->points[2 * i0 + 1], (1 - t) * normal.y * contrib);186 atomic_add(&d_p->points[2 * i1 + 0], ( t) * normal.x * contrib);187 atomic_add(&d_p->points[2 * i1 + 1], ( t) * normal.y * contrib);188 } else if (path.num_control_points[base_point_id] == 1) {189 // Quadratic Bezier curve190 auto i0 = point_id;191 auto i1 = point_id + 1;192 auto i2 = (point_id + 2) % path.num_points;193 // pt = (1-t)^2p0 + 2(1-t)t p1 + t^2 p2194 // velocity for p0.x: ((1-t)^2, 0)195 // p0.y: ( 0, (1-t)^2)196 // p1.x: (2(1-t)t, 0)197 // p1.y: ( 0, 2(1-t)t)198 // p1.x: ( t^2, 0)199 // p1.y: ( 0, t^2)200 atomic_add(&d_p->points[2 * i0 + 0], square(1 - t) * normal.x * contrib);201 atomic_add(&d_p->points[2 * i0 + 1], square(1 - t) * normal.y * contrib);202 atomic_add(&d_p->points[2 * i1 + 0], (2*(1-t)*t) * normal.x * contrib);203 atomic_add(&d_p->points[2 * i1 + 1], (2*(1-t)*t) * normal.y * contrib);204 atomic_add(&d_p->points[2 * i2 + 0], (t*t) * normal.x * contrib);205 atomic_add(&d_p->points[2 * i2 + 1], (t*t) * normal.y * contrib);206 } else if (path.num_control_points[base_point_id] == 2) {207 auto i0 = point_id;208 auto i1 = point_id + 1;209 auto i2 = point_id + 2;210 auto i3 = (point_id + 3) % path.num_points;211 // pt = (1-t)^3p0 + 3*(1-t)^2tp1 + 3*(1-t)t^2p2 + t^3p3212 // velocity for p0.x: ( (1-t)^3, 0)213 // p0.y: ( 0, (1-t)^3)214 // p1.x: (3*(1-t)^2t, 0)215 // p1.y: ( 0, 3*(1-t)^2t)216 // p2.x: (3*(1-t)t^2, 0)217 // p2.y: ( 0, 3*(1-t)t^2)218 // p2.x: ( t^3, 0)219 // p2.y: ( 0, t^3)220 atomic_add(&d_p->points[2 * i0 + 0], cubic(1 - t) * normal.x * contrib);221 atomic_add(&d_p->points[2 * i0 + 1], cubic(1 - t) * normal.y * contrib);222 atomic_add(&d_p->points[2 * i1 + 0], 3 * square(1 - t) * t * normal.x * contrib);223 atomic_add(&d_p->points[2 * i1 + 1], 3 * square(1 - t) * t * normal.y * contrib);224 atomic_add(&d_p->points[2 * i2 + 0], 3 * (1 - t) * t * t * normal.x * contrib);225 atomic_add(&d_p->points[2 * i2 + 1], 3 * (1 - t) * t * t * normal.y * contrib);226 atomic_add(&d_p->points[2 * i3 + 0], t * t * t * normal.x * contrib);227 atomic_add(&d_p->points[2 * i3 + 1], t * t * t * normal.y * contrib);228 } else {229 assert(false);230 }231 break;232 } case ShapeType::Rect: {233 Rect *d_p = (Rect*)d_shape.ptr;234 // The velocity depends on the position of the boundary235 if (normal == Vector2f{-1, 0}) {236 // left237 // velocity for p_min is (1, 0) for x and (0, 0) for y238 atomic_add(&d_p->p_min.x, -contrib);239 } else if (normal == Vector2f{1, 0}) {240 // right241 // velocity for p_max is (1, 0) for x and (0, 0) for y242 atomic_add(&d_p->p_max.x, contrib);243 } else if (normal == Vector2f{0, -1}) {244 // top245 // velocity for p_min is (0, 0) for x and (0, 1) for y246 atomic_add(&d_p->p_min.y, -contrib);247 } else if (normal == Vector2f{0, 1}) {248 // bottom249 // velocity for p_max is (0, 0) for x and (0, 1) for y250 atomic_add(&d_p->p_max.y, contrib);251 } else {252 // incorrect normal assignment?253 assert(false);254 }255 break;256 } default: {257 assert(false);258 break;259 }260 }261 // for shape_to_canvas we have the following relationship:262 // boundary_pt = xform_pt(shape_to_canvas, local_pt)263 // the velocity is the derivative of boundary_pt with respect to shape_to_canvas264 // we can use reverse-mode AD to compute the dot product of the velocity and the Jacobian265 // by passing the normal in d_xform_pt266 auto d_shape_to_canvas_ = Matrix3x3f();267 auto d_local_boundary_pt = Vector2f{0, 0};268 d_xform_pt(shape_to_canvas,269 local_boundary_pt,270 normal * contrib,271 d_shape_to_canvas_,272 d_local_boundary_pt);273 atomic_add(&d_shape_to_canvas(0, 0), d_shape_to_canvas_);274}275 276DEVICE277Vector4f sample_color(const ColorType &color_type,278 void *color,279 const Vector2f &pt) {280 switch (color_type) {281 case ColorType::Constant: {282 auto c = (const Constant*)color;283 assert(isfinite(c->color));284 return c->color;285 } case ColorType::LinearGradient: {286 auto c = (const LinearGradient*)color;287 // Project pt to (c->begin, c->end)288 auto beg = c->begin;289 auto end = c->end;290 auto t = dot(pt - beg, end - beg) / max(dot(end - beg, end - beg), 1e-3f);291 // Find the correponding stop:292 if (t < c->stop_offsets[0]) {293 return Vector4f{c->stop_colors[0],294 c->stop_colors[1],295 c->stop_colors[2],296 c->stop_colors[3]};297 }298 for (int i = 0; i < c->num_stops - 1; i++) {299 auto offset_curr = c->stop_offsets[i];300 auto offset_next = c->stop_offsets[i + 1];301 assert(offset_next > offset_curr);302 if (t >= offset_curr && t < offset_next) {303 auto color_curr = Vector4f{304 c->stop_colors[4 * i + 0],305 c->stop_colors[4 * i + 1],306 c->stop_colors[4 * i + 2],307 c->stop_colors[4 * i + 3]};308 auto color_next = Vector4f{309 c->stop_colors[4 * (i + 1) + 0],310 c->stop_colors[4 * (i + 1) + 1],311 c->stop_colors[4 * (i + 1) + 2],312 c->stop_colors[4 * (i + 1) + 3]};313 auto tt = (t - offset_curr) / (offset_next - offset_curr);314 assert(isfinite(tt));315 assert(isfinite(color_curr));316 assert(isfinite(color_next));317 return color_curr * (1 - tt) + color_next * tt;318 }319 }320 return Vector4f{c->stop_colors[4 * (c->num_stops - 1) + 0],321 c->stop_colors[4 * (c->num_stops - 1) + 1],322 c->stop_colors[4 * (c->num_stops - 1) + 2],323 c->stop_colors[4 * (c->num_stops - 1) + 3]};324 } case ColorType::RadialGradient: {325 auto c = (const RadialGradient*)color;326 // Distance from pt to center327 auto offset = pt - c->center;328 auto normalized_offset = offset / c->radius;329 auto t = length(normalized_offset);330 // Find the correponding stop:331 if (t < c->stop_offsets[0]) {332 return Vector4f{c->stop_colors[0],333 c->stop_colors[1],334 c->stop_colors[2],335 c->stop_colors[3]};336 }337 for (int i = 0; i < c->num_stops - 1; i++) {338 auto offset_curr = c->stop_offsets[i];339 auto offset_next = c->stop_offsets[i + 1];340 assert(offset_next > offset_curr);341 if (t >= offset_curr && t < offset_next) {342 auto color_curr = Vector4f{343 c->stop_colors[4 * i + 0],344 c->stop_colors[4 * i + 1],345 c->stop_colors[4 * i + 2],346 c->stop_colors[4 * i + 3]};347 auto color_next = Vector4f{348 c->stop_colors[4 * (i + 1) + 0],349 c->stop_colors[4 * (i + 1) + 1],350 c->stop_colors[4 * (i + 1) + 2],351 c->stop_colors[4 * (i + 1) + 3]};352 auto tt = (t - offset_curr) / (offset_next - offset_curr);353 assert(isfinite(tt));354 assert(isfinite(color_curr));355 assert(isfinite(color_next));356 return color_curr * (1 - tt) + color_next * tt;357 }358 }359 return Vector4f{c->stop_colors[4 * (c->num_stops - 1) + 0],360 c->stop_colors[4 * (c->num_stops - 1) + 1],361 c->stop_colors[4 * (c->num_stops - 1) + 2],362 c->stop_colors[4 * (c->num_stops - 1) + 3]};363 } default: {364 assert(false);365 }366 }367 return Vector4f{};368}369 370DEVICE371void d_sample_color(const ColorType &color_type,372 void *color_ptr,373 const Vector2f &pt,374 const Vector4f &d_color,375 void *d_color_ptr,376 float *d_translation) {377 switch (color_type) {378 case ColorType::Constant: {379 auto d_c = (Constant*)d_color_ptr;380 atomic_add(&d_c->color[0], d_color);381 return;382 } case ColorType::LinearGradient: {383 auto c = (const LinearGradient*)color_ptr;384 auto d_c = (LinearGradient*)d_color_ptr;385 // Project pt to (c->begin, c->end)386 auto beg = c->begin;387 auto end = c->end;388 auto t = dot(pt - beg, end - beg) / max(dot(end - beg, end - beg), 1e-3f);389 // Find the correponding stop:390 if (t < c->stop_offsets[0]) {391 atomic_add(&d_c->stop_colors[0], d_color);392 return;393 }394 for (int i = 0; i < c->num_stops - 1; i++) {395 auto offset_curr = c->stop_offsets[i];396 auto offset_next = c->stop_offsets[i + 1];397 assert(offset_next > offset_curr);398 if (t >= offset_curr && t < offset_next) {399 auto color_curr = Vector4f{400 c->stop_colors[4 * i + 0],401 c->stop_colors[4 * i + 1],402 c->stop_colors[4 * i + 2],403 c->stop_colors[4 * i + 3]};404 auto color_next = Vector4f{405 c->stop_colors[4 * (i + 1) + 0],406 c->stop_colors[4 * (i + 1) + 1],407 c->stop_colors[4 * (i + 1) + 2],408 c->stop_colors[4 * (i + 1) + 3]};409 auto tt = (t - offset_curr) / (offset_next - offset_curr);410 // return color_curr * (1 - tt) + color_next * tt;411 auto d_color_curr = d_color * (1 - tt);412 auto d_color_next = d_color * tt;413 auto d_tt = sum(d_color * (color_next - color_curr));414 auto d_offset_next = -d_tt * tt / (offset_next - offset_curr);415 auto d_offset_curr = d_tt * ((tt - 1.f) / (offset_next - offset_curr));416 auto d_t = d_tt / (offset_next - offset_curr);417 assert(isfinite(d_tt));418 atomic_add(&d_c->stop_colors[4 * i], d_color_curr);419 atomic_add(&d_c->stop_colors[4 * (i + 1)], d_color_next);420 atomic_add(&d_c->stop_offsets[i], d_offset_curr);421 atomic_add(&d_c->stop_offsets[i + 1], d_offset_next);422 // auto t = dot(pt - beg, end - beg) / max(dot(end - beg, end - beg), 1e-6f);423 // l = max(dot(end - beg, end - beg), 1e-3f)424 // t = dot(pt - beg, end - beg) / l;425 auto l = max(dot(end - beg, end - beg), 1e-3f);426 auto d_beg = d_t * (-(pt - beg)-(end - beg)) / l;427 auto d_end = d_t * (pt - beg) / l;428 auto d_l = -d_t * t / l;429 if (dot(end - beg, end - beg) > 1e-3f) {430 d_beg += 2 * d_l * (beg - end);431 d_end += 2 * d_l * (end - beg);432 }433 atomic_add(&d_c->begin[0], d_beg);434 atomic_add(&d_c->end[0], d_end);435 if (d_translation != nullptr) {436 atomic_add(d_translation, (d_beg + d_end));437 }438 return;439 }440 }441 atomic_add(&d_c->stop_colors[4 * (c->num_stops - 1)], d_color);442 return;443 } case ColorType::RadialGradient: {444 auto c = (const RadialGradient*)color_ptr;445 auto d_c = (RadialGradient*)d_color_ptr;446 // Distance from pt to center447 auto offset = pt - c->center;448 auto normalized_offset = offset / c->radius;449 auto t = length(normalized_offset);450 // Find the correponding stop:451 if (t < c->stop_offsets[0]) {452 atomic_add(&d_c->stop_colors[0], d_color);453 return;454 }455 for (int i = 0; i < c->num_stops - 1; i++) {456 auto offset_curr = c->stop_offsets[i];457 auto offset_next = c->stop_offsets[i + 1];458 assert(offset_next > offset_curr);459 if (t >= offset_curr && t < offset_next) {460 auto color_curr = Vector4f{461 c->stop_colors[4 * i + 0],462 c->stop_colors[4 * i + 1],463 c->stop_colors[4 * i + 2],464 c->stop_colors[4 * i + 3]};465 auto color_next = Vector4f{466 c->stop_colors[4 * (i + 1) + 0],467 c->stop_colors[4 * (i + 1) + 1],468 c->stop_colors[4 * (i + 1) + 2],469 c->stop_colors[4 * (i + 1) + 3]};470 auto tt = (t - offset_curr) / (offset_next - offset_curr);471 assert(isfinite(tt));472 // return color_curr * (1 - tt) + color_next * tt;473 auto d_color_curr = d_color * (1 - tt);474 auto d_color_next = d_color * tt;475 auto d_tt = sum(d_color * (color_next - color_curr));476 auto d_offset_next = -d_tt * tt / (offset_next - offset_curr);477 auto d_offset_curr = d_tt * ((tt - 1.f) / (offset_next - offset_curr));478 auto d_t = d_tt / (offset_next - offset_curr);479 assert(isfinite(d_t));480 atomic_add(&d_c->stop_colors[4 * i], d_color_curr);481 atomic_add(&d_c->stop_colors[4 * (i + 1)], d_color_next);482 atomic_add(&d_c->stop_offsets[i], d_offset_curr);483 atomic_add(&d_c->stop_offsets[i + 1], d_offset_next);484 // offset = pt - c->center485 // normalized_offset = offset / c->radius486 // t = length(normalized_offset)487 auto d_normalized_offset = d_length(normalized_offset, d_t);488 auto d_offset = d_normalized_offset / c->radius;489 auto d_radius = -d_normalized_offset * offset / (c->radius * c->radius);490 auto d_center = -d_offset;491 atomic_add(&d_c->center[0], d_center);492 atomic_add(&d_c->radius[0], d_radius);493 if (d_translation != nullptr) {494 atomic_add(d_translation, d_center);495 }496 }497 }498 atomic_add(&d_c->stop_colors[4 * (c->num_stops - 1)], d_color);499 return;500 } default: {501 assert(false);502 }503 }504}505 506struct Fragment {507 Vector3f color;508 float alpha;509 int group_id;510 bool is_stroke;511};512 513struct PrefilterFragment {514 Vector3f color;515 float alpha;516 int group_id;517 bool is_stroke;518 int shape_id;519 float distance;520 Vector2f closest_pt;521 ClosestPointPathInfo path_info;522 bool within_distance;523};524 525DEVICE526Vector4f sample_color(const SceneData &scene,527 const Vector4f *background_color,528 const Vector2f &screen_pt,529 const Vector4f *d_color = nullptr,530 EdgeQuery *edge_query = nullptr,531 Vector4f *d_background_color = nullptr,532 float *d_translation = nullptr) {533 if (edge_query != nullptr) {534 edge_query->hit = false;535 }536 537 // screen_pt is in screen space ([0, 1), [0, 1)),538 // need to transform to canvas space539 auto pt = screen_pt;540 pt.x *= scene.canvas_width;541 pt.y *= scene.canvas_height;542 constexpr auto max_hit_shapes = 256;543 constexpr auto max_bvh_stack_size = 64;544 Fragment fragments[max_hit_shapes];545 int bvh_stack[max_bvh_stack_size];546 auto stack_size = 0;547 auto num_fragments = 0;548 bvh_stack[stack_size++] = 2 * scene.num_shape_groups - 2;549 while (stack_size > 0) {550 const BVHNode &node = scene.bvh_nodes[bvh_stack[--stack_size]];551 if (node.child1 < 0) {552 // leaf553 auto group_id = node.child0;554 const ShapeGroup &shape_group = scene.shape_groups[group_id];555 if (shape_group.stroke_color != nullptr) {556 if (within_distance(scene, group_id, pt, edge_query)) {557 auto color_alpha = sample_color(shape_group.stroke_color_type,558 shape_group.stroke_color,559 pt);560 Fragment f;561 f.color = Vector3f{color_alpha[0], color_alpha[1], color_alpha[2]};562 f.alpha = color_alpha[3];563 f.group_id = group_id;564 f.is_stroke = true;565 assert(num_fragments < max_hit_shapes);566 fragments[num_fragments++] = f;567 }568 }569 if (shape_group.fill_color != nullptr) {570 if (is_inside(scene, group_id, pt, edge_query)) {571 auto color_alpha = sample_color(shape_group.fill_color_type,572 shape_group.fill_color,573 pt);574 Fragment f;575 f.color = Vector3f{color_alpha[0], color_alpha[1], color_alpha[2]};576 f.alpha = color_alpha[3];577 f.group_id = group_id;578 f.is_stroke = false;579 assert(num_fragments < max_hit_shapes);580 fragments[num_fragments++] = f;581 }582 }583 } else {584 assert(node.child0 >= 0 && node.child1 >= 0);585 const AABB &b0 = scene.bvh_nodes[node.child0].box;586 if (inside(b0, pt, scene.bvh_nodes[node.child0].max_radius)) {587 bvh_stack[stack_size++] = node.child0;588 }589 const AABB &b1 = scene.bvh_nodes[node.child1].box;590 if (inside(b1, pt, scene.bvh_nodes[node.child1].max_radius)) {591 bvh_stack[stack_size++] = node.child1;592 }593 assert(stack_size <= max_bvh_stack_size);594 }595 }596 if (num_fragments <= 0) {597 if (background_color != nullptr) {598 if (d_background_color != nullptr) {599 *d_background_color = *d_color;600 }601 return *background_color;602 }603 return Vector4f{0, 0, 0, 0};604 }605 // Sort the fragments from back to front (i.e. increasing order of group id)606 // https://github.com/frigaut/yorick-imutil/blob/master/insort.c#L37607 for (int i = 1; i < num_fragments; i++) {608 auto j = i;609 auto temp = fragments[j];610 while (j > 0 && fragments[j - 1].group_id > temp.group_id) {611 fragments[j] = fragments[j - 1];612 j--;613 }614 fragments[j] = temp;615 }616 // Blend the color617 Vector3f accum_color[max_hit_shapes];618 float accum_alpha[max_hit_shapes];619 // auto hit_opaque = false;620 auto first_alpha = 0.f;621 auto first_color = Vector3f{0, 0, 0};622 if (background_color != nullptr) {623 first_alpha = background_color->w;624 first_color = Vector3f{background_color->x,625 background_color->y,626 background_color->z};627 }628 for (int i = 0; i < num_fragments; i++) {629 const Fragment &fragment = fragments[i];630 auto new_color = fragment.color;631 auto new_alpha = fragment.alpha;632 auto prev_alpha = i > 0 ? accum_alpha[i - 1] : first_alpha;633 auto prev_color = i > 0 ? accum_color[i - 1] : first_color;634 if (edge_query != nullptr) {635 // Do we hit the target shape?636 if (new_alpha >= 1.f && edge_query->hit) {637 // A fully opaque shape in front of the target occludes it638 edge_query->hit = false;639 }640 if (edge_query->shape_group_id == fragment.group_id) {641 edge_query->hit = true;642 }643 }644 // prev_color is alpha premultiplied, don't need to multiply with645 // prev_alpha646 accum_color[i] = prev_color * (1 - new_alpha) + new_alpha * new_color;647 accum_alpha[i] = prev_alpha * (1 - new_alpha) + new_alpha;648 }649 auto final_color = accum_color[num_fragments - 1];650 auto final_alpha = accum_alpha[num_fragments - 1];651 if (final_alpha > 1e-6f) {652 final_color /= final_alpha;653 }654 assert(isfinite(final_color));655 assert(isfinite(final_alpha));656 if (d_color != nullptr) {657 // Backward pass658 auto d_final_color = Vector3f{(*d_color)[0], (*d_color)[1], (*d_color)[2]};659 auto d_final_alpha = (*d_color)[3];660 auto d_curr_color = d_final_color;661 auto d_curr_alpha = d_final_alpha;662 if (final_alpha > 1e-6f) {663 // final_color = curr_color / final_alpha664 d_curr_color = d_final_color / final_alpha;665 d_curr_alpha -= sum(d_final_color * final_color) / final_alpha;666 }667 assert(isfinite(*d_color));668 assert(isfinite(d_curr_color));669 assert(isfinite(d_curr_alpha));670 for (int i = num_fragments - 1; i >= 0; i--) {671 // color[n] = prev_color * (1 - new_alpha) + new_alpha * new_color;672 // alpha[n] = prev_alpha * (1 - new_alpha) + new_alpha;673 auto prev_alpha = i > 0 ? accum_alpha[i - 1] : first_alpha;674 auto prev_color = i > 0 ? accum_color[i - 1] : first_color;675 auto d_prev_alpha = d_curr_alpha * (1.f - fragments[i].alpha);676 auto d_alpha_i = d_curr_alpha * (1.f - prev_alpha);677 d_alpha_i += sum(d_curr_color * (fragments[i].color - prev_color));678 auto d_prev_color = d_curr_color * (1 - fragments[i].alpha);679 auto d_color_i = d_curr_color * fragments[i].alpha;680 auto group_id = fragments[i].group_id;681 if (fragments[i].is_stroke) {682 d_sample_color(scene.shape_groups[group_id].stroke_color_type,683 scene.shape_groups[group_id].stroke_color,684 pt,685 Vector4f{d_color_i[0], d_color_i[1], d_color_i[2], d_alpha_i},686 scene.d_shape_groups[group_id].stroke_color,687 d_translation);688 } else {689 d_sample_color(scene.shape_groups[group_id].fill_color_type,690 scene.shape_groups[group_id].fill_color,691 pt,692 Vector4f{d_color_i[0], d_color_i[1], d_color_i[2], d_alpha_i},693 scene.d_shape_groups[group_id].fill_color,694 d_translation);695 }696 d_curr_color = d_prev_color;697 d_curr_alpha = d_prev_alpha;698 }699 if (d_background_color != nullptr) {700 d_background_color->x += d_curr_color.x;701 d_background_color->y += d_curr_color.y;702 d_background_color->z += d_curr_color.z;703 d_background_color->w += d_curr_alpha;704 }705 }706 return Vector4f{final_color[0], final_color[1], final_color[2], final_alpha};707}708 709DEVICE710float sample_distance(const SceneData &scene,711 const Vector2f &screen_pt,712 float weight,713 const float *d_dist = nullptr,714 float *d_translation = nullptr) {715 // screen_pt is in screen space ([0, 1), [0, 1)),716 // need to transform to canvas space717 auto pt = screen_pt;718 pt.x *= scene.canvas_width;719 pt.y *= scene.canvas_height;720 // for each shape721 auto min_group_id = -1;722 auto min_distance = 0.f;723 auto min_shape_id = -1;724 auto closest_pt = Vector2f{0, 0};725 auto min_path_info = ClosestPointPathInfo{-1, -1, 0};726 for (int group_id = scene.num_shape_groups - 1; group_id >= 0; group_id--) {727 auto s = -1;728 auto p = Vector2f{0, 0};729 ClosestPointPathInfo local_path_info;730 auto d = infinity<float>();731 if (compute_distance(scene, group_id, pt, infinity<float>(), &s, &p, &local_path_info, &d)) {732 if (min_group_id == -1 || d < min_distance) {733 min_distance = d;734 min_group_id = group_id;735 min_shape_id = s;736 closest_pt = p;737 min_path_info = local_path_info;738 }739 }740 }741 if (min_group_id == -1) {742 return min_distance;743 }744 min_distance *= weight;745 auto inside = false;746 const ShapeGroup &shape_group = scene.shape_groups[min_group_id];747 if (shape_group.fill_color != nullptr) {748 inside = is_inside(scene,749 min_group_id,750 pt,751 nullptr);752 if (inside) {753 min_distance = -min_distance;754 }755 }756 assert((min_group_id >= 0 && min_shape_id >= 0) || scene.num_shape_groups == 0);757 if (d_dist != nullptr) {758 auto d_abs_dist = inside ? -(*d_dist) : (*d_dist);759 const ShapeGroup &shape_group = scene.shape_groups[min_group_id];760 const Shape &shape = scene.shapes[min_shape_id];761 ShapeGroup &d_shape_group = scene.d_shape_groups[min_group_id];762 Shape &d_shape = scene.d_shapes[min_shape_id];763 d_compute_distance(shape_group.canvas_to_shape,764 shape_group.shape_to_canvas,765 shape,766 pt,767 closest_pt,768 min_path_info,769 d_abs_dist,770 d_shape_group.shape_to_canvas,771 d_shape,772 d_translation);773 }774 return min_distance;775}776 777// Gather d_color from d_image inside the filter kernel, normalize by778// weight_image.779DEVICE780Vector4f gather_d_color(const Filter &filter,781 const float *d_color_image,782 const float *weight_image,783 int width,784 int height,785 const Vector2f &pt) {786 auto x = int(pt.x);787 auto y = int(pt.y);788 auto radius = filter.radius;789 assert(radius > 0);790 auto ri = (int)ceil(radius);791 auto d_color = Vector4f{0, 0, 0, 0};792 for (int dy = -ri; dy <= ri; dy++) {793 for (int dx = -ri; dx <= ri; dx++) {794 auto xx = x + dx;795 auto yy = y + dy;796 if (xx >= 0 && xx < width && yy >= 0 && yy < height) {797 auto xc = xx + 0.5f;798 auto yc = yy + 0.5f;799 auto filter_weight =800 compute_filter_weight(filter, xc - pt.x, yc - pt.y);801 // pixel = \sum weight * color / \sum weight802 auto weight_sum = weight_image[yy * width + xx];803 if (weight_sum > 0) {804 d_color += (filter_weight / weight_sum) * Vector4f{805 d_color_image[4 * (yy * width + xx) + 0],806 d_color_image[4 * (yy * width + xx) + 1],807 d_color_image[4 * (yy * width + xx) + 2],808 d_color_image[4 * (yy * width + xx) + 3],809 };810 }811 }812 }813 }814 return d_color;815}816 817DEVICE818float smoothstep(float d) {819 auto t = clamp((d + 1.f) / 2.f, 0.f, 1.f);820 return t * t * (3 - 2 * t);821}822 823DEVICE824float d_smoothstep(float d, float d_ret) {825 if (d < -1.f || d > 1.f) {826 return 0.f;827 }828 auto t = (d + 1.f) / 2.f;829 // ret = t * t * (3 - 2 * t)830 // = 3 * t * t - 2 * t * t * t831 auto d_t = d_ret * (6 * t - 6 * t * t);832 return d_t / 2.f;833}834 835DEVICE836Vector4f sample_color_prefiltered(const SceneData &scene,837 const Vector4f *background_color,838 const Vector2f &screen_pt,839 const Vector4f *d_color = nullptr,840 Vector4f *d_background_color = nullptr,841 float *d_translation = nullptr) {842 // screen_pt is in screen space ([0, 1), [0, 1)),843 // need to transform to canvas space844 auto pt = screen_pt;845 pt.x *= scene.canvas_width;846 pt.y *= scene.canvas_height;847 constexpr auto max_hit_shapes = 64;848 constexpr auto max_bvh_stack_size = 64;849 PrefilterFragment fragments[max_hit_shapes];850 int bvh_stack[max_bvh_stack_size];851 auto stack_size = 0;852 auto num_fragments = 0;853 bvh_stack[stack_size++] = 2 * scene.num_shape_groups - 2;854 while (stack_size > 0) {855 const BVHNode &node = scene.bvh_nodes[bvh_stack[--stack_size]];856 if (node.child1 < 0) {857 // leaf858 auto group_id = node.child0;859 const ShapeGroup &shape_group = scene.shape_groups[group_id];860 if (shape_group.stroke_color != nullptr) {861 auto min_shape_id = -1;862 auto closest_pt = Vector2f{0, 0};863 auto local_path_info = ClosestPointPathInfo{-1, -1, 0};864 auto d = infinity<float>();865 compute_distance(scene, group_id, pt, infinity<float>(),866 &min_shape_id, &closest_pt, &local_path_info, &d);867 assert(min_shape_id != -1);868 const auto &shape = scene.shapes[min_shape_id];869 auto w = smoothstep(fabs(d) + shape.stroke_width) -870 smoothstep(fabs(d) - shape.stroke_width);871 if (w > 0) {872 auto color_alpha = sample_color(shape_group.stroke_color_type,873 shape_group.stroke_color,874 pt);875 color_alpha[3] *= w;876 877 PrefilterFragment f;878 f.color = Vector3f{color_alpha[0], color_alpha[1], color_alpha[2]};879 f.alpha = color_alpha[3];880 f.group_id = group_id;881 f.shape_id = min_shape_id;882 f.distance = d;883 f.closest_pt = closest_pt;884 f.is_stroke = true;885 f.path_info = local_path_info;886 f.within_distance = true;887 assert(num_fragments < max_hit_shapes);888 fragments[num_fragments++] = f;889 }890 }891 if (shape_group.fill_color != nullptr) {892 auto min_shape_id = -1;893 auto closest_pt = Vector2f{0, 0};894 auto local_path_info = ClosestPointPathInfo{-1, -1, 0};895 auto d = infinity<float>();896 auto found = compute_distance(scene,897 group_id,898 pt,899 1.f,900 &min_shape_id,901 &closest_pt,902 &local_path_info,903 &d);904 auto inside = is_inside(scene, group_id, pt, nullptr);905 if (found || inside) {906 if (!inside) {907 d = -d;908 }909 auto w = smoothstep(d);910 if (w > 0) {911 auto color_alpha = sample_color(shape_group.fill_color_type,912 shape_group.fill_color,913 pt);914 color_alpha[3] *= w;915 916 PrefilterFragment f;917 f.color = Vector3f{color_alpha[0], color_alpha[1], color_alpha[2]};918 f.alpha = color_alpha[3];919 f.group_id = group_id;920 f.shape_id = min_shape_id;921 f.distance = d;922 f.closest_pt = closest_pt;923 f.is_stroke = false;924 f.path_info = local_path_info;925 f.within_distance = found;926 assert(num_fragments < max_hit_shapes);927 fragments[num_fragments++] = f;928 }929 }930 }931 } else {932 assert(node.child0 >= 0 && node.child1 >= 0);933 const AABB &b0 = scene.bvh_nodes[node.child0].box;934 if (inside(b0, pt, scene.bvh_nodes[node.child0].max_radius)) {935 bvh_stack[stack_size++] = node.child0;936 }937 const AABB &b1 = scene.bvh_nodes[node.child1].box;938 if (inside(b1, pt, scene.bvh_nodes[node.child1].max_radius)) {939 bvh_stack[stack_size++] = node.child1;940 }941 assert(stack_size <= max_bvh_stack_size);942 }943 }944 if (num_fragments <= 0) {945 if (background_color != nullptr) {946 if (d_background_color != nullptr) {947 *d_background_color = *d_color;948 }949 return *background_color;950 }951 return Vector4f{0, 0, 0, 0};952 }953 // Sort the fragments from back to front (i.e. increasing order of group id)954 // https://github.com/frigaut/yorick-imutil/blob/master/insort.c#L37955 for (int i = 1; i < num_fragments; i++) {956 auto j = i;957 auto temp = fragments[j];958 while (j > 0 && fragments[j - 1].group_id > temp.group_id) {959 fragments[j] = fragments[j - 1];960 j--;961 }962 fragments[j] = temp;963 }964 // Blend the color965 Vector3f accum_color[max_hit_shapes];966 float accum_alpha[max_hit_shapes];967 auto first_alpha = 0.f;968 auto first_color = Vector3f{0, 0, 0};969 if (background_color != nullptr) {970 first_alpha = background_color->w;971 first_color = Vector3f{background_color->x,972 background_color->y,973 background_color->z};974 }975 for (int i = 0; i < num_fragments; i++) {976 const PrefilterFragment &fragment = fragments[i];977 auto new_color = fragment.color;978 auto new_alpha = fragment.alpha;979 auto prev_alpha = i > 0 ? accum_alpha[i - 1] : first_alpha;980 auto prev_color = i > 0 ? accum_color[i - 1] : first_color;981 // prev_color is alpha premultiplied, don't need to multiply with982 // prev_alpha983 accum_color[i] = prev_color * (1 - new_alpha) + new_alpha * new_color;984 accum_alpha[i] = prev_alpha * (1 - new_alpha) + new_alpha;985 }986 auto final_color = accum_color[num_fragments - 1];987 auto final_alpha = accum_alpha[num_fragments - 1];988 if (final_alpha > 1e-6f) {989 final_color /= final_alpha;990 }991 assert(isfinite(final_color));992 assert(isfinite(final_alpha));993 if (d_color != nullptr) {994 // Backward pass995 auto d_final_color = Vector3f{(*d_color)[0], (*d_color)[1], (*d_color)[2]};996 auto d_final_alpha = (*d_color)[3];997 auto d_curr_color = d_final_color;998 auto d_curr_alpha = d_final_alpha;999 if (final_alpha > 1e-6f) {1000 // final_color = curr_color / final_alpha1001 d_curr_color = d_final_color / final_alpha;1002 d_curr_alpha -= sum(d_final_color * final_color) / final_alpha;1003 }1004 assert(isfinite(*d_color));1005 assert(isfinite(d_curr_color));1006 assert(isfinite(d_curr_alpha));1007 for (int i = num_fragments - 1; i >= 0; i--) {1008 // color[n] = prev_color * (1 - new_alpha) + new_alpha * new_color;1009 // alpha[n] = prev_alpha * (1 - new_alpha) + new_alpha;1010 auto prev_alpha = i > 0 ? accum_alpha[i - 1] : first_alpha;1011 auto prev_color = i > 0 ? accum_color[i - 1] : first_color;1012 auto d_prev_alpha = d_curr_alpha * (1.f - fragments[i].alpha);1013 auto d_alpha_i = d_curr_alpha * (1.f - prev_alpha);1014 d_alpha_i += sum(d_curr_color * (fragments[i].color - prev_color));1015 auto d_prev_color = d_curr_color * (1 - fragments[i].alpha);1016 auto d_color_i = d_curr_color * fragments[i].alpha;1017 auto group_id = fragments[i].group_id;1018 if (fragments[i].is_stroke) {1019 const auto &shape = scene.shapes[fragments[i].shape_id];1020 auto d = fragments[i].distance;1021 auto abs_d_plus_width = fabs(d) + shape.stroke_width;1022 auto abs_d_minus_width = fabs(d) - shape.stroke_width;1023 auto w = smoothstep(abs_d_plus_width) -1024 smoothstep(abs_d_minus_width);1025 if (w != 0) {1026 auto d_w = w > 0 ? (fragments[i].alpha / w) * d_alpha_i : 0.f;1027 d_alpha_i *= w;1028 1029 // Backprop to color1030 d_sample_color(scene.shape_groups[group_id].stroke_color_type,1031 scene.shape_groups[group_id].stroke_color,1032 pt,1033 Vector4f{d_color_i[0], d_color_i[1], d_color_i[2], d_alpha_i},1034 scene.d_shape_groups[group_id].stroke_color,1035 d_translation);1036 1037 auto d_abs_d_plus_width = d_smoothstep(abs_d_plus_width, d_w);1038 auto d_abs_d_minus_width = -d_smoothstep(abs_d_minus_width, d_w);1039 1040 auto d_d = d_abs_d_plus_width + d_abs_d_minus_width;1041 if (d < 0) {1042 d_d = -d_d;1043 }1044 auto d_stroke_width = d_abs_d_plus_width - d_abs_d_minus_width;1045 1046 const auto &shape_group = scene.shape_groups[group_id];1047 ShapeGroup &d_shape_group = scene.d_shape_groups[group_id];1048 Shape &d_shape = scene.d_shapes[fragments[i].shape_id];1049 if (fabs(d_d) > 1e-10f) {1050 d_compute_distance(shape_group.canvas_to_shape,1051 shape_group.shape_to_canvas,1052 shape,1053 pt,1054 fragments[i].closest_pt,1055 fragments[i].path_info,1056 d_d,1057 d_shape_group.shape_to_canvas,1058 d_shape,1059 d_translation);1060 }1061 atomic_add(&d_shape.stroke_width, d_stroke_width);1062 }1063 } else {1064 const auto &shape = scene.shapes[fragments[i].shape_id];1065 auto d = fragments[i].distance;1066 auto w = smoothstep(d);1067 if (w != 0) {1068 // color_alpha[3] = color_alpha[3] * w;1069 auto d_w = w > 0 ? (fragments[i].alpha / w) * d_alpha_i : 0.f;1070 d_alpha_i *= w;1071 1072 d_sample_color(scene.shape_groups[group_id].fill_color_type,1073 scene.shape_groups[group_id].fill_color,1074 pt,1075 Vector4f{d_color_i[0], d_color_i[1], d_color_i[2], d_alpha_i},1076 scene.d_shape_groups[group_id].fill_color,1077 d_translation);1078 1079 // w = smoothstep(d)1080 auto d_d = d_smoothstep(d, d_w);1081 if (d < 0) {1082 d_d = -d_d;1083 }1084 1085 const auto &shape_group = scene.shape_groups[group_id];1086 ShapeGroup &d_shape_group = scene.d_shape_groups[group_id];1087 Shape &d_shape = scene.d_shapes[fragments[i].shape_id];1088 if (fabs(d_d) > 1e-10f && fragments[i].within_distance) {1089 d_compute_distance(shape_group.canvas_to_shape,1090 shape_group.shape_to_canvas,1091 shape,1092 pt,1093 fragments[i].closest_pt,1094 fragments[i].path_info,1095 d_d,1096 d_shape_group.shape_to_canvas,1097 d_shape,1098 d_translation);1099 }1100 }1101 }1102 d_curr_color = d_prev_color;1103 d_curr_alpha = d_prev_alpha;1104 }1105 if (d_background_color != nullptr) {1106 d_background_color->x += d_curr_color.x;1107 d_background_color->y += d_curr_color.y;1108 d_background_color->z += d_curr_color.z;1109 d_background_color->w += d_curr_alpha;1110 }1111 }1112 return Vector4f{final_color[0], final_color[1], final_color[2], final_alpha};1113}1114 1115struct weight_kernel {1116 DEVICE void operator()(int idx) {1117 auto rng_state = init_pcg32(idx, seed);1118 // height * width * num_samples_y * num_samples_x1119 auto sx = idx % num_samples_x;1120 auto sy = (idx / num_samples_x) % num_samples_y;1121 auto x = (idx / (num_samples_x * num_samples_y)) % width;1122 auto y = (idx / (num_samples_x * num_samples_y * width));1123 assert(y < height);1124 auto rx = next_pcg32_float(&rng_state);1125 auto ry = next_pcg32_float(&rng_state);1126 if (use_prefiltering) {1127 rx = ry = 0.5f;1128 }1129 auto pt = Vector2f{x + ((float)sx + rx) / num_samples_x,1130 y + ((float)sy + ry) / num_samples_y};1131 auto radius = scene.filter->radius;1132 assert(radius >= 0);1133 auto ri = (int)ceil(radius);1134 for (int dy = -ri; dy <= ri; dy++) {1135 for (int dx = -ri; dx <= ri; dx++) {1136 auto xx = x + dx;1137 auto yy = y + dy;1138 if (xx >= 0 && xx < width && yy >= 0 && yy < height) {1139 auto xc = xx + 0.5f;1140 auto yc = yy + 0.5f;1141 auto filter_weight = compute_filter_weight(*scene.filter,1142 xc - pt.x,1143 yc - pt.y);1144 atomic_add(weight_image[yy * width + xx], filter_weight);1145 }1146 }1147 }1148 }1149 1150 SceneData scene;1151 float *weight_image;1152 int width;1153 int height;1154 int num_samples_x;1155 int num_samples_y;1156 uint64_t seed;1157 bool use_prefiltering;1158};1159 1160// We use a "mega kernel" for rendering1161struct render_kernel {1162 DEVICE void operator()(int idx) {1163 // height * width * num_samples_y * num_samples_x1164 auto pt = Vector2f{0, 0};1165 auto x = 0;1166 auto y = 0;1167 if (eval_positions == nullptr) {1168 auto rng_state = init_pcg32(idx, seed);1169 auto sx = idx % num_samples_x;1170 auto sy = (idx / num_samples_x) % num_samples_y;1171 x = (idx / (num_samples_x * num_samples_y)) % width;1172 y = (idx / (num_samples_x * num_samples_y * width));1173 assert(x < width && y < height);1174 auto rx = next_pcg32_float(&rng_state);1175 auto ry = next_pcg32_float(&rng_state);1176 if (use_prefiltering) {1177 rx = ry = 0.5f;1178 }1179 pt = Vector2f{x + ((float)sx + rx) / num_samples_x,1180 y + ((float)sy + ry) / num_samples_y};1181 } else {1182 pt = Vector2f{eval_positions[2 * idx],1183 eval_positions[2 * idx + 1]};1184 x = int(pt.x);1185 y = int(pt.y);1186 }1187 1188 // normalize pt to [0, 1]1189 auto npt = pt;1190 npt.x /= width;1191 npt.y /= height;1192 auto num_samples = num_samples_x * num_samples_y;1193 if (render_image != nullptr || d_render_image != nullptr) {1194 Vector4f d_color = Vector4f{0, 0, 0, 0};1195 if (d_render_image != nullptr) {1196 // Gather d_color from d_render_image inside the filter kernel1197 // normalize using weight_image1198 d_color = gather_d_color(*scene.filter,1199 d_render_image,1200 weight_image,