← Back to Step 2 in blog post

s01_literature → s02_reduce_scatter

Step 2: Warp Reduce-Scatter & Two-Splat Half-Warp Folding  ·  +234 / −55 lines across 2 changed files

csrc/auxiliary.h +78 −9
s01_literature/csrc/auxiliary.h
s02_reduce_scatter/csrc/auxiliary.h
↕ Expand 297 unchanged lines (lines 1–297) · in ForEachTileRow
1/*1/*
2 * Copyright (C) 2023, Inria2 * Copyright (C) 2023, Inria
3 * GRAPHDECO research group, https://team.inria.fr/graphdeco3 * GRAPHDECO research group, https://team.inria.fr/graphdeco
4 * All rights reserved.4 * All rights reserved.
5 *5 *
6 * This software is free for non-commercial, research and evaluation use6 * This software is free for non-commercial, research and evaluation use
7 * under the terms of the LICENSE.md file.7 * under the terms of the LICENSE.md file.
8 *8 *
9 * For inquiries contact george.drettakis@inria.fr9 * For inquiries contact george.drettakis@inria.fr
10 */10 */
1111
12#ifndef CUDA_RASTERIZER_AUXILIARY_H_INCLUDED12#ifndef CUDA_RASTERIZER_AUXILIARY_H_INCLUDED
13#define CUDA_RASTERIZER_AUXILIARY_H_INCLUDED13#define CUDA_RASTERIZER_AUXILIARY_H_INCLUDED
1414
15#include <cuda_runtime.h>15#include <cuda_runtime.h>
1616
17#include <cmath>17#include <cmath>
18#include <cstdint>18#include <cstdint>
1919
20#include "config.h"20#include "config.h"
21#include "stdio.h"21#include "stdio.h"
2222
23#define BLOCK_SIZE (BLOCK_X * BLOCK_Y)23#define BLOCK_SIZE (BLOCK_X * BLOCK_Y)
24#define NUM_WARPS (BLOCK_SIZE / 32)24#define NUM_WARPS (BLOCK_SIZE / 32)
2525
26// Spherical harmonics coefficients26// Spherical harmonics coefficients
27__device__ const float SH_C0 = 0.28209479177387814f;27__device__ const float SH_C0 = 0.28209479177387814f;
28__device__ const float SH_C1 = 0.4886025119029199f;28__device__ const float SH_C1 = 0.4886025119029199f;
29__device__ const float SH_C2[] = {1.0925484305920792f, -1.0925484305920792f,29__device__ const float SH_C2[] = {1.0925484305920792f, -1.0925484305920792f,
30 0.31539156525252005f, -1.0925484305920792f,30 0.31539156525252005f, -1.0925484305920792f,
31 0.5462742152960396f};31 0.5462742152960396f};
32__device__ const float SH_C3[] = {-0.5900435899266435f, 2.890611442640554f,32__device__ const float SH_C3[] = {-0.5900435899266435f, 2.890611442640554f,
33 -0.4570457994644658f, 0.3731763325901154f,33 -0.4570457994644658f, 0.3731763325901154f,
34 -0.4570457994644658f, 1.445305721320277f,34 -0.4570457994644658f, 1.445305721320277f,
35 -0.5900435899266435f};35 -0.5900435899266435f};
3636
37namespace gs {37namespace gs {
3838
39#define GS_HD __host__ __device__ __forceinline__39#define GS_HD __host__ __device__ __forceinline__
40#define GS_D __device__ __forceinline__40#define GS_D __device__ __forceinline__
4141
42GS_HD float3 operator+(float3 a, float3 b) {42GS_HD float3 operator+(float3 a, float3 b) {
43 return {a.x + b.x, a.y + b.y, a.z + b.z};43 return {a.x + b.x, a.y + b.y, a.z + b.z};
44}44}
45GS_HD float3 operator-(float3 a, float3 b) {45GS_HD float3 operator-(float3 a, float3 b) {
46 return {a.x - b.x, a.y - b.y, a.z - b.z};46 return {a.x - b.x, a.y - b.y, a.z - b.z};
47}47}
48GS_HD float3 operator*(float s, float3 a) {48GS_HD float3 operator*(float s, float3 a) {
49 return {s * a.x, s * a.y, s * a.z};49 return {s * a.x, s * a.y, s * a.z};
50}50}
51GS_HD float3& operator+=(float3& a, float3 b) {51GS_HD float3& operator+=(float3& a, float3 b) {
52 a.x += b.x;52 a.x += b.x;
53 a.y += b.y;53 a.y += b.y;
54 a.z += b.z;54 a.z += b.z;
55 return a;55 return a;
56}56}
57GS_HD float dot(float3 a, float3 b) {57GS_HD float dot(float3 a, float3 b) {
58 return a.x * b.x + a.y * b.y + a.z * b.z;58 return a.x * b.x + a.y * b.y + a.z * b.z;
59}59}
6060
61// Row-major 3x3 matrix.61// Row-major 3x3 matrix.
62struct Mat3 {62struct Mat3 {
63 float m[3][3];63 float m[3][3];
64 GS_HD float3 row(int i) const { return {m[i][0], m[i][1], m[i][2]}; }64 GS_HD float3 row(int i) const { return {m[i][0], m[i][1], m[i][2]}; }
65 GS_HD float3 col(int j) const { return {m[0][j], m[1][j], m[2][j]}; }65 GS_HD float3 col(int j) const { return {m[0][j], m[1][j], m[2][j]}; }
66};66};
6767
68GS_HD float3 Mul(const Mat3& a, float3 v) {68GS_HD float3 Mul(const Mat3& a, float3 v) {
69 return {dot(a.row(0), v), dot(a.row(1), v), dot(a.row(2), v)};69 return {dot(a.row(0), v), dot(a.row(1), v), dot(a.row(2), v)};
70}70}
71// a^T v.71// a^T v.
72GS_HD float3 MulT(const Mat3& a, float3 v) {72GS_HD float3 MulT(const Mat3& a, float3 v) {
73 return {dot(a.col(0), v), dot(a.col(1), v), dot(a.col(2), v)};73 return {dot(a.col(0), v), dot(a.col(1), v), dot(a.col(2), v)};
74}74}
7575
76// Reinterprets bits; used to pack small integers into float records.76// Reinterprets bits; used to pack small integers into float records.
77GS_HD float AsFloat(uint32_t u) {77GS_HD float AsFloat(uint32_t u) {
78 union {78 union {
79 uint32_t u;79 uint32_t u;
80 float f;80 float f;
81 } c{u};81 } c{u};
82 return c.f;82 return c.f;
83}83}
84GS_HD uint32_t AsUint(float f) {84GS_HD uint32_t AsUint(float f) {
85 union {85 union {
86 float f;86 float f;
87 uint32_t u;87 uint32_t u;
88 } c{f};88 } c{f};
89 return c.u;89 return c.u;
90}90}
9191
92// Number of floats in the packed camera buffer built by `camera.py`:92// Number of floats in the packed camera buffer built by `camera.py`:
93// [R (9, row-major world-to-camera), t (3), fx, fy, cx, cy, position (3), 0].93// [R (9, row-major world-to-camera), t (3), fx, fy, cx, cy, position (3), 0].
94inline constexpr int kCameraFloats = 20;94inline constexpr int kCameraFloats = 20;
9595
96// Pinhole camera with OpenCV axes (+x right, +y down, +z forward). Pixel (i, j)96// Pinhole camera with OpenCV axes (+x right, +y down, +z forward). Pixel (i, j)
97// is sampled at coordinate (j, i), as in the Inria rasterizer.97// is sampled at coordinate (j, i), as in the Inria rasterizer.
98struct Camera {98struct Camera {
99 Mat3 rotation; // World-to-camera rotation.99 Mat3 rotation; // World-to-camera rotation.
100 float3 translation;100 float3 translation;
101 float fx, fy, cx, cy;101 float fx, fy, cx, cy;
102 float3 position; // Camera center in world space.102 float3 position; // Camera center in world space.
103 int width, height;103 int width, height;
104104
105 GS_HD static Camera Load(const float* p, int width, int height) {105 GS_HD static Camera Load(const float* p, int width, int height) {
106 Camera c;106 Camera c;
107 for (int i = 0; i < 3; ++i)107 for (int i = 0; i < 3; ++i)
108 for (int j = 0; j < 3; ++j) c.rotation.m[i][j] = p[3 * i + j];108 for (int j = 0; j < 3; ++j) c.rotation.m[i][j] = p[3 * i + j];
109 c.translation = {p[9], p[10], p[11]};109 c.translation = {p[9], p[10], p[11]};
110 c.fx = p[12];110 c.fx = p[12];
111 c.fy = p[13];111 c.fy = p[13];
112 c.cx = p[14];112 c.cx = p[14];
113 c.cy = p[15];113 c.cy = p[15];
114 c.position = {p[16], p[17], p[18]};114 c.position = {p[16], p[17], p[18]};
115 c.width = width;115 c.width = width;
116 c.height = height;116 c.height = height;
117 return c;117 return c;
118 }118 }
119119
120 GS_HD float3 ToView(float3 p) const { return Mul(rotation, p) + translation; }120 GS_HD float3 ToView(float3 p) const { return Mul(rotation, p) + translation; }
121 // Tangent of the half field of view, as the Inria rasterizer defines it.121 // Tangent of the half field of view, as the Inria rasterizer defines it.
122 GS_HD float TanHalfFovX() const { return width / (2.0f * fx); }122 GS_HD float TanHalfFovX() const { return width / (2.0f * fx); }
123 GS_HD float TanHalfFovY() const { return height / (2.0f * fy); }123 GS_HD float TanHalfFovY() const { return height / (2.0f * fy); }
124};124};
125125
126// Screen tiling. Tiles are `kTileSize` x `kTileSize` pixels; one thread block126// Screen tiling. Tiles are `kTileSize` x `kTileSize` pixels; one thread block
127// rasterizes one tile.127// rasterizes one tile.
128inline constexpr int kTileSize = 16;128inline constexpr int kTileSize = 16;
129inline constexpr int kTilePixels = kTileSize * kTileSize;129inline constexpr int kTilePixels = kTileSize * kTileSize;
130130
131struct TileGrid {131struct TileGrid {
132 int tiles_x, tiles_y;132 int tiles_x, tiles_y;
133 GS_HD static TileGrid For(int width, int height) {133 GS_HD static TileGrid For(int width, int height) {
134 return {(width + kTileSize - 1) / kTileSize,134 return {(width + kTileSize - 1) / kTileSize,
135 (height + kTileSize - 1) / kTileSize};135 (height + kTileSize - 1) / kTileSize};
136 }136 }
137 GS_HD int num_tiles() const { return tiles_x * tiles_y; }137 GS_HD int num_tiles() const { return tiles_x * tiles_y; }
138};138};
139139
140// Smallest alpha that contributes to a pixel; smaller ones are skipped.140// Smallest alpha that contributes to a pixel; smaller ones are skipped.
141inline constexpr float kMinAlpha = 1.0f / 255.0f;141inline constexpr float kMinAlpha = 1.0f / 255.0f;
142inline constexpr float kMaxAlpha = 0.99f;142inline constexpr float kMaxAlpha = 0.99f;
143// Transmittance below which a pixel stops accumulating.143// Transmittance below which a pixel stops accumulating.
144inline constexpr float kMinTransmittance = 1e-4f;144inline constexpr float kMinTransmittance = 1e-4f;
145145
146// One splat as stored in memory: three float4 so the rasterizer issues three146// One splat as stored in memory: three float4 so the rasterizer issues three
147// 16-byte loads. Layout:147// 16-byte loads. Layout:
148// p0 = (x, y, conic.a, conic.b)148// p0 = (x, y, conic.a, conic.b)
149// p1 = (conic.c, opacity, limit, radius) radius 0 => culled149// p1 = (conic.c, opacity, limit, radius) radius 0 => culled
150// p2 = (r, g, b, min_power)150// p2 = (r, g, b, min_power)
151inline constexpr int kSplatFloats = 12;151inline constexpr int kSplatFloats = 12;
152struct alignas(16) Splat {152struct alignas(16) Splat {
153 float4 p0, p1, p2;153 float4 p0, p1, p2;
154 GS_HD float2 xy() const { return {p0.x, p0.y}; }154 GS_HD float2 xy() const { return {p0.x, p0.y}; }
155 GS_HD float3 conic() const { return {p0.z, p0.w, p1.x}; }155 GS_HD float3 conic() const { return {p0.z, p0.w, p1.x}; }
156 GS_HD float opacity() const { return p1.y; }156 GS_HD float opacity() const { return p1.y; }
157 GS_HD float limit() const { return p1.z; }157 GS_HD float limit() const { return p1.z; }
158 GS_HD int radius() const { return static_cast<int>(p1.w); }158 GS_HD int radius() const { return static_cast<int>(p1.w); }
159 GS_HD float3 rgb() const { return {p2.x, p2.y, p2.z}; }159 GS_HD float3 rgb() const { return {p2.x, p2.y, p2.z}; }
160};160};
161161
162// Gradient w.r.t. a splat's rasterized quantities, accumulated by the raster162// Gradient w.r.t. a splat's rasterized quantities, accumulated by the raster
163// backward pass: (dx, dy, da, db, dc, dopacity, dr, dg, db).163// backward pass: (dx, dy, da, db, dc, dopacity, dr, dg, db).
164inline constexpr int kSplatGradFloats = 9;164inline constexpr int kSplatGradFloats = 9;
165165
166// Half-open tile rectangle [x0, x1) x [y0, y1).166// Half-open tile rectangle [x0, x1) x [y0, y1).
167struct TileRect {167struct TileRect {
168 int x0, y0, x1, y1;168 int x0, y0, x1, y1;
169 GS_HD int area() const { return (x1 - x0) * (y1 - y0); }169 GS_HD int area() const { return (x1 - x0) * (y1 - y0); }
170};170};
171171
172// Tile rectangle of a splat of integer bounding `radius`, identical to the172// Tile rectangle of a splat of integer bounding `radius`, identical to the
173// reference rasterizer.173// reference rasterizer.
174GS_HD TileRect RectFor(float2 xy, int radius, const TileGrid& grid) {174GS_HD TileRect RectFor(float2 xy, int radius, const TileGrid& grid) {
175 const float r = static_cast<float>(radius);175 const float r = static_cast<float>(radius);
176 auto lo = [](float v, int n) {176 auto lo = [](float v, int n) {
177 return min(n, max(0, static_cast<int>(v / kTileSize)));177 return min(n, max(0, static_cast<int>(v / kTileSize)));
178 };178 };
179 auto hi = [](float v, int n) {179 auto hi = [](float v, int n) {
180 return min(n, max(0, static_cast<int>((v + kTileSize - 1) / kTileSize)));180 return min(n, max(0, static_cast<int>((v + kTileSize - 1) / kTileSize)));
181 };181 };
182 return {lo(xy.x - r, grid.tiles_x), lo(xy.y - r, grid.tiles_y),182 return {lo(xy.x - r, grid.tiles_x), lo(xy.y - r, grid.tiles_y),
183 hi(xy.x + r, grid.tiles_x), hi(xy.y + r, grid.tiles_y)};183 hi(xy.x + r, grid.tiles_x), hi(xy.y + r, grid.tiles_y)};
184}184}
185185
186// How splats are assigned to tiles.186// How splats are assigned to tiles.
187enum class Culling : int {187enum class Culling : int {
188 // Every tile of the bounding rectangle (reference behaviour).188 // Every tile of the bounding rectangle (reference behaviour).
189 kRect = 0,189 kRect = 0,
190 // Tiles of the rectangle where some pixel can reach alpha >= 1/255. Gives190 // Tiles of the rectangle where some pixel can reach alpha >= 1/255. Gives
191 // the same image: skipped (splat, tile) pairs never contribute.191 // the same image: skipped (splat, tile) pairs never contribute.
192 kEllipse = 1,192 kEllipse = 1,
193};193};
194194
195// alpha = o exp(-q/2) >= 1/255 <=> q <= 2 ln(255 o). Writes the bound on q195// alpha = o exp(-q/2) >= 1/255 <=> q <= 2 ln(255 o). Writes the bound on q
196// with margins that keep tests conservative under float rounding of the196// with margins that keep tests conservative under float rounding of the
197// rasterizer's power. Returns false if no pixel can reach 1/255.197// rasterizer's power. Returns false if no pixel can reach 1/255.
198GS_HD bool AlphaLimit(float opacity, float& limit) {198GS_HD bool AlphaLimit(float opacity, float& limit) {
199 const float o255 = opacity * 255.0f;199 const float o255 = opacity * 255.0f;
200 if (!(o255 >= 0.999f)) return false;200 if (!(o255 >= 0.999f)) return false;
201 limit = 2.0f * logf(fmaxf(o255, 1.0f)) * 1.001f + 0.02f;201 limit = 2.0f * logf(fmaxf(o255, 1.0f)) * 1.001f + 0.02f;
202 return true;202 return true;
203}203}
204204
205// Pad (pixels) on ellipse extents, against rounding.205// Pad (pixels) on ellipse extents, against rounding.
206inline constexpr float kSpanPadPx = 0.01f;206inline constexpr float kSpanPadPx = 0.01f;
207207
208// Precomputed splat-level ellipse constants for fast band intersection tests208// Precomputed splat-level ellipse constants for fast band intersection tests
209// of {q(d) <= limit}, where q(d) = a dx^2 + 2 b dx dy + c dy^2.209// of {q(d) <= limit}, where q(d) = a dx^2 + 2 b dx dy + c dy^2.
210struct EllipseBounds {210struct EllipseBounds {
211 float a, b, c;211 float a, b, c;
212 float det;212 float det;
213 float inv_a;213 float inv_a;
214 float a_limit;214 float a_limit;
215 float y_ext;215 float y_ext;
216 float dy_peak;216 float dy_peak;
217217
218 GS_D static bool Init(float3 conic, float opacity, float cached_limit,218 GS_D static bool Init(float3 conic, float opacity, float cached_limit,
219 EllipseBounds& out) {219 EllipseBounds& out) {
220 float limit = cached_limit;220 float limit = cached_limit;
221 if (limit <= 0.0f && !AlphaLimit(opacity, limit)) return false;221 if (limit <= 0.0f && !AlphaLimit(opacity, limit)) return false;
222 out.a = conic.x;222 out.a = conic.x;
223 out.b = conic.y;223 out.b = conic.y;
224 out.c = conic.z;224 out.c = conic.z;
225 out.det = out.a * out.c - out.b * out.b;225 out.det = out.a * out.c - out.b * out.b;
226 if (!(out.det > 0.0f && out.a > 0.0f && out.c > 0.0f)) return false;226 if (!(out.det > 0.0f && out.a > 0.0f && out.c > 0.0f)) return false;
227 out.inv_a = 1.0f / out.a;227 out.inv_a = 1.0f / out.a;
228 out.a_limit = out.a * limit;228 out.a_limit = out.a * limit;
229 const float limit_over_det = limit / out.det;229 const float limit_over_det = limit / out.det;
230 out.y_ext = sqrtf(out.a * limit_over_det);230 out.y_ext = sqrtf(out.a * limit_over_det);
231 out.dy_peak = out.b * sqrtf(limit_over_det / out.c);231 out.dy_peak = out.b * sqrtf(limit_over_det / out.c);
232 return true;232 return true;
233 }233 }
234234
235 // Span [x0_px, x1_px] of integer pixel columns hit across integer pixel rows235 // Span [x0_px, x1_px] of integer pixel columns hit across integer pixel rows
236 // Y in [y0_px, y1_px] (inclusive). Returns false if no integer pixel hits.236 // Y in [y0_px, y1_px] (inclusive). Returns false if no integer pixel hits.
237 GS_D bool SpanForPixelRows(float2 xy, int y0_px, int y1_px, int& x0_px,237 GS_D bool SpanForPixelRows(float2 xy, int y0_px, int y1_px, int& x0_px,
238 int& x1_px) const {238 int& x1_px) const {
239 const int ylo_i =239 const int ylo_i =
240 max(y0_px, static_cast<int>(ceilf(xy.y - y_ext - kSpanPadPx)));240 max(y0_px, static_cast<int>(ceilf(xy.y - y_ext - kSpanPadPx)));
241 const int yhi_i =241 const int yhi_i =
242 min(y1_px, static_cast<int>(floorf(xy.y + y_ext + kSpanPadPx)));242 min(y1_px, static_cast<int>(floorf(xy.y + y_ext + kSpanPadPx)));
243 if (ylo_i > yhi_i) return false;243 if (ylo_i > yhi_i) return false;
244 const float ylo = static_cast<float>(ylo_i) - xy.y;244 const float ylo = static_cast<float>(ylo_i) - xy.y;
245 const float yhi = static_cast<float>(yhi_i) - xy.y;245 const float yhi = static_cast<float>(yhi_i) - xy.y;
246 // Boundary x(dy) = (-b dy +- sqrt(a limit - det dy^2)) / a; the right246 // Boundary x(dy) = (-b dy +- sqrt(a limit - det dy^2)) / a; the right
247 // (left) branch is concave (convex) and peaks at dy = -+dy_peak.247 // (left) branch is concave (convex) and peaks at dy = -+dy_peak.
248 const float dyr = fminf(yhi, fmaxf(ylo, -dy_peak));248 const float dyr = fminf(yhi, fmaxf(ylo, -dy_peak));
249 const float dyl = fminf(yhi, fmaxf(ylo, dy_peak));249 const float dyl = fminf(yhi, fmaxf(ylo, dy_peak));
250 const float dyr2 = dyr * dyr, dyl2 = dyl * dyl;250 const float dyr2 = dyr * dyr, dyl2 = dyl * dyl;
251 const float rr = sqrtf(fmaxf(0.0f, a_limit - det * dyr2));251 const float rr = sqrtf(fmaxf(0.0f, a_limit - det * dyr2));
252 const float rl =252 const float rl =
253 (dyr2 == dyl2) ? rr : sqrtf(fmaxf(0.0f, a_limit - det * dyl2));253 (dyr2 == dyl2) ? rr : sqrtf(fmaxf(0.0f, a_limit - det * dyl2));
254 const float hi = (-b * dyr + rr) * inv_a;254 const float hi = (-b * dyr + rr) * inv_a;
255 const float lo = (-b * dyl - rl) * inv_a;255 const float lo = (-b * dyl - rl) * inv_a;
256 x0_px = static_cast<int>(ceilf(xy.x + lo - kSpanPadPx));256 x0_px = static_cast<int>(ceilf(xy.x + lo - kSpanPadPx));
257 x1_px = static_cast<int>(floorf(xy.x + hi + kSpanPadPx));257 x1_px = static_cast<int>(floorf(xy.x + hi + kSpanPadPx));
258 return x0_px <= x1_px;258 return x0_px <= x1_px;
259 }259 }
260};260};
261261
262// Visits every tile row `ty` with the half-open span [tx0, tx1) of tiles that262// Visits every tile row `ty` with the half-open span [tx0, tx1) of tiles that
263// splat `s` (not culled) is binned to. Rows may be empty (tx0 >= tx1).263// splat `s` (not culled) is binned to. Rows may be empty (tx0 >= tx1).
264template <Culling kCulling, typename Fn>264template <Culling kCulling, typename Fn>
265GS_D void ForEachTileRow(const Splat& s, const TileGrid& grid, int width,265GS_D void ForEachTileRow(const Splat& s, const TileGrid& grid, int width,
266 int height, Fn&& fn) {266 int height, Fn&& fn) {
267 const float2 xy = s.xy();267 const float2 xy = s.xy();
268 const TileRect rect = RectFor(xy, s.radius(), grid);268 const TileRect rect = RectFor(xy, s.radius(), grid);
269 if constexpr (kCulling == Culling::kRect) {269 if constexpr (kCulling == Culling::kRect) {
270 for (int ty = rect.y0; ty < rect.y1; ++ty) fn(ty, rect.x0, rect.x1);270 for (int ty = rect.y0; ty < rect.y1; ++ty) fn(ty, rect.x0, rect.x1);
271 } else {271 } else {
272 EllipseBounds eb;272 EllipseBounds eb;
273 if (!EllipseBounds::Init(s.conic(), s.opacity(), s.limit(), eb)) return;273 if (!EllipseBounds::Init(s.conic(), s.opacity(), s.limit(), eb)) return;
274 const int min_y =274 const int min_y =
275 max(0, static_cast<int>(ceilf(xy.y - eb.y_ext - kSpanPadPx)));275 max(0, static_cast<int>(ceilf(xy.y - eb.y_ext - kSpanPadPx)));
276 const int max_y =276 const int max_y =
277 min(height - 1, static_cast<int>(floorf(xy.y + eb.y_ext + kSpanPadPx)));277 min(height - 1, static_cast<int>(floorf(xy.y + eb.y_ext + kSpanPadPx)));
278 if (min_y > max_y) return;278 if (min_y > max_y) return;
279 const int ty0 = max(rect.y0, min_y / kTileSize);279 const int ty0 = max(rect.y0, min_y / kTileSize);
280 const int ty1 = min(rect.y1, (max_y / kTileSize) + 1);280 const int ty1 = min(rect.y1, (max_y / kTileSize) + 1);
281 for (int ty = ty0; ty < ty1; ++ty) {281 for (int ty = ty0; ty < ty1; ++ty) {
282 int x0_px, x1_px;282 int x0_px, x1_px;
283 if (!eb.SpanForPixelRows(xy, ty * kTileSize,283 if (!eb.SpanForPixelRows(xy, ty * kTileSize,
284 min((ty + 1) * kTileSize, height) - 1, x0_px,284 min((ty + 1) * kTileSize, height) - 1, x0_px,
285 x1_px)) {285 x1_px)) {
286 continue;286 continue;
287 }287 }
288 x0_px = max(0, x0_px);288 x0_px = max(0, x0_px);
289 x1_px = min(width - 1, x1_px);289 x1_px = min(width - 1, x1_px);
290 if (x0_px > x1_px) continue;290 if (x0_px > x1_px) continue;
291 const int tx0 = max(rect.x0, x0_px / kTileSize);291 const int tx0 = max(rect.x0, x0_px / kTileSize);
292 const int tx1 = min(rect.x1, (x1_px / kTileSize) + 1);292 const int tx1 = min(rect.x1, (x1_px / kTileSize) + 1);
293 if (tx0 < tx1) fn(ty, tx0, tx1);293 if (tx0 < tx1) fn(ty, tx0, tx1);
294 }294 }
295 }295 }
296}296}
297297
298namespace warp {298namespace warp {
299299
300inline constexpr unsigned kFullMask = 0xffffffffu;300inline constexpr unsigned kFullMask = 0xffffffffu;
301301
302// Standard warp butterfly reduction of an array of `N` per-lane values:302// Reduce-scatter of an array of `N` <= 32 per-lane values over the warp.
303// 5 steps of `__shfl_xor_sync` for each element (`5 * N` shuffles total),
304// after which lane 0 (and every other lane) holds the warp sum of `v[0..N)`.
303//
304// A butterfly where each step halves the payload: at offset `o`, lanes with
305// bit `o` clear keep the low half of the array and send the high half, the
306// others do the opposite. After log2(32) steps every lane holds a single
307// value, v[0], which is the warp total of element `ScatterIndex(N, lane)`
308// (or junk if that is -1). For N = 9 this takes 12 shuffles instead of the
309// 45 of nine independent butterflies, and the totals end in distinct lanes,
310// so writing them out is one instruction.
311
312namespace internal {
305template <int N>313template <int N, int kOffset>
306GS_D void ButterflyReduce(float (&v)[N]) {314GS_D void HalvingStep(float* v, int lane) {
315 constexpr int kHalf = (N + 1) / 2;
316 const bool upper = lane & kOffset;
307#pragma unroll317#pragma unroll
308 for (int offset = 16; offset >= 1; offset >>= 1) {
309#pragma unroll
310 for (int i = 0; i < N; ++i) {318 for (int i = 0; i < kHalf; ++i) {
319 const float lo = v[i];
320 const float hi = i + kHalf < N ? v[i + kHalf] : 0.0f;
321 const float send = upper ? lo : hi;
322 const float keep = upper ? hi : lo;
311 v[i] += __shfl_xor_sync(kFullMask, v[i], offset);323 v[i] = keep + __shfl_xor_sync(kFullMask, send, kOffset);
312 }324 }
313 }325}
326
327template <int N, int kOffset>
328GS_D void ReduceScatterImpl(float* v, int lane) {
329 if constexpr (kOffset > 0) {
330 HalvingStep<N, kOffset>(v, lane);
331 ReduceScatterImpl<(N + 1) / 2, kOffset / 2>(v, lane);
332 }
333}
334} // namespace internal
335
336template <int N>
337GS_D void ReduceScatter(float (&v)[N], int lane) {
338 static_assert(N >= 1 && N <= 32);
339 internal::ReduceScatterImpl<N, 16>(v, lane);
340}
341
342template <int N>
343GS_D void ReduceScatter16(float (&v)[N], int lane) {
344 static_assert(N >= 1 && N <= 16);
345 internal::ReduceScatterImpl<N, 8>(v, lane);
346}
347
348// Element whose warp total lane `lane` holds after `ReduceScatter<N>`, or -1.
349GS_HD constexpr int ScatterIndex(int n, int lane) {
350 // Payload sizes and halves per step, offset 16 first.
351 int sizes[5] = {};
352 int size = n;
353 for (int s = 0; s < 5; ++s) {
354 sizes[s] = size;
355 size = (size + 1) / 2;
356 }
357 int index = 0; // Slot in the final (size 1) array.
358 for (int s = 4; s >= 0; --s) {
359 const int half = (sizes[s] + 1) / 2;
360 if (lane & (16 >> s)) index += half;
361 if (index >= sizes[s]) return -1;
362 }
363 return index;
364}
365
366// Element whose 16-lane half-warp total lane `lane` holds after
367// `ReduceScatter16<N>`, or -1.
368GS_HD constexpr int ScatterIndex16(int n, int lane) {
369 const int half_lane = lane & 15;
370 int sizes[4] = {};
371 int size = n;
372 for (int s = 0; s < 4; ++s) {
373 sizes[s] = size;
374 size = (size + 1) / 2;
375 }
376 int index = 0;
377 for (int s = 3; s >= 0; --s) {
378 const int half = (sizes[s] + 1) / 2;
379 if (half_lane & (8 >> s)) index += half;
380 if (index >= sizes[s]) return -1;
381 }
382 return index;
314}383}
315384
316} // namespace warp385} // namespace warp
317386
↕ Expand 467 unchanged lines (lines 387–853) · in WarpMask
318inline constexpr int kShCoeffs = 16; // Degree 3.387inline constexpr int kShCoeffs = 16; // Degree 3.
319inline constexpr float kNearPlane = 0.2f;388inline constexpr float kNearPlane = 0.2f;
320inline constexpr float kLowPass = 0.3f;389inline constexpr float kLowPass = 0.3f;
321inline constexpr float kFovClamp = 1.3f;390inline constexpr float kFovClamp = 1.3f;
322391
323// Pointers to the trainable parameters, all activated, row-major.392// Pointers to the trainable parameters, all activated, row-major.
324struct GaussianParams {393struct GaussianParams {
325 const float* means; // (N, 3)394 const float* means; // (N, 3)
326 const float* scales; // (N, 3)395 const float* scales; // (N, 3)
327 const float* quats; // (N, 4) as (w, x, y, z), not normalized.396 const float* quats; // (N, 4) as (w, x, y, z), not normalized.
328 const float* opacities; // (N,)397 const float* opacities; // (N,)
329 const void* sh; // (N, 16, 3) of `float` or `ShBf16` (see `ShIo`).398 const void* sh; // (N, 16, 3) of `float` or `ShBf16` (see `ShIo`).
330 int n;399 int n;
331};400};
332401
333// Gradients w.r.t. `GaussianParams`, same layout and SH storage type.402// Gradients w.r.t. `GaussianParams`, same layout and SH storage type.
334struct GaussianGrads {403struct GaussianGrads {
335 float* means;404 float* means;
336 float* scales;405 float* scales;
337 float* quats;406 float* quats;
338 float* opacities;407 float* opacities;
339 void* sh;408 void* sh;
340};409};
341410
342// Screen-space quantities needed by rasterization. `conic` is the inverse 2D411// Screen-space quantities needed by rasterization. `conic` is the inverse 2D
343// covariance (a, b, c) with power = -0.5 (a dx^2 + c dy^2) - b dx dy.412// covariance (a, b, c) with power = -0.5 (a dx^2 + c dy^2) - b dx dy.
344struct Projection {413struct Projection {
345 float2 xy;414 float2 xy;
346 float depth;415 float depth;
347 float3 conic;416 float3 conic;
348 float opacity;417 float opacity;
349 float limit;418 float limit;
350 float3 rgb;419 float3 rgb;
351 int radius; // 0 means culled.420 int radius; // 0 means culled.
352};421};
353422
354// ----------------------------------------------------------------------------423// ----------------------------------------------------------------------------
355// SH coefficient storage. A row holds the 16 x 3 coefficients of one Gaussian424// SH coefficient storage. A row holds the 16 x 3 coefficients of one Gaussian
356// and may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32.425// and may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32.
357426
358using ShBf16 = uint16_t; // bfloat16 bits.427using ShBf16 = uint16_t; // bfloat16 bits.
359428
360template <typename T>429template <typename T>
361struct ShIo;430struct ShIo;
362431
363template <>432template <>
364struct ShIo<float> {433struct ShIo<float> {
365 GS_D static void LoadRow(const float* row, float3 coeffs[kShCoeffs]) {434 GS_D static void LoadRow(const float* row, float3 coeffs[kShCoeffs]) {
366 const float4* p = reinterpret_cast<const float4*>(row);435 const float4* p = reinterpret_cast<const float4*>(row);
367 float raw[3 * kShCoeffs];436 float raw[3 * kShCoeffs];
368#pragma unroll437#pragma unroll
369 for (int k = 0; k < 12; ++k) {438 for (int k = 0; k < 12; ++k) {
370 const float4 q = __ldg(p + k);439 const float4 q = __ldg(p + k);
371 raw[4 * k] = q.x;440 raw[4 * k] = q.x;
372 raw[4 * k + 1] = q.y;441 raw[4 * k + 1] = q.y;
373 raw[4 * k + 2] = q.z;442 raw[4 * k + 2] = q.z;
374 raw[4 * k + 3] = q.w;443 raw[4 * k + 3] = q.w;
375 }444 }
376#pragma unroll445#pragma unroll
377 for (int c = 0; c < kShCoeffs; ++c) {446 for (int c = 0; c < kShCoeffs; ++c) {
378 coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]};447 coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]};
379 }448 }
380 }449 }
381 GS_D static void StoreRow(float* row, const float3 dsh[kShCoeffs]) {450 GS_D static void StoreRow(float* row, const float3 dsh[kShCoeffs]) {
382 float raw[3 * kShCoeffs];451 float raw[3 * kShCoeffs];
383#pragma unroll452#pragma unroll
384 for (int c = 0; c < kShCoeffs; ++c) {453 for (int c = 0; c < kShCoeffs; ++c) {
385 raw[3 * c] = dsh[c].x;454 raw[3 * c] = dsh[c].x;
386 raw[3 * c + 1] = dsh[c].y;455 raw[3 * c + 1] = dsh[c].y;
387 raw[3 * c + 2] = dsh[c].z;456 raw[3 * c + 2] = dsh[c].z;
388 }457 }
389 float4* p = reinterpret_cast<float4*>(row);458 float4* p = reinterpret_cast<float4*>(row);
390#pragma unroll459#pragma unroll
391 for (int k = 0; k < 12; ++k) {460 for (int k = 0; k < 12; ++k) {
392 p[k] = {raw[4 * k], raw[4 * k + 1], raw[4 * k + 2], raw[4 * k + 3]};461 p[k] = {raw[4 * k], raw[4 * k + 1], raw[4 * k + 2], raw[4 * k + 3]};
393 }462 }
394 }463 }
395};464};
396465
397template <>466template <>
398struct ShIo<ShBf16> {467struct ShIo<ShBf16> {
399 GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); }468 GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); }
400 GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); }469 GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); }
401 // Round to nearest even (inputs are finite).470 // Round to nearest even (inputs are finite).
402 GS_D static uint32_t Round(float f) {471 GS_D static uint32_t Round(float f) {
403 const uint32_t u = __float_as_uint(f);472 const uint32_t u = __float_as_uint(f);
404 return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16;473 return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16;
405 }474 }
406 GS_D static uint32_t Pack(float lo, float hi) {475 GS_D static uint32_t Pack(float lo, float hi) {
407 return Round(lo) | (Round(hi) << 16);476 return Round(lo) | (Round(hi) << 16);
408 }477 }
409 GS_D static void LoadRow(const ShBf16* row, float3 coeffs[kShCoeffs]) {478 GS_D static void LoadRow(const ShBf16* row, float3 coeffs[kShCoeffs]) {
410 const uint2* p = reinterpret_cast<const uint2*>(row);479 const uint2* p = reinterpret_cast<const uint2*>(row);
411 float raw[3 * kShCoeffs];480 float raw[3 * kShCoeffs];
412#pragma unroll481#pragma unroll
413 for (int k = 0; k < 12; ++k) {482 for (int k = 0; k < 12; ++k) {
414 const uint2 w = __ldg(p + k);483 const uint2 w = __ldg(p + k);
415 raw[4 * k] = Lo(w.x);484 raw[4 * k] = Lo(w.x);
416 raw[4 * k + 1] = Hi(w.x);485 raw[4 * k + 1] = Hi(w.x);
417 raw[4 * k + 2] = Lo(w.y);486 raw[4 * k + 2] = Lo(w.y);
418 raw[4 * k + 3] = Hi(w.y);487 raw[4 * k + 3] = Hi(w.y);
419 }488 }
420#pragma unroll489#pragma unroll
421 for (int c = 0; c < kShCoeffs; ++c) {490 for (int c = 0; c < kShCoeffs; ++c) {
422 coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]};491 coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]};
423 }492 }
424 }493 }
425 GS_D static void StoreRow(ShBf16* row, const float3 dsh[kShCoeffs]) {494 GS_D static void StoreRow(ShBf16* row, const float3 dsh[kShCoeffs]) {
426 float raw[3 * kShCoeffs];495 float raw[3 * kShCoeffs];
427#pragma unroll496#pragma unroll
428 for (int c = 0; c < kShCoeffs; ++c) {497 for (int c = 0; c < kShCoeffs; ++c) {
429 raw[3 * c] = dsh[c].x;498 raw[3 * c] = dsh[c].x;
430 raw[3 * c + 1] = dsh[c].y;499 raw[3 * c + 1] = dsh[c].y;
431 raw[3 * c + 2] = dsh[c].z;500 raw[3 * c + 2] = dsh[c].z;
432 }501 }
433 uint2* p = reinterpret_cast<uint2*>(row);502 uint2* p = reinterpret_cast<uint2*>(row);
434#pragma unroll503#pragma unroll
435 for (int k = 0; k < 12; ++k) {504 for (int k = 0; k < 12; ++k) {
436 p[k] = {Pack(raw[4 * k], raw[4 * k + 1]),505 p[k] = {Pack(raw[4 * k], raw[4 * k + 1]),
437 Pack(raw[4 * k + 2], raw[4 * k + 3])};506 Pack(raw[4 * k + 2], raw[4 * k + 3])};
438 }507 }
439 }508 }
440};509};
441510
442template <typename ShT>511template <typename ShT>
443GS_D const ShT* ShRow(const void* sh, int i) {512GS_D const ShT* ShRow(const void* sh, int i) {
444 return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;513 return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;
445}514}
446template <typename ShT>515template <typename ShT>
447GS_D ShT* ShRow(void* sh, int i) {516GS_D ShT* ShRow(void* sh, int i) {
448 return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;517 return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;
449}518}
450519
451// ----------------------------------------------------------------------------520// ----------------------------------------------------------------------------
452// Spherical harmonics (degree 3, Inria sign convention).521// Spherical harmonics (degree 3, Inria sign convention).
453522
454namespace sh {523namespace sh {
455inline constexpr float kC0 = 0.28209479177387814f;524inline constexpr float kC0 = 0.28209479177387814f;
456inline constexpr float kC1 = 0.4886025119029199f;525inline constexpr float kC1 = 0.4886025119029199f;
457inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f,526inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f,
458 0.31539156525252005f, -1.0925484305920792f,527 0.31539156525252005f, -1.0925484305920792f,
459 0.5462742152960396f};528 0.5462742152960396f};
460inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f,529inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f,
461 -0.4570457994644658f, 0.3731763325901154f,530 -0.4570457994644658f, 0.3731763325901154f,
462 -0.4570457994644658f, 1.445305721320277f,531 -0.4570457994644658f, 1.445305721320277f,
463 -0.5900435899266435f};532 -0.5900435899266435f};
464533
465// Basis values for a unit direction.534// Basis values for a unit direction.
466GS_D void Basis(float3 d, float b[kShCoeffs]) {535GS_D void Basis(float3 d, float b[kShCoeffs]) {
467 const float x = d.x, y = d.y, z = d.z;536 const float x = d.x, y = d.y, z = d.z;
468 const float xx = x * x, yy = y * y, zz = z * z;537 const float xx = x * x, yy = y * y, zz = z * z;
469 b[0] = kC0;538 b[0] = kC0;
470 b[1] = -kC1 * y;539 b[1] = -kC1 * y;
471 b[2] = kC1 * z;540 b[2] = kC1 * z;
472 b[3] = -kC1 * x;541 b[3] = -kC1 * x;
473 b[4] = kC2[0] * x * y;542 b[4] = kC2[0] * x * y;
474 b[5] = kC2[1] * y * z;543 b[5] = kC2[1] * y * z;
475 b[6] = kC2[2] * (2.0f * zz - xx - yy);544 b[6] = kC2[2] * (2.0f * zz - xx - yy);
476 b[7] = kC2[3] * x * z;545 b[7] = kC2[3] * x * z;
477 b[8] = kC2[4] * (xx - yy);546 b[8] = kC2[4] * (xx - yy);
478 b[9] = kC3[0] * y * (3.0f * xx - yy);547 b[9] = kC3[0] * y * (3.0f * xx - yy);
479 b[10] = kC3[1] * x * y * z;548 b[10] = kC3[1] * x * y * z;
480 b[11] = kC3[2] * y * (4.0f * zz - xx - yy);549 b[11] = kC3[2] * y * (4.0f * zz - xx - yy);
481 b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy);550 b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy);
482 b[13] = kC3[4] * x * (4.0f * zz - xx - yy);551 b[13] = kC3[4] * x * (4.0f * zz - xx - yy);
483 b[14] = kC3[5] * z * (xx - yy);552 b[14] = kC3[5] * z * (xx - yy);
484 b[15] = kC3[6] * x * (xx - 3.0f * yy);553 b[15] = kC3[6] * x * (xx - 3.0f * yy);
485}554}
486555
487// Partial derivatives of every basis function w.r.t. (x, y, z) of the unit556// Partial derivatives of every basis function w.r.t. (x, y, z) of the unit
488// direction (treated as free coordinates).557// direction (treated as free coordinates).
489GS_D void BasisGrad(float3 d, float3 g[kShCoeffs]) {558GS_D void BasisGrad(float3 d, float3 g[kShCoeffs]) {
490 const float x = d.x, y = d.y, z = d.z;559 const float x = d.x, y = d.y, z = d.z;
491 const float xx = x * x, yy = y * y, zz = z * z;560 const float xx = x * x, yy = y * y, zz = z * z;
492 g[0] = {0.0f, 0.0f, 0.0f};561 g[0] = {0.0f, 0.0f, 0.0f};
493 g[1] = {0.0f, -kC1, 0.0f};562 g[1] = {0.0f, -kC1, 0.0f};
494 g[2] = {0.0f, 0.0f, kC1};563 g[2] = {0.0f, 0.0f, kC1};
495 g[3] = {-kC1, 0.0f, 0.0f};564 g[3] = {-kC1, 0.0f, 0.0f};
496 g[4] = kC2[0] * float3{y, x, 0.0f};565 g[4] = kC2[0] * float3{y, x, 0.0f};
497 g[5] = kC2[1] * float3{0.0f, z, y};566 g[5] = kC2[1] * float3{0.0f, z, y};
498 g[6] = kC2[2] * float3{-2.0f * x, -2.0f * y, 4.0f * z};567 g[6] = kC2[2] * float3{-2.0f * x, -2.0f * y, 4.0f * z};
499 g[7] = kC2[3] * float3{z, 0.0f, x};568 g[7] = kC2[3] * float3{z, 0.0f, x};
500 g[8] = kC2[4] * float3{2.0f * x, -2.0f * y, 0.0f};569 g[8] = kC2[4] * float3{2.0f * x, -2.0f * y, 0.0f};
501 g[9] = kC3[0] * float3{6.0f * x * y, 3.0f * (xx - yy), 0.0f};570 g[9] = kC3[0] * float3{6.0f * x * y, 3.0f * (xx - yy), 0.0f};
502 g[10] = kC3[1] * float3{y * z, x * z, x * y};571 g[10] = kC3[1] * float3{y * z, x * z, x * y};
503 g[11] =572 g[11] =
504 kC3[2] * float3{-2.0f * x * y, 4.0f * zz - xx - 3.0f * yy, 8.0f * y * z};573 kC3[2] * float3{-2.0f * x * y, 4.0f * zz - xx - 3.0f * yy, 8.0f * y * z};
505 g[12] = kC3[3] * float3{-6.0f * x * z, -6.0f * y * z,574 g[12] = kC3[3] * float3{-6.0f * x * z, -6.0f * y * z,
506 6.0f * zz - 3.0f * xx - 3.0f * yy};575 6.0f * zz - 3.0f * xx - 3.0f * yy};
507 g[13] =576 g[13] =
508 kC3[4] * float3{4.0f * zz - 3.0f * xx - yy, -2.0f * x * y, 8.0f * x * z};577 kC3[4] * float3{4.0f * zz - 3.0f * xx - yy, -2.0f * x * y, 8.0f * x * z};
509 g[14] = kC3[5] * float3{2.0f * x * z, -2.0f * y * z, xx - yy};578 g[14] = kC3[5] * float3{2.0f * x * z, -2.0f * y * z, xx - yy};
510 g[15] = kC3[6] * float3{3.0f * (xx - yy), -6.0f * x * y, 0.0f};579 g[15] = kC3[6] * float3{3.0f * (xx - yy), -6.0f * x * y, 0.0f};
511}580}
512581
513// Pre-clamp color 0.5 + sum_k b_k sh_k, loading the full 16-basis SH row.582// Pre-clamp color 0.5 + sum_k b_k sh_k, loading the full 16-basis SH row.
514template <typename ShT>583template <typename ShT>
515GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) {584GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) {
516 float3 coeffs[kShCoeffs];585 float3 coeffs[kShCoeffs];
517 ShIo<ShT>::LoadRow(sh_row, coeffs);586 ShIo<ShT>::LoadRow(sh_row, coeffs);
518 float3 c = {0.5f, 0.5f, 0.5f};587 float3 c = {0.5f, 0.5f, 0.5f};
519#pragma unroll588#pragma unroll
520 for (int k = 0; k < kShCoeffs; ++k) {589 for (int k = 0; k < kShCoeffs; ++k) {
521 c += b[k] * coeffs[k];590 c += b[k] * coeffs[k];
522 }591 }
523 return c;592 return c;
524}593}
525} // namespace sh594} // namespace sh
526595
527// ----------------------------------------------------------------------------596// ----------------------------------------------------------------------------
528// Covariance helpers.597// Covariance helpers.
529598
530// Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z).599// Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z).
531GS_D Mat3 QuatToRotation(float4 q) {600GS_D Mat3 QuatToRotation(float4 q) {
532 const float w = q.x, x = q.y, y = q.z, z = q.w;601 const float w = q.x, x = q.y, y = q.z, z = q.w;
533 Mat3 r;602 Mat3 r;
534 r.m[0][0] = 1.0f - 2.0f * (y * y + z * z);603 r.m[0][0] = 1.0f - 2.0f * (y * y + z * z);
535 r.m[0][1] = 2.0f * (x * y - w * z);604 r.m[0][1] = 2.0f * (x * y - w * z);
536 r.m[0][2] = 2.0f * (x * z + w * y);605 r.m[0][2] = 2.0f * (x * z + w * y);
537 r.m[1][0] = 2.0f * (x * y + w * z);606 r.m[1][0] = 2.0f * (x * y + w * z);
538 r.m[1][1] = 1.0f - 2.0f * (x * x + z * z);607 r.m[1][1] = 1.0f - 2.0f * (x * x + z * z);
539 r.m[1][2] = 2.0f * (y * z - w * x);608 r.m[1][2] = 2.0f * (y * z - w * x);
540 r.m[2][0] = 2.0f * (x * z - w * y);609 r.m[2][0] = 2.0f * (x * z - w * y);
541 r.m[2][1] = 2.0f * (y * z + w * x);610 r.m[2][1] = 2.0f * (y * z + w * x);
542 r.m[2][2] = 1.0f - 2.0f * (x * x + y * y);611 r.m[2][2] = 1.0f - 2.0f * (x * x + y * y);
543 return r;612 return r;
544}613}
545614
546// Symmetric 3x3 matrix.615// Symmetric 3x3 matrix.
547struct Sym3 {616struct Sym3 {
548 float xx, xy, xz, yy, yz, zz;617 float xx, xy, xz, yy, yz, zz;
549 GS_D float3 Mul(float3 v) const {618 GS_D float3 Mul(float3 v) const {
550 return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z,619 return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z,
551 xz * v.x + yz * v.y + zz * v.z};620 xz * v.x + yz * v.y + zz * v.z};
552 }621 }
553};622};
554623
555// Sigma = A A^T with A = R diag(s).624// Sigma = A A^T with A = R diag(s).
556GS_D Sym3 Covariance3D(const Mat3& r, float3 s) {625GS_D Sym3 Covariance3D(const Mat3& r, float3 s) {
557 Mat3 a;626 Mat3 a;
558 const float sv[3] = {s.x, s.y, s.z};627 const float sv[3] = {s.x, s.y, s.z};
559#pragma unroll628#pragma unroll
560 for (int i = 0; i < 3; ++i)629 for (int i = 0; i < 3; ++i)
561#pragma unroll630#pragma unroll
562 for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j];631 for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j];
563 return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)),632 return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)),
564 dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)),633 dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)),
565 dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))};634 dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))};
566}635}
567636
568// Intermediate values of the projection shared by forward and VJP.637// Intermediate values of the projection shared by forward and VJP.
569struct ProjectionState {638struct ProjectionState {
570 float3 t; // View-space mean.639 float3 t; // View-space mean.
571 float tx_c, ty_c; // Clamped x, y used by the Jacobian.640 float tx_c, ty_c; // Clamped x, y used by the Jacobian.
572 bool x_clamped, y_clamped;641 bool x_clamped, y_clamped;
573 float3 m0, m1; // Rows of M = J W (2x3).642 float3 m0, m1; // Rows of M = J W (2x3).
574 Sym3 sigma; // World-space 3D covariance.643 Sym3 sigma; // World-space 3D covariance.
575 float a, b, c; // Dilated 2D covariance.644 float a, b, c; // Dilated 2D covariance.
576};645};
577646
578// Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane).647// Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane).
579GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale,648GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale,
580 float4 quat, ProjectionState& st) {649 float4 quat, ProjectionState& st) {
581 st.t = t;650 st.t = t;
582 const float tz = t.z;651 const float tz = t.z;
583 const float limx = kFovClamp * cam.TanHalfFovX();652 const float limx = kFovClamp * cam.TanHalfFovX();
584 const float limy = kFovClamp * cam.TanHalfFovY();653 const float limy = kFovClamp * cam.TanHalfFovY();
585 const float txtz = t.x / tz, tytz = t.y / tz;654 const float txtz = t.x / tz, tytz = t.y / tz;
586 st.x_clamped = txtz < -limx || txtz > limx;655 st.x_clamped = txtz < -limx || txtz > limx;
587 st.y_clamped = tytz < -limy || tytz > limy;656 st.y_clamped = tytz < -limy || tytz > limy;
588 st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;657 st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;
589 st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;658 st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;
590 const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz);659 const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz);
591 const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz);660 const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz);
592 const Mat3& w = cam.rotation;661 const Mat3& w = cam.rotation;
593 st.m0 = j00 * w.row(0) + j02 * w.row(2);662 st.m0 = j00 * w.row(0) + j02 * w.row(2);
594 st.m1 = j11 * w.row(1) + j12 * w.row(2);663 st.m1 = j11 * w.row(1) + j12 * w.row(2);
595 st.sigma = Covariance3D(QuatToRotation(quat), scale);664 st.sigma = Covariance3D(QuatToRotation(quat), scale);
596 const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1);665 const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1);
597 st.a = dot(st.m0, s0) + kLowPass;666 st.a = dot(st.m0, s0) + kLowPass;
598 st.b = dot(st.m0, s1);667 st.b = dot(st.m0, s1);
599 st.c = dot(st.m1, s1) + kLowPass;668 st.c = dot(st.m1, s1) + kLowPass;
600}669}
601670
602// Computes everything up to the 2D covariance. Returns false if culled by the671// Computes everything up to the 2D covariance. Returns false if culled by the
603// near plane.672// near plane.
604GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale,673GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale,
605 float4 quat, ProjectionState& st) {674 float4 quat, ProjectionState& st) {
606 const float3 t = cam.ToView(mean);675 const float3 t = cam.ToView(mean);
607 if (t.z <= kNearPlane) return false;676 if (t.z <= kNearPlane) return false;
608 ProjectCovarianceFromView(cam, t, scale, quat, st);677 ProjectCovarianceFromView(cam, t, scale, quat, st);
609 return true;678 return true;
610}679}
611680
612// Integer bounding radius (pixels) of the 2D covariance, as in the reference.681// Integer bounding radius (pixels) of the 2D covariance, as in the reference.
613GS_D int BoundingRadius(float a, float b, float c) {682GS_D int BoundingRadius(float a, float b, float c) {
614 const float det = a * c - b * b;683 const float det = a * c - b * b;
615 const float mid = 0.5f * (a + c);684 const float mid = 0.5f * (a + c);
616 const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det));685 const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det));
617 return static_cast<int>(ceilf(3.0f * sqrtf(lambda)));686 return static_cast<int>(ceilf(3.0f * sqrtf(lambda)));
618}687}
619688
620GS_D float2 ProjectMean(const Camera& cam, float3 t) {689GS_D float2 ProjectMean(const Camera& cam, float3 t) {
621 return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy};690 return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy};
622}691}
623692
624GS_D float3 LoadMean(const GaussianParams& p, int i) {693GS_D float3 LoadMean(const GaussianParams& p, int i) {
625 return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]};694 return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]};
626}695}
627696
628// Unit direction from the camera center to `mean`, and 1 / distance.697// Unit direction from the camera center to `mean`, and 1 / distance.
629GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) {698GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) {
630 const float3 v = mean - cam.position;699 const float3 v = mean - cam.position;
631 inv_len = rsqrtf(dot(v, v));700 inv_len = rsqrtf(dot(v, v));
632 return inv_len * v;701 return inv_len * v;
633}702}
634703
635// Gradient of the loss w.r.t. one splat's screen-space quantities.704// Gradient of the loss w.r.t. one splat's screen-space quantities.
636struct ProjectionGrad {705struct ProjectionGrad {
637 float2 xy;706 float2 xy;
638 float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`.707 float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`.
639 float opacity;708 float opacity;
640 float3 rgb;709 float3 rgb;
641};710};
642711
643// Image geometry passed by value to every kernel.712// Image geometry passed by value to every kernel.
644struct Frame {713struct Frame {
645 const float* camera; // Device pointer to the packed camera.714 const float* camera; // Device pointer to the packed camera.
646 int width, height;715 int width, height;
647 TileGrid grid;716 TileGrid grid;
648 GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); }717 GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); }
649};718};
650719
651// ---------------------------------------------------------------------------720// ---------------------------------------------------------------------------
652// Per-Gaussian kernels run one thread per Gaussian.721// Per-Gaussian kernels run one thread per Gaussian.
653722
654inline constexpr int kGaussianThreads = 128;723inline constexpr int kGaussianThreads = 128;
655724
656inline int GaussianBlocks(int64_t n) {725inline int GaussianBlocks(int64_t n) {
657 return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads);726 return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads);
658}727}
659728
660// Base-2 exponent scale: power2 = kPower2Scale * q = (-log2(e)/2) * q.729// Base-2 exponent scale: power2 = kPower2Scale * q = (-log2(e)/2) * q.
661inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2730inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2
662inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;731inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;
663732
664GS_HD float3 Power2Conic(float a, float b, float c) {733GS_HD float3 Power2Conic(float a, float b, float c) {
665 return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};734 return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};
666}735}
667736
668GS_D float Exp2Approx(float x) {737GS_D float Exp2Approx(float x) {
669 float y;738 float y;
670 asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));739 asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
671 return y;740 return y;
672}741}
673742
674// ---------------------------------------------------------------------------743// ---------------------------------------------------------------------------
675// Rasterization. One block per 16x16 tile, one pixel per thread, arranged into744// Rasterization. One block per 16x16 tile, one pixel per thread, arranged into
676// eight 8x4 sub-tile warps (`RasterLayout<1>`). Splats are staged through745// eight 8x4 sub-tile warps (`RasterLayout<1>`). Splats are staged through
677// shared memory in batches of 256 with an 8-bit per-warp sub-tile mask.746// shared memory in batches of 256 with an 8-bit per-warp sub-tile mask.
678747
679template <int kRows>748template <int kRows>
680struct RasterLayout {749struct RasterLayout {
681 static_assert(kRows == 1 || kRows == 2 || kRows == 4);750 static_assert(kRows == 1 || kRows == 2 || kRows == 4);
682 static constexpr int kThreads = kTilePixels / kRows;751 static constexpr int kThreads = kTilePixels / kRows;
683 static constexpr int kWarps = kThreads / 32;752 static constexpr int kWarps = kThreads / 32;
684 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;753 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;
685 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;754 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;
686 static constexpr int kWarpsX = kTileSize / kWarpWidth;755 static constexpr int kWarpsX = kTileSize / kWarpWidth;
687 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);756 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);
688757
689 GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; }758 GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; }
690 GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; }759 GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; }
691};760};
692761
693struct RasterPixels {762struct RasterPixels {
694 int x, y0;763 int x, y0;
695 template <int kRows>764 template <int kRows>
696 GS_D static RasterPixels For(int tid) {765 GS_D static RasterPixels For(int tid) {
697 using L = RasterLayout<kRows>;766 using L = RasterLayout<kRows>;
698 const int warp = tid / 32, lane = tid % 32;767 const int warp = tid / 32, lane = tid % 32;
699 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +768 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +
700 lane % L::kWarpWidth,769 lane % L::kWarpWidth,
701 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +770 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +
702 (lane / L::kWarpWidth) * kRows};771 (lane / L::kWarpWidth) * kRows};
703 }772 }
704};773};
705774
706// Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the775// Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the
707// pixel region of warp w of this block's tile.776// pixel region of warp w of this block's tile.
708template <int kRows>777template <int kRows>
709GS_D uint32_t WarpMask(float4 p0, float4 p1) {778GS_D uint32_t WarpMask(float4 p0, float4 p1) {
710 using L = RasterLayout<kRows>;779 using L = RasterLayout<kRows>;
711 constexpr int kWarpsY = L::kWarps / L::kWarpsX;780 constexpr int kWarpsY = L::kWarps / L::kWarpsX;
712 float limit = p1.z;781 float limit = p1.z;
713 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;782 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;
714 const float a = p0.z, b = p0.w, c = p1.x;783 const float a = p0.z, b = p0.w, c = p1.x;
715 const float det = a * c - b * b;784 const float det = a * c - b * b;
716 if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0;785 if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0;
717 const float l_over_det = limit / det;786 const float l_over_det = limit / det;
718 const float rx = sqrtf(c * l_over_det);787 const float rx = sqrtf(c * l_over_det);
719 const float ry = sqrtf(a * l_over_det);788 const float ry = sqrtf(a * l_over_det);
720 const float2 xy = {p0.x, p0.y};789 const float2 xy = {p0.x, p0.y};
721 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));790 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));
722 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));791 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));
723 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));792 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));
724 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));793 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));
725 if (x0 > x1 || y0 > y1) return 0;794 if (x0 > x1 || y0 > y1) return 0;
726 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;795 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;
727 uint32_t mx = 0u;796 uint32_t mx = 0u;
728#pragma unroll797#pragma unroll
729 for (int wx = 0; wx < L::kWarpsX; ++wx) {798 for (int wx = 0; wx < L::kWarpsX; ++wx) {
730 const int wx0 = tx + wx * L::kWarpWidth;799 const int wx0 = tx + wx * L::kWarpWidth;
731 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);800 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);
732 }801 }
733 if (mx == 0u) return 0;802 if (mx == 0u) return 0;
734 uint32_t my = 0u;803 uint32_t my = 0u;
735#pragma unroll804#pragma unroll
736 for (int wy = 0; wy < kWarpsY; ++wy) {805 for (int wy = 0; wy < kWarpsY; ++wy) {
737 const int wy0 = ty + wy * L::kWarpRows;806 const int wy0 = ty + wy * L::kWarpRows;
738 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);807 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);
739 }808 }
740 if (my == 0u) return 0;809 if (my == 0u) return 0;
741 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {810 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {
742 uint32_t mask = 0u;811 uint32_t mask = 0u;
743#pragma unroll812#pragma unroll
744 for (int wy = 0; wy < kWarpsY; ++wy) {813 for (int wy = 0; wy < kWarpsY; ++wy) {
745 if (!((my >> wy) & 1u)) continue;814 if (!((my >> wy) & 1u)) continue;
746#pragma unroll815#pragma unroll
747 for (int wx = 0; wx < L::kWarpsX; ++wx) {816 for (int wx = 0; wx < L::kWarpsX; ++wx) {
748 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));817 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));
749 }818 }
750 }819 }
751 return mask;820 return mask;
752 }821 }
753 EllipseBounds eb;822 EllipseBounds eb;
754 eb.a = a;823 eb.a = a;
755 eb.b = b;824 eb.b = b;
756 eb.c = c;825 eb.c = c;
757 eb.det = det;826 eb.det = det;
758 eb.inv_a = 1.0f / a;827 eb.inv_a = 1.0f / a;
759 eb.a_limit = a * limit;828 eb.a_limit = a * limit;
760 eb.y_ext = ry;829 eb.y_ext = ry;
761 eb.dy_peak = b * sqrtf(l_over_det / c);830 eb.dy_peak = b * sqrtf(l_over_det / c);
762 uint32_t mask = 0u;831 uint32_t mask = 0u;
763#pragma unroll832#pragma unroll
764 for (int wy = 0; wy < kWarpsY; ++wy) {833 for (int wy = 0; wy < kWarpsY; ++wy) {
765 if (!((my >> wy) & 1u)) continue;834 if (!((my >> wy) & 1u)) continue;
766 const int wy0 = ty + wy * L::kWarpRows;835 const int wy0 = ty + wy * L::kWarpRows;
767 int x0_px, x1_px;836 int x0_px, x1_px;
768 if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) {837 if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) {
769 continue;838 continue;
770 }839 }
771#pragma unroll840#pragma unroll
772 for (int wx = 0; wx < L::kWarpsX; ++wx) {841 for (int wx = 0; wx < L::kWarpsX; ++wx) {
773 const int wx0 = tx + wx * L::kWarpWidth;842 const int wx0 = tx + wx * L::kWarpWidth;
774 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {843 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {
775 mask |= 1u << (wy * L::kWarpsX + wx);844 mask |= 1u << (wy * L::kWarpsX + wx);
776 }845 }
777 }846 }
778 }847 }
779 return mask;848 return mask;
780}849}
781850
782} // namespace gs851} // namespace gs
783852
784#endif853#endif
↕ Expand 297 unchanged lines (lines 1–297) · in ForEachTileRow
11 /*
22 * Copyright (C) 2023, Inria
33 * GRAPHDECO research group, https://team.inria.fr/graphdeco
44 * All rights reserved.
55 *
66 * This software is free for non-commercial, research and evaluation use
77 * under the terms of the LICENSE.md file.
88 *
99 * For inquiries contact george.drettakis@inria.fr
1010 */
1111
1212 #ifndef CUDA_RASTERIZER_AUXILIARY_H_INCLUDED
1313 #define CUDA_RASTERIZER_AUXILIARY_H_INCLUDED
1414
1515 #include <cuda_runtime.h>
1616
1717 #include <cmath>
1818 #include <cstdint>
1919
2020 #include "config.h"
2121 #include "stdio.h"
2222
2323 #define BLOCK_SIZE (BLOCK_X * BLOCK_Y)
2424 #define NUM_WARPS (BLOCK_SIZE / 32)
2525
2626 // Spherical harmonics coefficients
2727 __device__ const float SH_C0 = 0.28209479177387814f;
2828 __device__ const float SH_C1 = 0.4886025119029199f;
2929 __device__ const float SH_C2[] = {1.0925484305920792f, -1.0925484305920792f,
3030 0.31539156525252005f, -1.0925484305920792f,
3131 0.5462742152960396f};
3232 __device__ const float SH_C3[] = {-0.5900435899266435f, 2.890611442640554f,
3333 -0.4570457994644658f, 0.3731763325901154f,
3434 -0.4570457994644658f, 1.445305721320277f,
3535 -0.5900435899266435f};
3636
3737 namespace gs {
3838
3939 #define GS_HD __host__ __device__ __forceinline__
4040 #define GS_D __device__ __forceinline__
4141
4242 GS_HD float3 operator+(float3 a, float3 b) {
4343 return {a.x + b.x, a.y + b.y, a.z + b.z};
4444 }
4545 GS_HD float3 operator-(float3 a, float3 b) {
4646 return {a.x - b.x, a.y - b.y, a.z - b.z};
4747 }
4848 GS_HD float3 operator*(float s, float3 a) {
4949 return {s * a.x, s * a.y, s * a.z};
5050 }
5151 GS_HD float3& operator+=(float3& a, float3 b) {
5252 a.x += b.x;
5353 a.y += b.y;
5454 a.z += b.z;
5555 return a;
5656 }
5757 GS_HD float dot(float3 a, float3 b) {
5858 return a.x * b.x + a.y * b.y + a.z * b.z;
5959 }
6060
6161 // Row-major 3x3 matrix.
6262 struct Mat3 {
6363 float m[3][3];
6464 GS_HD float3 row(int i) const { return {m[i][0], m[i][1], m[i][2]}; }
6565 GS_HD float3 col(int j) const { return {m[0][j], m[1][j], m[2][j]}; }
6666 };
6767
6868 GS_HD float3 Mul(const Mat3& a, float3 v) {
6969 return {dot(a.row(0), v), dot(a.row(1), v), dot(a.row(2), v)};
7070 }
7171 // a^T v.
7272 GS_HD float3 MulT(const Mat3& a, float3 v) {
7373 return {dot(a.col(0), v), dot(a.col(1), v), dot(a.col(2), v)};
7474 }
7575
7676 // Reinterprets bits; used to pack small integers into float records.
7777 GS_HD float AsFloat(uint32_t u) {
7878 union {
7979 uint32_t u;
8080 float f;
8181 } c{u};
8282 return c.f;
8383 }
8484 GS_HD uint32_t AsUint(float f) {
8585 union {
8686 float f;
8787 uint32_t u;
8888 } c{f};
8989 return c.u;
9090 }
9191
9292 // Number of floats in the packed camera buffer built by `camera.py`:
9393 // [R (9, row-major world-to-camera), t (3), fx, fy, cx, cy, position (3), 0].
9494 inline constexpr int kCameraFloats = 20;
9595
9696 // Pinhole camera with OpenCV axes (+x right, +y down, +z forward). Pixel (i, j)
9797 // is sampled at coordinate (j, i), as in the Inria rasterizer.
9898 struct Camera {
9999 Mat3 rotation; // World-to-camera rotation.
100100 float3 translation;
101101 float fx, fy, cx, cy;
102102 float3 position; // Camera center in world space.
103103 int width, height;
104104
105105 GS_HD static Camera Load(const float* p, int width, int height) {
106106 Camera c;
107107 for (int i = 0; i < 3; ++i)
108108 for (int j = 0; j < 3; ++j) c.rotation.m[i][j] = p[3 * i + j];
109109 c.translation = {p[9], p[10], p[11]};
110110 c.fx = p[12];
111111 c.fy = p[13];
112112 c.cx = p[14];
113113 c.cy = p[15];
114114 c.position = {p[16], p[17], p[18]};
115115 c.width = width;
116116 c.height = height;
117117 return c;
118118 }
119119
120120 GS_HD float3 ToView(float3 p) const { return Mul(rotation, p) + translation; }
121121 // Tangent of the half field of view, as the Inria rasterizer defines it.
122122 GS_HD float TanHalfFovX() const { return width / (2.0f * fx); }
123123 GS_HD float TanHalfFovY() const { return height / (2.0f * fy); }
124124 };
125125
126126 // Screen tiling. Tiles are `kTileSize` x `kTileSize` pixels; one thread block
127127 // rasterizes one tile.
128128 inline constexpr int kTileSize = 16;
129129 inline constexpr int kTilePixels = kTileSize * kTileSize;
130130
131131 struct TileGrid {
132132 int tiles_x, tiles_y;
133133 GS_HD static TileGrid For(int width, int height) {
134134 return {(width + kTileSize - 1) / kTileSize,
135135 (height + kTileSize - 1) / kTileSize};
136136 }
137137 GS_HD int num_tiles() const { return tiles_x * tiles_y; }
138138 };
139139
140140 // Smallest alpha that contributes to a pixel; smaller ones are skipped.
141141 inline constexpr float kMinAlpha = 1.0f / 255.0f;
142142 inline constexpr float kMaxAlpha = 0.99f;
143143 // Transmittance below which a pixel stops accumulating.
144144 inline constexpr float kMinTransmittance = 1e-4f;
145145
146146 // One splat as stored in memory: three float4 so the rasterizer issues three
147147 // 16-byte loads. Layout:
148148 // p0 = (x, y, conic.a, conic.b)
149149 // p1 = (conic.c, opacity, limit, radius) radius 0 => culled
150150 // p2 = (r, g, b, min_power)
151151 inline constexpr int kSplatFloats = 12;
152152 struct alignas(16) Splat {
153153 float4 p0, p1, p2;
154154 GS_HD float2 xy() const { return {p0.x, p0.y}; }
155155 GS_HD float3 conic() const { return {p0.z, p0.w, p1.x}; }
156156 GS_HD float opacity() const { return p1.y; }
157157 GS_HD float limit() const { return p1.z; }
158158 GS_HD int radius() const { return static_cast<int>(p1.w); }
159159 GS_HD float3 rgb() const { return {p2.x, p2.y, p2.z}; }
160160 };
161161
162162 // Gradient w.r.t. a splat's rasterized quantities, accumulated by the raster
163163 // backward pass: (dx, dy, da, db, dc, dopacity, dr, dg, db).
164164 inline constexpr int kSplatGradFloats = 9;
165165
166166 // Half-open tile rectangle [x0, x1) x [y0, y1).
167167 struct TileRect {
168168 int x0, y0, x1, y1;
169169 GS_HD int area() const { return (x1 - x0) * (y1 - y0); }
170170 };
171171
172172 // Tile rectangle of a splat of integer bounding `radius`, identical to the
173173 // reference rasterizer.
174174 GS_HD TileRect RectFor(float2 xy, int radius, const TileGrid& grid) {
175175 const float r = static_cast<float>(radius);
176176 auto lo = [](float v, int n) {
177177 return min(n, max(0, static_cast<int>(v / kTileSize)));
178178 };
179179 auto hi = [](float v, int n) {
180180 return min(n, max(0, static_cast<int>((v + kTileSize - 1) / kTileSize)));
181181 };
182182 return {lo(xy.x - r, grid.tiles_x), lo(xy.y - r, grid.tiles_y),
183183 hi(xy.x + r, grid.tiles_x), hi(xy.y + r, grid.tiles_y)};
184184 }
185185
186186 // How splats are assigned to tiles.
187187 enum class Culling : int {
188188 // Every tile of the bounding rectangle (reference behaviour).
189189 kRect = 0,
190190 // Tiles of the rectangle where some pixel can reach alpha >= 1/255. Gives
191191 // the same image: skipped (splat, tile) pairs never contribute.
192192 kEllipse = 1,
193193 };
194194
195195 // alpha = o exp(-q/2) >= 1/255 <=> q <= 2 ln(255 o). Writes the bound on q
196196 // with margins that keep tests conservative under float rounding of the
197197 // rasterizer's power. Returns false if no pixel can reach 1/255.
198198 GS_HD bool AlphaLimit(float opacity, float& limit) {
199199 const float o255 = opacity * 255.0f;
200200 if (!(o255 >= 0.999f)) return false;
201201 limit = 2.0f * logf(fmaxf(o255, 1.0f)) * 1.001f + 0.02f;
202202 return true;
203203 }
204204
205205 // Pad (pixels) on ellipse extents, against rounding.
206206 inline constexpr float kSpanPadPx = 0.01f;
207207
208208 // Precomputed splat-level ellipse constants for fast band intersection tests
209209 // of {q(d) <= limit}, where q(d) = a dx^2 + 2 b dx dy + c dy^2.
210210 struct EllipseBounds {
211211 float a, b, c;
212212 float det;
213213 float inv_a;
214214 float a_limit;
215215 float y_ext;
216216 float dy_peak;
217217
218218 GS_D static bool Init(float3 conic, float opacity, float cached_limit,
219219 EllipseBounds& out) {
220220 float limit = cached_limit;
221221 if (limit <= 0.0f && !AlphaLimit(opacity, limit)) return false;
222222 out.a = conic.x;
223223 out.b = conic.y;
224224 out.c = conic.z;
225225 out.det = out.a * out.c - out.b * out.b;
226226 if (!(out.det > 0.0f && out.a > 0.0f && out.c > 0.0f)) return false;
227227 out.inv_a = 1.0f / out.a;
228228 out.a_limit = out.a * limit;
229229 const float limit_over_det = limit / out.det;
230230 out.y_ext = sqrtf(out.a * limit_over_det);
231231 out.dy_peak = out.b * sqrtf(limit_over_det / out.c);
232232 return true;
233233 }
234234
235235 // Span [x0_px, x1_px] of integer pixel columns hit across integer pixel rows
236236 // Y in [y0_px, y1_px] (inclusive). Returns false if no integer pixel hits.
237237 GS_D bool SpanForPixelRows(float2 xy, int y0_px, int y1_px, int& x0_px,
238238 int& x1_px) const {
239239 const int ylo_i =
240240 max(y0_px, static_cast<int>(ceilf(xy.y - y_ext - kSpanPadPx)));
241241 const int yhi_i =
242242 min(y1_px, static_cast<int>(floorf(xy.y + y_ext + kSpanPadPx)));
243243 if (ylo_i > yhi_i) return false;
244244 const float ylo = static_cast<float>(ylo_i) - xy.y;
245245 const float yhi = static_cast<float>(yhi_i) - xy.y;
246246 // Boundary x(dy) = (-b dy +- sqrt(a limit - det dy^2)) / a; the right
247247 // (left) branch is concave (convex) and peaks at dy = -+dy_peak.
248248 const float dyr = fminf(yhi, fmaxf(ylo, -dy_peak));
249249 const float dyl = fminf(yhi, fmaxf(ylo, dy_peak));
250250 const float dyr2 = dyr * dyr, dyl2 = dyl * dyl;
251251 const float rr = sqrtf(fmaxf(0.0f, a_limit - det * dyr2));
252252 const float rl =
253253 (dyr2 == dyl2) ? rr : sqrtf(fmaxf(0.0f, a_limit - det * dyl2));
254254 const float hi = (-b * dyr + rr) * inv_a;
255255 const float lo = (-b * dyl - rl) * inv_a;
256256 x0_px = static_cast<int>(ceilf(xy.x + lo - kSpanPadPx));
257257 x1_px = static_cast<int>(floorf(xy.x + hi + kSpanPadPx));
258258 return x0_px <= x1_px;
259259 }
260260 };
261261
262262 // Visits every tile row `ty` with the half-open span [tx0, tx1) of tiles that
263263 // splat `s` (not culled) is binned to. Rows may be empty (tx0 >= tx1).
264264 template <Culling kCulling, typename Fn>
265265 GS_D void ForEachTileRow(const Splat& s, const TileGrid& grid, int width,
266266 int height, Fn&& fn) {
267267 const float2 xy = s.xy();
268268 const TileRect rect = RectFor(xy, s.radius(), grid);
269269 if constexpr (kCulling == Culling::kRect) {
270270 for (int ty = rect.y0; ty < rect.y1; ++ty) fn(ty, rect.x0, rect.x1);
271271 } else {
272272 EllipseBounds eb;
273273 if (!EllipseBounds::Init(s.conic(), s.opacity(), s.limit(), eb)) return;
274274 const int min_y =
275275 max(0, static_cast<int>(ceilf(xy.y - eb.y_ext - kSpanPadPx)));
276276 const int max_y =
277277 min(height - 1, static_cast<int>(floorf(xy.y + eb.y_ext + kSpanPadPx)));
278278 if (min_y > max_y) return;
279279 const int ty0 = max(rect.y0, min_y / kTileSize);
280280 const int ty1 = min(rect.y1, (max_y / kTileSize) + 1);
281281 for (int ty = ty0; ty < ty1; ++ty) {
282282 int x0_px, x1_px;
283283 if (!eb.SpanForPixelRows(xy, ty * kTileSize,
284284 min((ty + 1) * kTileSize, height) - 1, x0_px,
285285 x1_px)) {
286286 continue;
287287 }
288288 x0_px = max(0, x0_px);
289289 x1_px = min(width - 1, x1_px);
290290 if (x0_px > x1_px) continue;
291291 const int tx0 = max(rect.x0, x0_px / kTileSize);
292292 const int tx1 = min(rect.x1, (x1_px / kTileSize) + 1);
293293 if (tx0 < tx1) fn(ty, tx0, tx1);
294294 }
295295 }
296296 }
297297
298298 namespace warp {
299299
300300 inline constexpr unsigned kFullMask = 0xffffffffu;
301301
302−// Standard warp butterfly reduction of an array of `N` per-lane values:
302+// Reduce-scatter of an array of `N` <= 32 per-lane values over the warp.
303−// 5 steps of `__shfl_xor_sync` for each element (`5 * N` shuffles total),
304−// after which lane 0 (and every other lane) holds the warp sum of `v[0..N)`.
303+//
304+// A butterfly where each step halves the payload: at offset `o`, lanes with
305+// bit `o` clear keep the low half of the array and send the high half, the
306+// others do the opposite. After log2(32) steps every lane holds a single
307+// value, v[0], which is the warp total of element `ScatterIndex(N, lane)`
308+// (or junk if that is -1). For N = 9 this takes 12 shuffles instead of the
309+// 45 of nine independent butterflies, and the totals end in distinct lanes,
310+// so writing them out is one instruction.
311+
312+namespace internal {
305−template <int N>
313+template <int N, int kOffset>
306−GS_D void ButterflyReduce(float (&v)[N]) {
314+GS_D void HalvingStep(float* v, int lane) {
315+ constexpr int kHalf = (N + 1) / 2;
316+ const bool upper = lane & kOffset;
307317 #pragma unroll
308− for (int offset = 16; offset >= 1; offset >>= 1) {
309−#pragma unroll
310− for (int i = 0; i < N; ++i) {
318+ for (int i = 0; i < kHalf; ++i) {
319+ const float lo = v[i];
320+ const float hi = i + kHalf < N ? v[i + kHalf] : 0.0f;
321+ const float send = upper ? lo : hi;
322+ const float keep = upper ? hi : lo;
311− v[i] += __shfl_xor_sync(kFullMask, v[i], offset);
323+ v[i] = keep + __shfl_xor_sync(kFullMask, send, kOffset);
312324⇥ }
313325⇥}
326+
327+template <int N, int kOffset>
328+GS_D void ReduceScatterImpl(float* v, int lane) {
329+ if constexpr (kOffset > 0) {
330+ HalvingStep<N, kOffset>(v, lane);
331+ ReduceScatterImpl<(N + 1) / 2, kOffset / 2>(v, lane);
332+ }
333+}
334+} // namespace internal
335+
336+template <int N>
337+GS_D void ReduceScatter(float (&v)[N], int lane) {
338+ static_assert(N >= 1 && N <= 32);
339+ internal::ReduceScatterImpl<N, 16>(v, lane);
340+}
341+
342+template <int N>
343+GS_D void ReduceScatter16(float (&v)[N], int lane) {
344+ static_assert(N >= 1 && N <= 16);
345+ internal::ReduceScatterImpl<N, 8>(v, lane);
346+}
347+
348+// Element whose warp total lane `lane` holds after `ReduceScatter<N>`, or -1.
349+GS_HD constexpr int ScatterIndex(int n, int lane) {
350+ // Payload sizes and halves per step, offset 16 first.
351+ int sizes[5] = {};
352+ int size = n;
353+ for (int s = 0; s < 5; ++s) {
354+ sizes[s] = size;
355+ size = (size + 1) / 2;
356+ }
357+ int index = 0; // Slot in the final (size 1) array.
358+ for (int s = 4; s >= 0; --s) {
359+ const int half = (sizes[s] + 1) / 2;
360+ if (lane & (16 >> s)) index += half;
361+ if (index >= sizes[s]) return -1;
362+ }
363+ return index;
364+}
365+
366+// Element whose 16-lane half-warp total lane `lane` holds after
367+// `ReduceScatter16<N>`, or -1.
368+GS_HD constexpr int ScatterIndex16(int n, int lane) {
369+ const int half_lane = lane & 15;
370+ int sizes[4] = {};
371+ int size = n;
372+ for (int s = 0; s < 4; ++s) {
373+ sizes[s] = size;
374+ size = (size + 1) / 2;
375+ }
376+ int index = 0;
377+ for (int s = 3; s >= 0; --s) {
378+ const int half = (sizes[s] + 1) / 2;
379+ if (half_lane & (8 >> s)) index += half;
380+ if (index >= sizes[s]) return -1;
381+ }
382+ return index;
314383 }
315384
316385 } // namespace warp
317386
↕ Expand 467 unchanged lines (lines 387–853) · in WarpMask
318387 inline constexpr int kShCoeffs = 16; // Degree 3.
319388 inline constexpr float kNearPlane = 0.2f;
320389 inline constexpr float kLowPass = 0.3f;
321390 inline constexpr float kFovClamp = 1.3f;
322391
323392 // Pointers to the trainable parameters, all activated, row-major.
324393 struct GaussianParams {
325394 const float* means; // (N, 3)
326395 const float* scales; // (N, 3)
327396 const float* quats; // (N, 4) as (w, x, y, z), not normalized.
328397 const float* opacities; // (N,)
329398 const void* sh; // (N, 16, 3) of `float` or `ShBf16` (see `ShIo`).
330399 int n;
331400 };
332401
333402 // Gradients w.r.t. `GaussianParams`, same layout and SH storage type.
334403 struct GaussianGrads {
335404 float* means;
336405 float* scales;
337406 float* quats;
338407 float* opacities;
339408 void* sh;
340409 };
341410
342411 // Screen-space quantities needed by rasterization. `conic` is the inverse 2D
343412 // covariance (a, b, c) with power = -0.5 (a dx^2 + c dy^2) - b dx dy.
344413 struct Projection {
345414 float2 xy;
346415 float depth;
347416 float3 conic;
348417 float opacity;
349418 float limit;
350419 float3 rgb;
351420 int radius; // 0 means culled.
352421 };
353422
354423 // ----------------------------------------------------------------------------
355424 // SH coefficient storage. A row holds the 16 x 3 coefficients of one Gaussian
356425 // and may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32.
357426
358427 using ShBf16 = uint16_t; // bfloat16 bits.
359428
360429 template <typename T>
361430 struct ShIo;
362431
363432 template <>
364433 struct ShIo<float> {
365434 GS_D static void LoadRow(const float* row, float3 coeffs[kShCoeffs]) {
366435 const float4* p = reinterpret_cast<const float4*>(row);
367436 float raw[3 * kShCoeffs];
368437 #pragma unroll
369438 for (int k = 0; k < 12; ++k) {
370439 const float4 q = __ldg(p + k);
371440 raw[4 * k] = q.x;
372441 raw[4 * k + 1] = q.y;
373442 raw[4 * k + 2] = q.z;
374443 raw[4 * k + 3] = q.w;
375444 }
376445 #pragma unroll
377446 for (int c = 0; c < kShCoeffs; ++c) {
378447 coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]};
379448 }
380449 }
381450 GS_D static void StoreRow(float* row, const float3 dsh[kShCoeffs]) {
382451 float raw[3 * kShCoeffs];
383452 #pragma unroll
384453 for (int c = 0; c < kShCoeffs; ++c) {
385454 raw[3 * c] = dsh[c].x;
386455 raw[3 * c + 1] = dsh[c].y;
387456 raw[3 * c + 2] = dsh[c].z;
388457 }
389458 float4* p = reinterpret_cast<float4*>(row);
390459 #pragma unroll
391460 for (int k = 0; k < 12; ++k) {
392461 p[k] = {raw[4 * k], raw[4 * k + 1], raw[4 * k + 2], raw[4 * k + 3]};
393462 }
394463 }
395464 };
396465
397466 template <>
398467 struct ShIo<ShBf16> {
399468 GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); }
400469 GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); }
401470 // Round to nearest even (inputs are finite).
402471 GS_D static uint32_t Round(float f) {
403472 const uint32_t u = __float_as_uint(f);
404473 return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16;
405474 }
406475 GS_D static uint32_t Pack(float lo, float hi) {
407476 return Round(lo) | (Round(hi) << 16);
408477 }
409478 GS_D static void LoadRow(const ShBf16* row, float3 coeffs[kShCoeffs]) {
410479 const uint2* p = reinterpret_cast<const uint2*>(row);
411480 float raw[3 * kShCoeffs];
412481 #pragma unroll
413482 for (int k = 0; k < 12; ++k) {
414483 const uint2 w = __ldg(p + k);
415484 raw[4 * k] = Lo(w.x);
416485 raw[4 * k + 1] = Hi(w.x);
417486 raw[4 * k + 2] = Lo(w.y);
418487 raw[4 * k + 3] = Hi(w.y);
419488 }
420489 #pragma unroll
421490 for (int c = 0; c < kShCoeffs; ++c) {
422491 coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]};
423492 }
424493 }
425494 GS_D static void StoreRow(ShBf16* row, const float3 dsh[kShCoeffs]) {
426495 float raw[3 * kShCoeffs];
427496 #pragma unroll
428497 for (int c = 0; c < kShCoeffs; ++c) {
429498 raw[3 * c] = dsh[c].x;
430499 raw[3 * c + 1] = dsh[c].y;
431500 raw[3 * c + 2] = dsh[c].z;
432501 }
433502 uint2* p = reinterpret_cast<uint2*>(row);
434503 #pragma unroll
435504 for (int k = 0; k < 12; ++k) {
436505 p[k] = {Pack(raw[4 * k], raw[4 * k + 1]),
437506 Pack(raw[4 * k + 2], raw[4 * k + 3])};
438507 }
439508 }
440509 };
441510
442511 template <typename ShT>
443512 GS_D const ShT* ShRow(const void* sh, int i) {
444513 return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;
445514 }
446515 template <typename ShT>
447516 GS_D ShT* ShRow(void* sh, int i) {
448517 return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;
449518 }
450519
451520 // ----------------------------------------------------------------------------
452521 // Spherical harmonics (degree 3, Inria sign convention).
453522
454523 namespace sh {
455524 inline constexpr float kC0 = 0.28209479177387814f;
456525 inline constexpr float kC1 = 0.4886025119029199f;
457526 inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f,
458527 0.31539156525252005f, -1.0925484305920792f,
459528 0.5462742152960396f};
460529 inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f,
461530 -0.4570457994644658f, 0.3731763325901154f,
462531 -0.4570457994644658f, 1.445305721320277f,
463532 -0.5900435899266435f};
464533
465534 // Basis values for a unit direction.
466535 GS_D void Basis(float3 d, float b[kShCoeffs]) {
467536 const float x = d.x, y = d.y, z = d.z;
468537 const float xx = x * x, yy = y * y, zz = z * z;
469538 b[0] = kC0;
470539 b[1] = -kC1 * y;
471540 b[2] = kC1 * z;
472541 b[3] = -kC1 * x;
473542 b[4] = kC2[0] * x * y;
474543 b[5] = kC2[1] * y * z;
475544 b[6] = kC2[2] * (2.0f * zz - xx - yy);
476545 b[7] = kC2[3] * x * z;
477546 b[8] = kC2[4] * (xx - yy);
478547 b[9] = kC3[0] * y * (3.0f * xx - yy);
479548 b[10] = kC3[1] * x * y * z;
480549 b[11] = kC3[2] * y * (4.0f * zz - xx - yy);
481550 b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy);
482551 b[13] = kC3[4] * x * (4.0f * zz - xx - yy);
483552 b[14] = kC3[5] * z * (xx - yy);
484553 b[15] = kC3[6] * x * (xx - 3.0f * yy);
485554 }
486555
487556 // Partial derivatives of every basis function w.r.t. (x, y, z) of the unit
488557 // direction (treated as free coordinates).
489558 GS_D void BasisGrad(float3 d, float3 g[kShCoeffs]) {
490559 const float x = d.x, y = d.y, z = d.z;
491560 const float xx = x * x, yy = y * y, zz = z * z;
492561 g[0] = {0.0f, 0.0f, 0.0f};
493562 g[1] = {0.0f, -kC1, 0.0f};
494563 g[2] = {0.0f, 0.0f, kC1};
495564 g[3] = {-kC1, 0.0f, 0.0f};
496565 g[4] = kC2[0] * float3{y, x, 0.0f};
497566 g[5] = kC2[1] * float3{0.0f, z, y};
498567 g[6] = kC2[2] * float3{-2.0f * x, -2.0f * y, 4.0f * z};
499568 g[7] = kC2[3] * float3{z, 0.0f, x};
500569 g[8] = kC2[4] * float3{2.0f * x, -2.0f * y, 0.0f};
501570 g[9] = kC3[0] * float3{6.0f * x * y, 3.0f * (xx - yy), 0.0f};
502571 g[10] = kC3[1] * float3{y * z, x * z, x * y};
503572 g[11] =
504573 kC3[2] * float3{-2.0f * x * y, 4.0f * zz - xx - 3.0f * yy, 8.0f * y * z};
505574 g[12] = kC3[3] * float3{-6.0f * x * z, -6.0f * y * z,
506575 6.0f * zz - 3.0f * xx - 3.0f * yy};
507576 g[13] =
508577 kC3[4] * float3{4.0f * zz - 3.0f * xx - yy, -2.0f * x * y, 8.0f * x * z};
509578 g[14] = kC3[5] * float3{2.0f * x * z, -2.0f * y * z, xx - yy};
510579 g[15] = kC3[6] * float3{3.0f * (xx - yy), -6.0f * x * y, 0.0f};
511580 }
512581
513582 // Pre-clamp color 0.5 + sum_k b_k sh_k, loading the full 16-basis SH row.
514583 template <typename ShT>
515584 GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) {
516585 float3 coeffs[kShCoeffs];
517586 ShIo<ShT>::LoadRow(sh_row, coeffs);
518587 float3 c = {0.5f, 0.5f, 0.5f};
519588 #pragma unroll
520589 for (int k = 0; k < kShCoeffs; ++k) {
521590 c += b[k] * coeffs[k];
522591 }
523592 return c;
524593 }
525594 } // namespace sh
526595
527596 // ----------------------------------------------------------------------------
528597 // Covariance helpers.
529598
530599 // Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z).
531600 GS_D Mat3 QuatToRotation(float4 q) {
532601 const float w = q.x, x = q.y, y = q.z, z = q.w;
533602 Mat3 r;
534603 r.m[0][0] = 1.0f - 2.0f * (y * y + z * z);
535604 r.m[0][1] = 2.0f * (x * y - w * z);
536605 r.m[0][2] = 2.0f * (x * z + w * y);
537606 r.m[1][0] = 2.0f * (x * y + w * z);
538607 r.m[1][1] = 1.0f - 2.0f * (x * x + z * z);
539608 r.m[1][2] = 2.0f * (y * z - w * x);
540609 r.m[2][0] = 2.0f * (x * z - w * y);
541610 r.m[2][1] = 2.0f * (y * z + w * x);
542611 r.m[2][2] = 1.0f - 2.0f * (x * x + y * y);
543612 return r;
544613 }
545614
546615 // Symmetric 3x3 matrix.
547616 struct Sym3 {
548617 float xx, xy, xz, yy, yz, zz;
549618 GS_D float3 Mul(float3 v) const {
550619 return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z,
551620 xz * v.x + yz * v.y + zz * v.z};
552621 }
553622 };
554623
555624 // Sigma = A A^T with A = R diag(s).
556625 GS_D Sym3 Covariance3D(const Mat3& r, float3 s) {
557626 Mat3 a;
558627 const float sv[3] = {s.x, s.y, s.z};
559628 #pragma unroll
560629 for (int i = 0; i < 3; ++i)
561630 #pragma unroll
562631 for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j];
563632 return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)),
564633 dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)),
565634 dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))};
566635 }
567636
568637 // Intermediate values of the projection shared by forward and VJP.
569638 struct ProjectionState {
570639 float3 t; // View-space mean.
571640 float tx_c, ty_c; // Clamped x, y used by the Jacobian.
572641 bool x_clamped, y_clamped;
573642 float3 m0, m1; // Rows of M = J W (2x3).
574643 Sym3 sigma; // World-space 3D covariance.
575644 float a, b, c; // Dilated 2D covariance.
576645 };
577646
578647 // Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane).
579648 GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale,
580649 float4 quat, ProjectionState& st) {
581650 st.t = t;
582651 const float tz = t.z;
583652 const float limx = kFovClamp * cam.TanHalfFovX();
584653 const float limy = kFovClamp * cam.TanHalfFovY();
585654 const float txtz = t.x / tz, tytz = t.y / tz;
586655 st.x_clamped = txtz < -limx || txtz > limx;
587656 st.y_clamped = tytz < -limy || tytz > limy;
588657 st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;
589658 st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;
590659 const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz);
591660 const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz);
592661 const Mat3& w = cam.rotation;
593662 st.m0 = j00 * w.row(0) + j02 * w.row(2);
594663 st.m1 = j11 * w.row(1) + j12 * w.row(2);
595664 st.sigma = Covariance3D(QuatToRotation(quat), scale);
596665 const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1);
597666 st.a = dot(st.m0, s0) + kLowPass;
598667 st.b = dot(st.m0, s1);
599668 st.c = dot(st.m1, s1) + kLowPass;
600669 }
601670
602671 // Computes everything up to the 2D covariance. Returns false if culled by the
603672 // near plane.
604673 GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale,
605674 float4 quat, ProjectionState& st) {
606675 const float3 t = cam.ToView(mean);
607676 if (t.z <= kNearPlane) return false;
608677 ProjectCovarianceFromView(cam, t, scale, quat, st);
609678 return true;
610679 }
611680
612681 // Integer bounding radius (pixels) of the 2D covariance, as in the reference.
613682 GS_D int BoundingRadius(float a, float b, float c) {
614683 const float det = a * c - b * b;
615684 const float mid = 0.5f * (a + c);
616685 const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det));
617686 return static_cast<int>(ceilf(3.0f * sqrtf(lambda)));
618687 }
619688
620689 GS_D float2 ProjectMean(const Camera& cam, float3 t) {
621690 return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy};
622691 }
623692
624693 GS_D float3 LoadMean(const GaussianParams& p, int i) {
625694 return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]};
626695 }
627696
628697 // Unit direction from the camera center to `mean`, and 1 / distance.
629698 GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) {
630699 const float3 v = mean - cam.position;
631700 inv_len = rsqrtf(dot(v, v));
632701 return inv_len * v;
633702 }
634703
635704 // Gradient of the loss w.r.t. one splat's screen-space quantities.
636705 struct ProjectionGrad {
637706 float2 xy;
638707 float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`.
639708 float opacity;
640709 float3 rgb;
641710 };
642711
643712 // Image geometry passed by value to every kernel.
644713 struct Frame {
645714 const float* camera; // Device pointer to the packed camera.
646715 int width, height;
647716 TileGrid grid;
648717 GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); }
649718 };
650719
651720 // ---------------------------------------------------------------------------
652721 // Per-Gaussian kernels run one thread per Gaussian.
653722
654723 inline constexpr int kGaussianThreads = 128;
655724
656725 inline int GaussianBlocks(int64_t n) {
657726 return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads);
658727 }
659728
660729 // Base-2 exponent scale: power2 = kPower2Scale * q = (-log2(e)/2) * q.
661730 inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2
662731 inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;
663732
664733 GS_HD float3 Power2Conic(float a, float b, float c) {
665734 return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};
666735 }
667736
668737 GS_D float Exp2Approx(float x) {
669738 float y;
670739 asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
671740 return y;
672741 }
673742
674743 // ---------------------------------------------------------------------------
675744 // Rasterization. One block per 16x16 tile, one pixel per thread, arranged into
676745 // eight 8x4 sub-tile warps (`RasterLayout<1>`). Splats are staged through
677746 // shared memory in batches of 256 with an 8-bit per-warp sub-tile mask.
678747
679748 template <int kRows>
680749 struct RasterLayout {
681750 static_assert(kRows == 1 || kRows == 2 || kRows == 4);
682751 static constexpr int kThreads = kTilePixels / kRows;
683752 static constexpr int kWarps = kThreads / 32;
684753 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;
685754 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;
686755 static constexpr int kWarpsX = kTileSize / kWarpWidth;
687756 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);
688757
689758 GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; }
690759 GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; }
691760 };
692761
693762 struct RasterPixels {
694763 int x, y0;
695764 template <int kRows>
696765 GS_D static RasterPixels For(int tid) {
697766 using L = RasterLayout<kRows>;
698767 const int warp = tid / 32, lane = tid % 32;
699768 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +
700769 lane % L::kWarpWidth,
701770 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +
702771 (lane / L::kWarpWidth) * kRows};
703772 }
704773 };
705774
706775 // Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the
707776 // pixel region of warp w of this block's tile.
708777 template <int kRows>
709778 GS_D uint32_t WarpMask(float4 p0, float4 p1) {
710779 using L = RasterLayout<kRows>;
711780 constexpr int kWarpsY = L::kWarps / L::kWarpsX;
712781 float limit = p1.z;
713782 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;
714783 const float a = p0.z, b = p0.w, c = p1.x;
715784 const float det = a * c - b * b;
716785 if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0;
717786 const float l_over_det = limit / det;
718787 const float rx = sqrtf(c * l_over_det);
719788 const float ry = sqrtf(a * l_over_det);
720789 const float2 xy = {p0.x, p0.y};
721790 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));
722791 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));
723792 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));
724793 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));
725794 if (x0 > x1 || y0 > y1) return 0;
726795 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;
727796 uint32_t mx = 0u;
728797 #pragma unroll
729798 for (int wx = 0; wx < L::kWarpsX; ++wx) {
730799 const int wx0 = tx + wx * L::kWarpWidth;
731800 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);
732801 }
733802 if (mx == 0u) return 0;
734803 uint32_t my = 0u;
735804 #pragma unroll
736805 for (int wy = 0; wy < kWarpsY; ++wy) {
737806 const int wy0 = ty + wy * L::kWarpRows;
738807 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);
739808 }
740809 if (my == 0u) return 0;
741810 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {
742811 uint32_t mask = 0u;
743812 #pragma unroll
744813 for (int wy = 0; wy < kWarpsY; ++wy) {
745814 if (!((my >> wy) & 1u)) continue;
746815 #pragma unroll
747816 for (int wx = 0; wx < L::kWarpsX; ++wx) {
748817 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));
749818 }
750819 }
751820 return mask;
752821 }
753822 EllipseBounds eb;
754823 eb.a = a;
755824 eb.b = b;
756825 eb.c = c;
757826 eb.det = det;
758827 eb.inv_a = 1.0f / a;
759828 eb.a_limit = a * limit;
760829 eb.y_ext = ry;
761830 eb.dy_peak = b * sqrtf(l_over_det / c);
762831 uint32_t mask = 0u;
763832 #pragma unroll
764833 for (int wy = 0; wy < kWarpsY; ++wy) {
765834 if (!((my >> wy) & 1u)) continue;
766835 const int wy0 = ty + wy * L::kWarpRows;
767836 int x0_px, x1_px;
768837 if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) {
769838 continue;
770839 }
771840 #pragma unroll
772841 for (int wx = 0; wx < L::kWarpsX; ++wx) {
773842 const int wx0 = tx + wx * L::kWarpWidth;
774843 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {
775844 mask |= 1u << (wy * L::kWarpsX + wx);
776845 }
777846 }
778847 }
779848 return mask;
780849 }
781850
782851 } // namespace gs
783852
784853 #endif
csrc/backward.cu.cc +156 −46
s01_literature/csrc/backward.cu.cc
s02_reduce_scatter/csrc/backward.cu.cc
↕ Expand 167 unchanged lines (lines 1–167) · in ProjectVjp
1/*1/*
2 * Copyright (C) 2023, Inria2 * Copyright (C) 2023, Inria
3 * GRAPHDECO research group, https://team.inria.fr/graphdeco3 * GRAPHDECO research group, https://team.inria.fr/graphdeco
4 * All rights reserved.4 * All rights reserved.
5 *5 *
6 * This software is free for non-commercial, research and evaluation use6 * This software is free for non-commercial, research and evaluation use
7 * under the terms of the LICENSE.md file.7 * under the terms of the LICENSE.md file.
8 *8 *
9 * For inquiries contact george.drettakis@inria.fr9 * For inquiries contact george.drettakis@inria.fr
10 */10 */
1111
12#include "backward.h"12#include "backward.h"
1313
14#include "auxiliary.h"14#include "auxiliary.h"
1515
16namespace gs {16namespace gs {
1717
18namespace sh {18namespace sh {
19// Evaluates SH color VJP loading the full 16-basis row `sh_in` and storing19// Evaluates SH color VJP loading the full 16-basis row `sh_in` and storing
20// `dsh` to `sh_out`.20// `dsh` to `sh_out`.
21template <typename ShT>21template <typename ShT>
22GS_D float3 VjpRow(float3 d, float3 g_rgb, const ShT* __restrict__ sh_in,22GS_D float3 VjpRow(float3 d, float3 g_rgb, const ShT* __restrict__ sh_in,
23 ShT* __restrict__ sh_out) {23 ShT* __restrict__ sh_out) {
24 float b[kShCoeffs];24 float b[kShCoeffs];
25 Basis(d, b);25 Basis(d, b);
26 float3 coeffs[kShCoeffs];26 float3 coeffs[kShCoeffs];
27 ShIo<ShT>::LoadRow(sh_in, coeffs);27 ShIo<ShT>::LoadRow(sh_in, coeffs);
28 float3 raw = {0.5f, 0.5f, 0.5f};28 float3 raw = {0.5f, 0.5f, 0.5f};
29#pragma unroll29#pragma unroll
30 for (int k = 0; k < kShCoeffs; ++k) {30 for (int k = 0; k < kShCoeffs; ++k) {
31 raw += b[k] * coeffs[k];31 raw += b[k] * coeffs[k];
32 }32 }
33 const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x,33 const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x,
34 raw.y < 0.0f ? 0.0f : g_rgb.y,34 raw.y < 0.0f ? 0.0f : g_rgb.y,
35 raw.z < 0.0f ? 0.0f : g_rgb.z};35 raw.z < 0.0f ? 0.0f : g_rgb.z};
36 float3 bgrad[kShCoeffs];36 float3 bgrad[kShCoeffs];
37 BasisGrad(d, bgrad);37 BasisGrad(d, bgrad);
38 float3 dsh[kShCoeffs];38 float3 dsh[kShCoeffs];
39 float3 ddir = {0.0f, 0.0f, 0.0f};39 float3 ddir = {0.0f, 0.0f, 0.0f};
40#pragma unroll40#pragma unroll
41 for (int k = 0; k < kShCoeffs; ++k) {41 for (int k = 0; k < kShCoeffs; ++k) {
42 dsh[k] = b[k] * drgb;42 dsh[k] = b[k] * drgb;
43 const float s = dot(coeffs[k], drgb);43 const float s = dot(coeffs[k], drgb);
44 ddir += s * bgrad[k];44 ddir += s * bgrad[k];
45 }45 }
46 ShIo<ShT>::StoreRow(sh_out, dsh);46 ShIo<ShT>::StoreRow(sh_out, dsh);
47 return ddir;47 return ddir;
48}48}
49} // namespace sh49} // namespace sh
5050
51// VJP of the projection for a touched Gaussian `i`; reads its SH row from51// VJP of the projection for a touched Gaussian `i`; reads its SH row from
52// `sh_in` and writes dL/dsh directly to `sh_out`.52// `sh_in` and writes dL/dsh directly to `sh_out`.
53// Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`.53// Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`.
54template <typename ShT>54template <typename ShT>
55GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i,55GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i,
56 const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out,56 const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out,
57 const ProjectionGrad& g, float3& dmean_out,57 const ProjectionGrad& g, float3& dmean_out,
58 float3& dscale_out, float4& dquat_out) {58 float3& dscale_out, float4& dquat_out) {
59 const float3 mean = LoadMean(p, i);59 const float3 mean = LoadMean(p, i);
60 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],60 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],
61 p.scales[3 * i + 2]};61 p.scales[3 * i + 2]};
62 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];62 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];
6363
64 // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0).64 // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0).
65 float3 dmean = {0.0f, 0.0f, 0.0f};65 float3 dmean = {0.0f, 0.0f, 0.0f};
66 {66 {
67 float inv_len;67 float inv_len;
68 const float3 dir = ViewDir(cam, mean, inv_len);68 const float3 dir = ViewDir(cam, mean, inv_len);
69 const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out);69 const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out);
70 // d(v/|v|)/dv = (I - dir dir^T) / |v|.70 // d(v/|v|)/dv = (I - dir dir^T) / |v|.
71 dmean += inv_len * (ddir - dot(dir, ddir) * dir);71 dmean += inv_len * (ddir - dot(dir, ddir) * dir);
72 }72 }
7373
74 // --- Forward covariance intermediates (reusing R, s0, s1 in VJP).74 // --- Forward covariance intermediates (reusing R, s0, s1 in VJP).
75 const float3 t = cam.ToView(mean);75 const float3 t = cam.ToView(mean);
76 const float tz = t.z;76 const float tz = t.z;
77 const float limx = kFovClamp * cam.TanHalfFovX();77 const float limx = kFovClamp * cam.TanHalfFovX();
78 const float limy = kFovClamp * cam.TanHalfFovY();78 const float limy = kFovClamp * cam.TanHalfFovY();
79 const float txtz = t.x / tz, tytz = t.y / tz;79 const float txtz = t.x / tz, tytz = t.y / tz;
80 const bool x_clamped = txtz < -limx || txtz > limx;80 const bool x_clamped = txtz < -limx || txtz > limx;
81 const bool y_clamped = tytz < -limy || tytz > limy;81 const bool y_clamped = tytz < -limy || tytz > limy;
82 const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;82 const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;
83 const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;83 const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;
84 const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz);84 const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz);
85 const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz);85 const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz);
86 const Mat3& w = cam.rotation;86 const Mat3& w = cam.rotation;
87 const float3 m0 = j00 * w.row(0) + j02 * w.row(2);87 const float3 m0 = j00 * w.row(0) + j02 * w.row(2);
88 const float3 m1 = j11 * w.row(1) + j12 * w.row(2);88 const float3 m1 = j11 * w.row(1) + j12 * w.row(2);
8989
90 const Mat3 r = QuatToRotation(quat);90 const Mat3 r = QuatToRotation(quat);
91 const Sym3 sigma = Covariance3D(r, scale);91 const Sym3 sigma = Covariance3D(r, scale);
92 const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1);92 const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1);
93 const float cov_a = dot(m0, s0) + kLowPass;93 const float cov_a = dot(m0, s0) + kLowPass;
94 const float cov_b = dot(m0, s1);94 const float cov_b = dot(m0, s1);
95 const float cov_c = dot(m1, s1) + kLowPass;95 const float cov_c = dot(m1, s1) + kLowPass;
9696
97 // --- Conic -> 2D covariance: G_cov = -K G_K K.97 // --- Conic -> 2D covariance: G_cov = -K G_K K.
98 const float det = cov_a * cov_c - cov_b * cov_b;98 const float det = cov_a * cov_c - cov_b * cov_b;
99 const float inv = 1.0f / det;99 const float inv = 1.0f / det;
100 const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv;100 const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv;
101 const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z;101 const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z;
102 // P = G_K K.102 // P = G_K K.
103 const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc;103 const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc;
104 const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc;104 const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc;
105 // G_cov = -K P (symmetric).105 // G_cov = -K P (symmetric).
106 const float ca = -(ka * p00 + kb * p10);106 const float ca = -(ka * p00 + kb * p10);
107 const float cb = -(ka * p01 + kb * p11);107 const float cb = -(ka * p01 + kb * p11);
108 const float cc = -(kb * p01 + kc * p11);108 const float cc = -(kb * p01 + kc * p11);
109109
110 // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M.110 // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M.
111 const float3 dm0 = 2.0f * (ca * s0 + cb * s1);111 const float3 dm0 = 2.0f * (ca * s0 + cb * s1);
112 const float3 dm1 = 2.0f * (cb * s0 + cc * s1);112 const float3 dm1 = 2.0f * (cb * s0 + cc * s1);
113 // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T.113 // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T.
114 Sym3 dsig;114 Sym3 dsig;
115 dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x;115 dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x;
116 dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y;116 dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y;
117 dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z;117 dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z;
118 dsig.xy =118 dsig.xy =
119 ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y;119 ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y;
120 dsig.xz =120 dsig.xz =
121 ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z;121 ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z;
122 dsig.yz =122 dsig.yz =
123 ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z;123 ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z;
124124
125 // --- M = J W: dJ = dM W^T (only the nonzero entries of J).125 // --- M = J W: dJ = dM W^T (only the nonzero entries of J).
126 const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2));126 const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2));
127 const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2));127 const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2));
128 const float tz2 = tz * tz, tz3 = tz2 * tz;128 const float tz2 = tz * tz, tz3 = tz2 * tz;
129 float3 dt;129 float3 dt;
130 dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02;130 dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02;
131 dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12;131 dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12;
132 dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 +132 dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 +
133 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12;133 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12;
134 // --- Screen-space mean u = fx x / z + cx.134 // --- Screen-space mean u = fx x / z + cx.
135 dt.x += cam.fx / tz * g.xy.x;135 dt.x += cam.fx / tz * g.xy.x;
136 dt.y += cam.fy / tz * g.xy.y;136 dt.y += cam.fy / tz * g.xy.y;
137 dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2;137 dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2;
138 dmean += MulT(w, dt);138 dmean += MulT(w, dt);
139 dmean_out = dmean;139 dmean_out = dmean;
140140
141 // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A.141 // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A.
142 const float sv[3] = {scale.x, scale.y, scale.z};142 const float sv[3] = {scale.x, scale.y, scale.z};
143 float ds[3];143 float ds[3];
144 Mat3 dr;144 Mat3 dr;
145#pragma unroll145#pragma unroll
146 for (int j = 0; j < 3; ++j) {146 for (int j = 0; j < 3; ++j) {
147 const float3 r_col = r.col(j);147 const float3 r_col = r.col(j);
148 const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col);148 const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col);
149 ds[j] = dot(da_col, r_col);149 ds[j] = dot(da_col, r_col);
150 dr.m[0][j] = da_col.x * sv[j];150 dr.m[0][j] = da_col.x * sv[j];
151 dr.m[1][j] = da_col.y * sv[j];151 dr.m[1][j] = da_col.y * sv[j];
152 dr.m[2][j] = da_col.z * sv[j];152 dr.m[2][j] = da_col.z * sv[j];
153 }153 }
154 dscale_out = {ds[0], ds[1], ds[2]};154 dscale_out = {ds[0], ds[1], ds[2]};
155155
156 // --- R(q) -> q.156 // --- R(q) -> q.
157 const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w;157 const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w;
158 const auto& G = dr.m;158 const auto& G = dr.m;
159 dquat_out = {159 dquat_out = {
160 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] -160 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] -
161 qy * G[2][0] + qx * G[2][1]),161 qy * G[2][0] + qx * G[2][1]),
162 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] -162 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] -
163 qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]),163 qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]),
164 2.0f *164 2.0f *
165 (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] +165 (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] +
166 qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]),166 qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]),
167 2.0f *167 2.0f *
168 (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] -168 (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] -
169 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])};169 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])};
170}170}
171171
172// Reverse traversal per pixel; accumulates splat gradients with standard172// Reverse traversal per pixel; accumulates splat gradients via Half-Warp
173// 5-step full-warp butterfly reductions (45 shuffles) and 9 lane-0 atomics.
173// Two-Splat Folding and Halving Register Reduce-Scatter.
174__global__ void __launch_bounds__(RasterLayout<1>::kThreads, 6)174__global__ void __launch_bounds__(RasterLayout<1>::kThreads, 4)
175 RasterBackwardKernel(const int2* __restrict__ ranges,175 RasterBackwardKernel(const int2* __restrict__ ranges,
176 const int32_t* __restrict__ point_list,176 const int32_t* __restrict__ point_list,
177 const Splat* __restrict__ splats, Frame frame,177 const Splat* __restrict__ splats, Frame frame,
178 const float* __restrict__ final_t,178 const float* __restrict__ final_t,
↕ Expand 44 unchanged lines (lines 179–222) · in RasterBackwardKernel
179 const int32_t* __restrict__ n_contrib,179 const int32_t* __restrict__ n_contrib,
180 const float* __restrict__ dl_dimage,180 const float* __restrict__ dl_dimage,
181 float* __restrict__ splat_grads) {181 float* __restrict__ splat_grads) {
182 using L = RasterLayout<1>;182 using L = RasterLayout<1>;
183 constexpr int kRasterThreads = L::kThreads;183 constexpr int kRasterThreads = L::kThreads;
184 constexpr int kWarps = L::kWarps;184 constexpr int kWarps = L::kWarps;
185 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;185 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
186 const int tid = threadIdx.x;186 const int tid = threadIdx.x;
187 const int warp = tid / 32;187 const int warp = tid / 32;
188 const int lane = tid % 32;188 const int lane = tid % 32;
189 const RasterPixels px = RasterPixels::For<1>(tid);189 const RasterPixels px = RasterPixels::For<1>(tid);
190 const float fx = static_cast<float>(px.x);190 const float fx = static_cast<float>(px.x);
191 const float fy = static_cast<float>(px.y0);191 const float fy = static_cast<float>(px.y0);
192 const int2 range = ranges[tile];192 const int2 range = ranges[tile];
193193
194 const bool inside = px.x < frame.width && px.y0 < frame.height;194 const bool inside = px.x < frame.width && px.y0 < frame.height;
195 const int pixels = frame.width * frame.height;195 const int pixels = frame.width * frame.height;
196 const int p = px.y0 * frame.width + px.x;196 const int p = px.y0 * frame.width + px.x;
197 float t = inside ? final_t[p] : 0.0f;197 float t = inside ? final_t[p] : 0.0f;
198 const float3 dpix =198 const float3 dpix =
199 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],199 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],
200 dl_dimage[2 * pixels + p]}200 dl_dimage[2 * pixels + p]}
201 : float3{0.0f, 0.0f, 0.0f};201 : float3{0.0f, 0.0f, 0.0f};
202 float accum = 0.0f; // dot(color accumulated behind, dpix)202 float accum = 0.0f; // dot(color accumulated behind, dpix)
203 const int last = inside ? n_contrib[p] : 0;203 const int last = inside ? n_contrib[p] : 0;
204204
205 int warp_max_last = last;205 int warp_max_last = last;
206#pragma unroll206#pragma unroll
207 for (int offset = 16; offset > 0; offset /= 2) {207 for (int offset = 16; offset > 0; offset /= 2) {
208 warp_max_last = max(208 warp_max_last = max(
209 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));209 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));
210 }210 }
211 __shared__ int shared_max_last[kWarps];211 __shared__ int shared_max_last[kWarps];
212 if (lane == 0) shared_max_last[warp] = warp_max_last;212 if (lane == 0) shared_max_last[warp] = warp_max_last;
213 __syncthreads();213 __syncthreads();
214 int block_max_last = 0;214 int block_max_last = 0;
215#pragma unroll215#pragma unroll
216 for (int w = 0; w < kWarps; ++w) {216 for (int w = 0; w < kWarps; ++w) {
217 block_max_last = max(block_max_last, shared_max_last[w]);217 block_max_last = max(block_max_last, shared_max_last[w]);
218 }218 }
219 if (block_max_last <= 0) return;219 if (block_max_last <= 0) return;
220220
221 __shared__ float4 batch_geom0[kRasterThreads];221 __shared__ float4 batch_geom0[kRasterThreads];
222 __shared__ float2 batch_geom1[kRasterThreads];222 __shared__ float2 batch_geom1[kRasterThreads];
223 __shared__ float4 batch_rgb_o[kRasterThreads];223 __shared__ float4 batch_rgb_o[kRasterThreads];
224 __shared__ uint32_t batch_id[kRasterThreads];224 __shared__ uint32_t batch_id[kRasterThreads];
225 __shared__ uint8_t batch_mask[kRasterThreads];225 __shared__ uint8_t batch_mask[kRasterThreads];
226 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);226 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
227 int scatter = -1;
228 int scatter2 = -1;
229 {
230 const int sub_lane = lane & 15;
231 const int row = sub_lane >> 3, col = sub_lane & 7;
232 if (col == 0) {
233 scatter2 = row;
234 } else if (col == 2) {
235 scatter2 = 2 + row;
236 } else if (col == 4) {
237 scatter2 = row == 0 ? 4 : 7;
238 } else if (col == 6) {
239 scatter2 = row == 0 ? 6 : 8;
240 } else if (col == 1 && row == 0) {
241 scatter2 = 5;
242 }
243 const int full_row = lane >> 3, full_col = lane & 7;
244 if (full_col == 0) {
245 scatter = full_row;
246 } else if (full_col == 2) {
247 scatter = full_row == 0 ? 4 : 5 + full_row;
248 } else if (full_col == 4 && full_row == 0) {
249 scatter = 5;
250 }
251 }
227252
253 auto eval_splat = [&](float4 g0, float2 g1, float4 ro, int entry, float& dx,
254 float& ga_sum, float& ga_dy, float& ga_dy2,
255 float3& drgb) -> bool {
256 dx = g0.x - fx;
257 ga_sum = 0.0f;
258 ga_dy = 0.0f;
259 ga_dy2 = 0.0f;
260 drgb = {0.0f, 0.0f, 0.0f};
261 if (entry < last) {
262 const float dy = g0.y - fy;
263 const float power = g0.z * dx * dx + g1.x * dy * dy + g0.w * dx * dy;
264 if (power <= 0.0f && power >= g1.y) {
265 const float g_val = Exp2Approx(power);
266 const float alpha = fminf(kMaxAlpha, ro.w * g_val);
267 if (alpha >= kMinAlpha) {
268 t = __fdividef(t, 1.0f - alpha);
269 const float w = alpha * t;
270 const float color_dot = ro.x * dpix.x + ro.y * dpix.y + ro.z * dpix.z;
271 const float diff = color_dot - accum;
272 const float dl_dalpha = t * diff;
273 accum += alpha * diff;
274 const float ga = g_val * dl_dalpha;
275 ga_sum = ga;
276 const float ga_y = ga * dy;
277 ga_dy = ga_y;
278 ga_dy2 = ga_y * dy;
279 drgb = w * dpix;
280 return true;
281 }
282 }
283 }
284 return false;
285 };
228 auto raw_conic = [](float4 g0, float2 g1) {286 auto raw_conic = [](float4 g0, float2 g1) {
229 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,287 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,
230 kInvPower2Scale * g1.x};288 kInvPower2Scale * g1.x};
231 };289 };
↕ Expand 21 unchanged lines (lines 290–310) · in RasterBackwardKernel
232290
233 for (int end = block_max_last; end > 0; end -= kRasterThreads) {291 for (int end = block_max_last; end > 0; end -= kRasterThreads) {
234 const int e = end - 1 - tid;292 const int e = end - 1 - tid;
235 if (e >= 0) {293 if (e >= 0) {
236 const uint32_t id = static_cast<uint32_t>(point_list[range.x + e]);294 const uint32_t id = static_cast<uint32_t>(point_list[range.x + e]);
237 const float4 p0 = __ldg(&splats[id].p0);295 const float4 p0 = __ldg(&splats[id].p0);
238 const float4 p1 = __ldg(&splats[id].p1);296 const float4 p1 = __ldg(&splats[id].p1);
239 const float4 p2 = __ldg(&splats[id].p2);297 const float4 p2 = __ldg(&splats[id].p2);
240 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);298 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
241 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};299 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
242 batch_geom1[tid] = {c2.z, p2.w};300 batch_geom1[tid] = {c2.z, p2.w};
243 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};301 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};
244 batch_id[tid] = id;302 batch_id[tid] = id;
245 batch_mask[tid] = static_cast<uint8_t>(WarpMask<1>(p0, p1));303 batch_mask[tid] = static_cast<uint8_t>(WarpMask<1>(p0, p1));
246 } else {304 } else {
247 batch_mask[tid] = 0u;305 batch_mask[tid] = 0u;
248 }306 }
249 __syncthreads();307 __syncthreads();
250 const int count = min(kRasterThreads, end);308 const int count = min(kRasterThreads, end);
251#pragma unroll309#pragma unroll
252 for (int c = 0; c < kRasterThreads; c += 32) {310 for (int c = 0; c < kRasterThreads; c += 32) {
253 if (c < count && end - 32 - c < warp_max_last) {311 if (c < count && end - 32 - c < warp_max_last) {
254 uint32_t mask = __ballot_sync(312 uint32_t mask = __ballot_sync(
255 warp::kFullMask,313 warp::kFullMask,
256 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);314 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
315 while ((mask & (mask - 1u)) != 0u) {
316 const int bit0 = __ffs(mask) - 1;
317 mask &= mask - 1u;
318 const int bit1 = __ffs(mask) - 1;
319 mask &= mask - 1u;
320 const int j0 = c + bit0, j1 = c + bit1;
321 const float4 g0_0 = batch_geom0[j0];
322 const float2 g1_0 = batch_geom1[j0];
323 const float4 ro_0 = batch_rgb_o[j0];
324 const float4 g0_1 = batch_geom0[j1];
325 const float2 g1_1 = batch_geom1[j1];
326 const float4 ro_1 = batch_rgb_o[j1];
327
328 float dx0, ga_sum0, ga_dy0, ga_dy2_0;
329 float3 drgb0;
330 const bool hit0 = eval_splat(g0_0, g1_0, ro_0, end - 1 - j0, dx0,
331 ga_sum0, ga_dy0, ga_dy2_0, drgb0);
332
333 float dx1, ga_sum1, ga_dy1, ga_dy2_1;
334 float3 drgb1;
335 const bool hit1 = eval_splat(g0_1, g1_1, ro_1, end - 1 - j1, dx1,
336 ga_sum1, ga_dy1, ga_dy2_1, drgb1);
337 // As in s01: a pair that no pixel of the warp blends has zero
338 // gradients, so skip its reduction and atomics entirely.
339 if (!__any_sync(warp::kFullMask, hit0 || hit1)) continue;
340
341 const bool hi16 = (lane & 16) != 0;
342 const float opacity = hi16 ? ro_1.w : ro_0.w;
343 const float dx = hi16 ? dx1 : dx0;
344 const float3 cn =
345 hi16 ? raw_conic(g0_1, g1_1) : raw_conic(g0_0, g1_0);
346
347 auto fold16 = [&](float v0, float v1) {
348 const float send = hi16 ? v0 : v1;
349 const float recv = __shfl_xor_sync(warp::kFullMask, send, 16);
350 return (hi16 ? v1 : v0) + recv;
351 };
352 float ga_sum = fold16(ga_sum0, ga_sum1);
353 float ga_dy = fold16(ga_dy0, ga_dy1);
354 float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1),
355 fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y),
356 fold16(drgb0.z, drgb1.z)};
357 warp::internal::HalvingStep<4, 8>(b4, lane);
358 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
359 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
360
361 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
362 const float p_dx = dx * p_sum;
363 const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
364 : -(cn.y * p_dx + cn.z * p_dy);
365 const float v_dx1 = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
366 float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]};
367 warp::internal::HalvingStep<4, 4>(v4, lane);
368 warp::internal::HalvingStep<2, 2>(v4, lane);
369 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4);
370 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2);
371 const float v_sum =
372 v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1);
373 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1);
374 const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum;
375 if (scatter2 >= 0 && out_grad != 0.0f) {
376 const uint32_t target_id = batch_id[hi16 ? j1 : j0];
377 atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2,
378 out_grad);
379 }
380 }
257 while (mask != 0u) {381 while (mask != 0u) {
258 const int bit = __ffs(mask) - 1;382 const int bit = __ffs(mask) - 1;
259 mask &= mask - 1u;383 mask &= mask - 1u;
260 const int j = c + bit;384 const int j = c + bit;
261 const int entry = end - 1 - j;
262 float gr[kSplatGradFloats] = {};
263 bool hit = false;
264 if (entry < last) {
265 const float4 g0 = batch_geom0[j];385 const float4 g0 = batch_geom0[j];
266 const float2 g1 = batch_geom1[j];386 const float2 g1 = batch_geom1[j];
267 const float dx = g0.x - fx;
268 const float dy = g0.y - fy;
269 const float power =
270 g0.z * dx * dx + g1.x * dy * dy + g0.w * dx * dy;
271 if (power <= 0.0f && power >= g1.y) {
272 const float4 ro = batch_rgb_o[j];387 const float4 ro = batch_rgb_o[j];
273 const float g_val = Exp2Approx(power);
274 const float alpha = fminf(kMaxAlpha, ro.w * g_val);
275 if (alpha >= kMinAlpha) {
276 hit = true;
277 t = __fdividef(t, 1.0f - alpha);
278 const float w = alpha * t;
279 const float color_dot =
280 ro.x * dpix.x + ro.y * dpix.y + ro.z * dpix.z;
281 const float diff = color_dot - accum;
282 const float dl_dalpha = t * diff;
283 accum += alpha * diff;
284 const float ga = g_val * dl_dalpha;388 float dx, ga_sum, ga_dy, ga_dy2;
285 const float dl_dpower = ro.w * ga;389 float3 drgb;
286 const float p_dx = dx * dl_dpower;
390 const bool hit =
391 eval_splat(g0, g1, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2, drgb);
392 if (!__any_sync(warp::kFullMask, hit)) continue;
287 const float p_dy = dy * dl_dpower;393 const float opacity = ro.w;
288 const float3 cn = raw_conic(g0, g1);394 const float3 cn = raw_conic(g0, g1);
395 float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z};
396 warp::internal::HalvingStep<4, 16>(b4, lane);
397 warp::internal::HalvingStep<2, 8>(b4, lane);
398 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16);
399 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16);
400 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
401 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
402 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
403 const float p_dx = dx * p_sum;
404 float v_dx;
405 if ((lane & 16) == 0) {
289 gr[0] = -(cn.x * p_dx + cn.y * p_dy);406 v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
290 gr[1] = -(cn.y * p_dx + cn.z * p_dy);407 : -(cn.y * p_dx + cn.z * p_dy);
408 } else {
291 gr[2] = -0.5f * dx * p_dx;409 v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
292 gr[3] = -dx * p_dy;
293 gr[4] = -0.5f * dy * p_dy;
294 gr[5] = ga;
295 gr[6] = w * dpix.x;
296 gr[7] = w * dpix.y;
297 gr[8] = w * dpix.z;
298 }410 }
299 }
300 }
411 float v[3] = {v_dx, b4[0], ga_sum};
412 warp::internal::HalvingStep<3, 4>(v, lane);
413 warp::internal::HalvingStep<2, 2>(v, lane);
414 const float out_grad =
301 if (!__any_sync(warp::kFullMask, hit)) continue;415 v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1);
302 warp::ButterflyReduce(gr);
303 if (lane == 0) {416 if (scatter >= 0 && out_grad != 0.0f) {
304 float* dst = splat_grads + kSplatGradFloats * batch_id[j];417 atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter,
305#pragma unroll
306 for (int q = 0; q < kSplatGradFloats; ++q) {
307 atomicAdd(dst + q, gr[q]);
308 }
418 out_grad);
309 }419 }
310 }420 }
311 }421 }
312 }422 }
↕ Expand 91 unchanged lines (lines 423–513) · in PreprocessBackwardKernel
313 __syncthreads();423 __syncthreads();
314 }424 }
315}425}
316426
317// ---------------------------------------------------------------------------427// ---------------------------------------------------------------------------
318// Preprocess backward: splat gradients -> parameter gradients.428// Preprocess backward: splat gradients -> parameter gradients.
319// Parameter gradient buffers are bulk-zeroed beforehand via `cudaMemsetAsync`;429// Parameter gradient buffers are bulk-zeroed beforehand via `cudaMemsetAsync`;
320// culled or zero-gradient Gaussians return early.430// culled or zero-gradient Gaussians return early.
321431
322template <typename ShT>432template <typename ShT>
323__global__ void __launch_bounds__(kGaussianThreads)433__global__ void __launch_bounds__(kGaussianThreads)
324 PreprocessBackwardKernel(GaussianParams params, Frame frame,434 PreprocessBackwardKernel(GaussianParams params, Frame frame,
325 const Splat* __restrict__ splats,435 const Splat* __restrict__ splats,
326 const float* __restrict__ splat_grads,436 const float* __restrict__ splat_grads,
327 GaussianGrads grads) {437 GaussianGrads grads) {
328 const int i = blockIdx.x * blockDim.x + threadIdx.x;438 const int i = blockIdx.x * blockDim.x + threadIdx.x;
329 if (i >= params.n || splats[i].radius() == 0) return;439 if (i >= params.n || splats[i].radius() == 0) return;
330 float g[kSplatGradFloats];440 float g[kSplatGradFloats];
331 bool nonzero = false;441 bool nonzero = false;
332#pragma unroll442#pragma unroll
333 for (int q = 0; q < kSplatGradFloats; ++q) {443 for (int q = 0; q < kSplatGradFloats; ++q) {
334 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);444 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);
335 nonzero = nonzero || g[q] != 0.0f;445 nonzero = nonzero || g[q] != 0.0f;
336 }446 }
337 if (!nonzero) return;447 if (!nonzero) return;
338 ProjectionGrad pg;448 ProjectionGrad pg;
339 pg.xy = {g[0], g[1]};449 pg.xy = {g[0], g[1]};
340 pg.conic = {g[2], g[3], g[4]};450 pg.conic = {g[2], g[3], g[4]};
341 pg.opacity = g[5];451 pg.opacity = g[5];
342 pg.rgb = {g[6], g[7], g[8]};452 pg.rgb = {g[6], g[7], g[8]};
343 float3 dmean, dscale;453 float3 dmean, dscale;
344 float4 dquat;454 float4 dquat;
345 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),455 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),
346 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);456 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);
347 grads.means[3 * i] = dmean.x;457 grads.means[3 * i] = dmean.x;
348 grads.means[3 * i + 1] = dmean.y;458 grads.means[3 * i + 1] = dmean.y;
349 grads.means[3 * i + 2] = dmean.z;459 grads.means[3 * i + 2] = dmean.z;
350 grads.scales[3 * i] = dscale.x;460 grads.scales[3 * i] = dscale.x;
351 grads.scales[3 * i + 1] = dscale.y;461 grads.scales[3 * i + 1] = dscale.y;
352 grads.scales[3 * i + 2] = dscale.z;462 grads.scales[3 * i + 2] = dscale.z;
353 reinterpret_cast<float4*>(grads.quats)[i] = dquat;463 reinterpret_cast<float4*>(grads.quats)[i] = dquat;
354 grads.opacities[i] = g[5];464 grads.opacities[i] = g[5];
355}465}
356466
357template <typename ShT>467template <typename ShT>
358cudaError_t LaunchPreprocessBackward(const GaussianParams& params,468cudaError_t LaunchPreprocessBackward(const GaussianParams& params,
359 const Frame& frame, const Splat* splats,469 const Frame& frame, const Splat* splats,
360 const float* splat_grads,470 const float* splat_grads,
361 const GaussianGrads& grads,471 const GaussianGrads& grads,
362 cudaStream_t stream) {472 cudaStream_t stream) {
363 const int n = params.n;473 const int n = params.n;
364 // As Inria's PyTorch wrapper (`torch::zeros`): bulk-zero every parameter474 // As Inria's PyTorch wrapper (`torch::zeros`): bulk-zero every parameter
365 // gradient up front; culled Gaussians then exit early in the kernel.475 // gradient up front; culled Gaussians then exit early in the kernel.
366 if (auto e = cudaMemsetAsync(grads.means, 0, sizeof(float) * 3 * n, stream);476 if (auto e = cudaMemsetAsync(grads.means, 0, sizeof(float) * 3 * n, stream);
367 e != cudaSuccess)477 e != cudaSuccess)
368 return e;478 return e;
369 if (auto e = cudaMemsetAsync(grads.scales, 0, sizeof(float) * 3 * n, stream);479 if (auto e = cudaMemsetAsync(grads.scales, 0, sizeof(float) * 3 * n, stream);
370 e != cudaSuccess)480 e != cudaSuccess)
371 return e;481 return e;
372 if (auto e = cudaMemsetAsync(grads.quats, 0, sizeof(float) * 4 * n, stream);482 if (auto e = cudaMemsetAsync(grads.quats, 0, sizeof(float) * 4 * n, stream);
373 e != cudaSuccess)483 e != cudaSuccess)
374 return e;484 return e;
375 if (auto e = cudaMemsetAsync(grads.opacities, 0, sizeof(float) * n, stream);485 if (auto e = cudaMemsetAsync(grads.opacities, 0, sizeof(float) * n, stream);
376 e != cudaSuccess)486 e != cudaSuccess)
377 return e;487 return e;
378 if (auto e =488 if (auto e =
379 cudaMemsetAsync(grads.sh, 0, sizeof(ShT) * 3 * kShCoeffs * n, stream);489 cudaMemsetAsync(grads.sh, 0, sizeof(ShT) * 3 * kShCoeffs * n, stream);
380 e != cudaSuccess)490 e != cudaSuccess)
381 return e;491 return e;
382 PreprocessBackwardKernel<ShT>492 PreprocessBackwardKernel<ShT>
383 <<<GaussianBlocks(n), kGaussianThreads, 0, stream>>>(493 <<<GaussianBlocks(n), kGaussianThreads, 0, stream>>>(
384 params, frame, splats, splat_grads, grads);494 params, frame, splats, splat_grads, grads);
385 return cudaSuccess;495 return cudaSuccess;
386}496}
387497
388template __global__ void PreprocessBackwardKernel<float>(498template __global__ void PreprocessBackwardKernel<float>(
389 GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__,499 GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__,
390 GaussianGrads);500 GaussianGrads);
391template __global__ void PreprocessBackwardKernel<ShBf16>(501template __global__ void PreprocessBackwardKernel<ShBf16>(
392 GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__,502 GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__,
393 GaussianGrads);503 GaussianGrads);
394template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,504template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,
395 const Frame&, const Splat*,505 const Frame&, const Splat*,
396 const float*,506 const float*,
397 const GaussianGrads&,507 const GaussianGrads&,
398 cudaStream_t);508 cudaStream_t);
399template cudaError_t LaunchPreprocessBackward<ShBf16>(509template cudaError_t LaunchPreprocessBackward<ShBf16>(
400 const GaussianParams&, const Frame&, const Splat*, const float*,510 const GaussianParams&, const Frame&, const Splat*, const float*,
401 const GaussianGrads&, cudaStream_t);511 const GaussianGrads&, cudaStream_t);
402512
403} // namespace gs513} // namespace gs
↕ Expand 167 unchanged lines (lines 1–167) · in ProjectVjp
11 /*
22 * Copyright (C) 2023, Inria
33 * GRAPHDECO research group, https://team.inria.fr/graphdeco
44 * All rights reserved.
55 *
66 * This software is free for non-commercial, research and evaluation use
77 * under the terms of the LICENSE.md file.
88 *
99 * For inquiries contact george.drettakis@inria.fr
1010 */
1111
1212 #include "backward.h"
1313
1414 #include "auxiliary.h"
1515
1616 namespace gs {
1717
1818 namespace sh {
1919 // Evaluates SH color VJP loading the full 16-basis row `sh_in` and storing
2020 // `dsh` to `sh_out`.
2121 template <typename ShT>
2222 GS_D float3 VjpRow(float3 d, float3 g_rgb, const ShT* __restrict__ sh_in,
2323 ShT* __restrict__ sh_out) {
2424 float b[kShCoeffs];
2525 Basis(d, b);
2626 float3 coeffs[kShCoeffs];
2727 ShIo<ShT>::LoadRow(sh_in, coeffs);
2828 float3 raw = {0.5f, 0.5f, 0.5f};
2929 #pragma unroll
3030 for (int k = 0; k < kShCoeffs; ++k) {
3131 raw += b[k] * coeffs[k];
3232 }
3333 const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x,
3434 raw.y < 0.0f ? 0.0f : g_rgb.y,
3535 raw.z < 0.0f ? 0.0f : g_rgb.z};
3636 float3 bgrad[kShCoeffs];
3737 BasisGrad(d, bgrad);
3838 float3 dsh[kShCoeffs];
3939 float3 ddir = {0.0f, 0.0f, 0.0f};
4040 #pragma unroll
4141 for (int k = 0; k < kShCoeffs; ++k) {
4242 dsh[k] = b[k] * drgb;
4343 const float s = dot(coeffs[k], drgb);
4444 ddir += s * bgrad[k];
4545 }
4646 ShIo<ShT>::StoreRow(sh_out, dsh);
4747 return ddir;
4848 }
4949 } // namespace sh
5050
5151 // VJP of the projection for a touched Gaussian `i`; reads its SH row from
5252 // `sh_in` and writes dL/dsh directly to `sh_out`.
5353 // Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`.
5454 template <typename ShT>
5555 GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i,
5656 const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out,
5757 const ProjectionGrad& g, float3& dmean_out,
5858 float3& dscale_out, float4& dquat_out) {
5959 const float3 mean = LoadMean(p, i);
6060 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],
6161 p.scales[3 * i + 2]};
6262 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];
6363
6464 // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0).
6565 float3 dmean = {0.0f, 0.0f, 0.0f};
6666 {
6767 float inv_len;
6868 const float3 dir = ViewDir(cam, mean, inv_len);
6969 const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out);
7070 // d(v/|v|)/dv = (I - dir dir^T) / |v|.
7171 dmean += inv_len * (ddir - dot(dir, ddir) * dir);
7272 }
7373
7474 // --- Forward covariance intermediates (reusing R, s0, s1 in VJP).
7575 const float3 t = cam.ToView(mean);
7676 const float tz = t.z;
7777 const float limx = kFovClamp * cam.TanHalfFovX();
7878 const float limy = kFovClamp * cam.TanHalfFovY();
7979 const float txtz = t.x / tz, tytz = t.y / tz;
8080 const bool x_clamped = txtz < -limx || txtz > limx;
8181 const bool y_clamped = tytz < -limy || tytz > limy;
8282 const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;
8383 const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;
8484 const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz);
8585 const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz);
8686 const Mat3& w = cam.rotation;
8787 const float3 m0 = j00 * w.row(0) + j02 * w.row(2);
8888 const float3 m1 = j11 * w.row(1) + j12 * w.row(2);
8989
9090 const Mat3 r = QuatToRotation(quat);
9191 const Sym3 sigma = Covariance3D(r, scale);
9292 const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1);
9393 const float cov_a = dot(m0, s0) + kLowPass;
9494 const float cov_b = dot(m0, s1);
9595 const float cov_c = dot(m1, s1) + kLowPass;
9696
9797 // --- Conic -> 2D covariance: G_cov = -K G_K K.
9898 const float det = cov_a * cov_c - cov_b * cov_b;
9999 const float inv = 1.0f / det;
100100 const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv;
101101 const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z;
102102 // P = G_K K.
103103 const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc;
104104 const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc;
105105 // G_cov = -K P (symmetric).
106106 const float ca = -(ka * p00 + kb * p10);
107107 const float cb = -(ka * p01 + kb * p11);
108108 const float cc = -(kb * p01 + kc * p11);
109109
110110 // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M.
111111 const float3 dm0 = 2.0f * (ca * s0 + cb * s1);
112112 const float3 dm1 = 2.0f * (cb * s0 + cc * s1);
113113 // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T.
114114 Sym3 dsig;
115115 dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x;
116116 dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y;
117117 dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z;
118118 dsig.xy =
119119 ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y;
120120 dsig.xz =
121121 ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z;
122122 dsig.yz =
123123 ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z;
124124
125125 // --- M = J W: dJ = dM W^T (only the nonzero entries of J).
126126 const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2));
127127 const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2));
128128 const float tz2 = tz * tz, tz3 = tz2 * tz;
129129 float3 dt;
130130 dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02;
131131 dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12;
132132 dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 +
133133 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12;
134134 // --- Screen-space mean u = fx x / z + cx.
135135 dt.x += cam.fx / tz * g.xy.x;
136136 dt.y += cam.fy / tz * g.xy.y;
137137 dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2;
138138 dmean += MulT(w, dt);
139139 dmean_out = dmean;
140140
141141 // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A.
142142 const float sv[3] = {scale.x, scale.y, scale.z};
143143 float ds[3];
144144 Mat3 dr;
145145 #pragma unroll
146146 for (int j = 0; j < 3; ++j) {
147147 const float3 r_col = r.col(j);
148148 const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col);
149149 ds[j] = dot(da_col, r_col);
150150 dr.m[0][j] = da_col.x * sv[j];
151151 dr.m[1][j] = da_col.y * sv[j];
152152 dr.m[2][j] = da_col.z * sv[j];
153153 }
154154 dscale_out = {ds[0], ds[1], ds[2]};
155155
156156 // --- R(q) -> q.
157157 const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w;
158158 const auto& G = dr.m;
159159 dquat_out = {
160160 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] -
161161 qy * G[2][0] + qx * G[2][1]),
162162 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] -
163163 qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]),
164164 2.0f *
165165 (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] +
166166 qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]),
167167 2.0f *
168168 (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] -
169169 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])};
170170 }
171171
172−// Reverse traversal per pixel; accumulates splat gradients with standard
172+// Reverse traversal per pixel; accumulates splat gradients via Half-Warp
173−// 5-step full-warp butterfly reductions (45 shuffles) and 9 lane-0 atomics.
173+// Two-Splat Folding and Halving Register Reduce-Scatter.
174−__global__ void __launch_bounds__(RasterLayout<1>::kThreads, 6)
174+__global__ void __launch_bounds__(RasterLayout<1>::kThreads, 4)
175175 RasterBackwardKernel(const int2* __restrict__ ranges,
176176 const int32_t* __restrict__ point_list,
177177 const Splat* __restrict__ splats, Frame frame,
178178 const float* __restrict__ final_t,
↕ Expand 44 unchanged lines (lines 179–222) · in RasterBackwardKernel
179179 const int32_t* __restrict__ n_contrib,
180180 const float* __restrict__ dl_dimage,
181181 float* __restrict__ splat_grads) {
182182 using L = RasterLayout<1>;
183183 constexpr int kRasterThreads = L::kThreads;
184184 constexpr int kWarps = L::kWarps;
185185 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
186186 const int tid = threadIdx.x;
187187 const int warp = tid / 32;
188188 const int lane = tid % 32;
189189 const RasterPixels px = RasterPixels::For<1>(tid);
190190 const float fx = static_cast<float>(px.x);
191191 const float fy = static_cast<float>(px.y0);
192192 const int2 range = ranges[tile];
193193
194194 const bool inside = px.x < frame.width && px.y0 < frame.height;
195195 const int pixels = frame.width * frame.height;
196196 const int p = px.y0 * frame.width + px.x;
197197 float t = inside ? final_t[p] : 0.0f;
198198 const float3 dpix =
199199 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],
200200 dl_dimage[2 * pixels + p]}
201201 : float3{0.0f, 0.0f, 0.0f};
202202 float accum = 0.0f; // dot(color accumulated behind, dpix)
203203 const int last = inside ? n_contrib[p] : 0;
204204
205205 int warp_max_last = last;
206206 #pragma unroll
207207 for (int offset = 16; offset > 0; offset /= 2) {
208208 warp_max_last = max(
209209 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));
210210 }
211211 __shared__ int shared_max_last[kWarps];
212212 if (lane == 0) shared_max_last[warp] = warp_max_last;
213213 __syncthreads();
214214 int block_max_last = 0;
215215 #pragma unroll
216216 for (int w = 0; w < kWarps; ++w) {
217217 block_max_last = max(block_max_last, shared_max_last[w]);
218218 }
219219 if (block_max_last <= 0) return;
220220
221221 __shared__ float4 batch_geom0[kRasterThreads];
222222 __shared__ float2 batch_geom1[kRasterThreads];
223223 __shared__ float4 batch_rgb_o[kRasterThreads];
224224 __shared__ uint32_t batch_id[kRasterThreads];
225225 __shared__ uint8_t batch_mask[kRasterThreads];
226226 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
227+ int scatter = -1;
228+ int scatter2 = -1;
229+ {
230+ const int sub_lane = lane & 15;
231+ const int row = sub_lane >> 3, col = sub_lane & 7;
232+ if (col == 0) {
233+ scatter2 = row;
234+ } else if (col == 2) {
235+ scatter2 = 2 + row;
236+ } else if (col == 4) {
237+ scatter2 = row == 0 ? 4 : 7;
238+ } else if (col == 6) {
239+ scatter2 = row == 0 ? 6 : 8;
240+ } else if (col == 1 && row == 0) {
241+ scatter2 = 5;
242+ }
243+ const int full_row = lane >> 3, full_col = lane & 7;
244+ if (full_col == 0) {
245+ scatter = full_row;
246+ } else if (full_col == 2) {
247+ scatter = full_row == 0 ? 4 : 5 + full_row;
248+ } else if (full_col == 4 && full_row == 0) {
249+ scatter = 5;
250+ }
251+ }
227252
253+ auto eval_splat = [&](float4 g0, float2 g1, float4 ro, int entry, float& dx,
254+ float& ga_sum, float& ga_dy, float& ga_dy2,
255+ float3& drgb) -> bool {
256+ dx = g0.x - fx;
257+ ga_sum = 0.0f;
258+ ga_dy = 0.0f;
259+ ga_dy2 = 0.0f;
260+ drgb = {0.0f, 0.0f, 0.0f};
261+ if (entry < last) {
262+ const float dy = g0.y - fy;
263+ const float power = g0.z * dx * dx + g1.x * dy * dy + g0.w * dx * dy;
264+ if (power <= 0.0f && power >= g1.y) {
265+ const float g_val = Exp2Approx(power);
266+ const float alpha = fminf(kMaxAlpha, ro.w * g_val);
267+ if (alpha >= kMinAlpha) {
268+ t = __fdividef(t, 1.0f - alpha);
269+ const float w = alpha * t;
270+ const float color_dot = ro.x * dpix.x + ro.y * dpix.y + ro.z * dpix.z;
271+ const float diff = color_dot - accum;
272+ const float dl_dalpha = t * diff;
273+ accum += alpha * diff;
274+ const float ga = g_val * dl_dalpha;
275+ ga_sum = ga;
276+ const float ga_y = ga * dy;
277+ ga_dy = ga_y;
278+ ga_dy2 = ga_y * dy;
279+ drgb = w * dpix;
280+ return true;
281+ }
282+ }
283+ }
284+ return false;
285+ };
228286 auto raw_conic = [](float4 g0, float2 g1) {
229287 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,
230288 kInvPower2Scale * g1.x};
231289 };
↕ Expand 21 unchanged lines (lines 290–310) · in RasterBackwardKernel
232290
233291 for (int end = block_max_last; end > 0; end -= kRasterThreads) {
234292 const int e = end - 1 - tid;
235293 if (e >= 0) {
236294 const uint32_t id = static_cast<uint32_t>(point_list[range.x + e]);
237295 const float4 p0 = __ldg(&splats[id].p0);
238296 const float4 p1 = __ldg(&splats[id].p1);
239297 const float4 p2 = __ldg(&splats[id].p2);
240298 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
241299 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
242300 batch_geom1[tid] = {c2.z, p2.w};
243301 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};
244302 batch_id[tid] = id;
245303 batch_mask[tid] = static_cast<uint8_t>(WarpMask<1>(p0, p1));
246304 } else {
247305 batch_mask[tid] = 0u;
248306 }
249307 __syncthreads();
250308 const int count = min(kRasterThreads, end);
251309 #pragma unroll
252310 for (int c = 0; c < kRasterThreads; c += 32) {
253311 if (c < count && end - 32 - c < warp_max_last) {
254312 uint32_t mask = __ballot_sync(
255313 warp::kFullMask,
256314 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
315+ while ((mask & (mask - 1u)) != 0u) {
316+ const int bit0 = __ffs(mask) - 1;
317+ mask &= mask - 1u;
318+ const int bit1 = __ffs(mask) - 1;
319+ mask &= mask - 1u;
320+ const int j0 = c + bit0, j1 = c + bit1;
321+ const float4 g0_0 = batch_geom0[j0];
322+ const float2 g1_0 = batch_geom1[j0];
323+ const float4 ro_0 = batch_rgb_o[j0];
324+ const float4 g0_1 = batch_geom0[j1];
325+ const float2 g1_1 = batch_geom1[j1];
326+ const float4 ro_1 = batch_rgb_o[j1];
327+
328+ float dx0, ga_sum0, ga_dy0, ga_dy2_0;
329+ float3 drgb0;
330+ const bool hit0 = eval_splat(g0_0, g1_0, ro_0, end - 1 - j0, dx0,
331+ ga_sum0, ga_dy0, ga_dy2_0, drgb0);
332+
333+ float dx1, ga_sum1, ga_dy1, ga_dy2_1;
334+ float3 drgb1;
335+ const bool hit1 = eval_splat(g0_1, g1_1, ro_1, end - 1 - j1, dx1,
336+ ga_sum1, ga_dy1, ga_dy2_1, drgb1);
337+ // As in s01: a pair that no pixel of the warp blends has zero
338+ // gradients, so skip its reduction and atomics entirely.
339+ if (!__any_sync(warp::kFullMask, hit0 || hit1)) continue;
340+
341+ const bool hi16 = (lane & 16) != 0;
342+ const float opacity = hi16 ? ro_1.w : ro_0.w;
343+ const float dx = hi16 ? dx1 : dx0;
344+ const float3 cn =
345+ hi16 ? raw_conic(g0_1, g1_1) : raw_conic(g0_0, g1_0);
346+
347+ auto fold16 = [&](float v0, float v1) {
348+ const float send = hi16 ? v0 : v1;
349+ const float recv = __shfl_xor_sync(warp::kFullMask, send, 16);
350+ return (hi16 ? v1 : v0) + recv;
351+ };
352+ float ga_sum = fold16(ga_sum0, ga_sum1);
353+ float ga_dy = fold16(ga_dy0, ga_dy1);
354+ float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1),
355+ fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y),
356+ fold16(drgb0.z, drgb1.z)};
357+ warp::internal::HalvingStep<4, 8>(b4, lane);
358+ ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
359+ ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
360+
361+ const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
362+ const float p_dx = dx * p_sum;
363+ const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
364+ : -(cn.y * p_dx + cn.z * p_dy);
365+ const float v_dx1 = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
366+ float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]};
367+ warp::internal::HalvingStep<4, 4>(v4, lane);
368+ warp::internal::HalvingStep<2, 2>(v4, lane);
369+ ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4);
370+ ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2);
371+ const float v_sum =
372+ v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1);
373+ ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1);
374+ const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum;
375+ if (scatter2 >= 0 && out_grad != 0.0f) {
376+ const uint32_t target_id = batch_id[hi16 ? j1 : j0];
377+ atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2,
378+ out_grad);
379+ }
380+ }
257381 while (mask != 0u) {
258382 const int bit = __ffs(mask) - 1;
259383 mask &= mask - 1u;
260384 const int j = c + bit;
261− const int entry = end - 1 - j;
262− float gr[kSplatGradFloats] = {};
263− bool hit = false;
264− if (entry < last) {
265385⇥ const float4 g0 = batch_geom0[j];
266386⇥ const float2 g1 = batch_geom1[j];
267− const float dx = g0.x - fx;
268− const float dy = g0.y - fy;
269− const float power =
270− g0.z * dx * dx + g1.x * dy * dy + g0.w * dx * dy;
271− if (power <= 0.0f && power >= g1.y) {
272387⇥ const float4 ro = batch_rgb_o[j];
273− const float g_val = Exp2Approx(power);
274− const float alpha = fminf(kMaxAlpha, ro.w * g_val);
275− if (alpha >= kMinAlpha) {
276− hit = true;
277− t = __fdividef(t, 1.0f - alpha);
278− const float w = alpha * t;
279− const float color_dot =
280− ro.x * dpix.x + ro.y * dpix.y + ro.z * dpix.z;
281− const float diff = color_dot - accum;
282− const float dl_dalpha = t * diff;
283− accum += alpha * diff;
284− const float ga = g_val * dl_dalpha;
388+ float dx, ga_sum, ga_dy, ga_dy2;
285− const float dl_dpower = ro.w * ga;
389+ float3 drgb;
286− const float p_dx = dx * dl_dpower;
390+ const bool hit =
391+ eval_splat(g0, g1, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2, drgb);
392+ if (!__any_sync(warp::kFullMask, hit)) continue;
287− const float p_dy = dy * dl_dpower;
393+ const float opacity = ro.w;
288394⇥ const float3 cn = raw_conic(g0, g1);
395+ float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z};
396+ warp::internal::HalvingStep<4, 16>(b4, lane);
397+ warp::internal::HalvingStep<2, 8>(b4, lane);
398+ ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16);
399+ ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16);
400+ ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
401+ ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
402+ const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
403+ const float p_dx = dx * p_sum;
404+ float v_dx;
405+ if ((lane & 16) == 0) {
289− gr[0] = -(cn.x * p_dx + cn.y * p_dy);
406+ v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
290− gr[1] = -(cn.y * p_dx + cn.z * p_dy);
407+ : -(cn.y * p_dx + cn.z * p_dy);
408+ } else {
291− gr[2] = -0.5f * dx * p_dx;
409+ v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
292− gr[3] = -dx * p_dy;
293− gr[4] = -0.5f * dy * p_dy;
294− gr[5] = ga;
295− gr[6] = w * dpix.x;
296− gr[7] = w * dpix.y;
297− gr[8] = w * dpix.z;
298410⇥ }
299− }
300− }
411+ float v[3] = {v_dx, b4[0], ga_sum};
412+ warp::internal::HalvingStep<3, 4>(v, lane);
413+ warp::internal::HalvingStep<2, 2>(v, lane);
414+ const float out_grad =
301− if (!__any_sync(warp::kFullMask, hit)) continue;
415+ v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1);
302− warp::ButterflyReduce(gr);
303− if (lane == 0) {
416+ if (scatter >= 0 && out_grad != 0.0f) {
304− float* dst = splat_grads + kSplatGradFloats * batch_id[j];
417+ atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter,
305−#pragma unroll
306− for (int q = 0; q < kSplatGradFloats; ++q) {
307− atomicAdd(dst + q, gr[q]);
308− }
418+ out_grad);
309419 }
310420 }
311421 }
312422 }
↕ Expand 91 unchanged lines (lines 423–513) · in PreprocessBackwardKernel
313423 __syncthreads();
314424 }
315425 }
316426
317427 // ---------------------------------------------------------------------------
318428 // Preprocess backward: splat gradients -> parameter gradients.
319429 // Parameter gradient buffers are bulk-zeroed beforehand via `cudaMemsetAsync`;
320430 // culled or zero-gradient Gaussians return early.
321431
322432 template <typename ShT>
323433 __global__ void __launch_bounds__(kGaussianThreads)
324434 PreprocessBackwardKernel(GaussianParams params, Frame frame,
325435 const Splat* __restrict__ splats,
326436 const float* __restrict__ splat_grads,
327437 GaussianGrads grads) {
328438 const int i = blockIdx.x * blockDim.x + threadIdx.x;
329439 if (i >= params.n || splats[i].radius() == 0) return;
330440 float g[kSplatGradFloats];
331441 bool nonzero = false;
332442 #pragma unroll
333443 for (int q = 0; q < kSplatGradFloats; ++q) {
334444 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);
335445 nonzero = nonzero || g[q] != 0.0f;
336446 }
337447 if (!nonzero) return;
338448 ProjectionGrad pg;
339449 pg.xy = {g[0], g[1]};
340450 pg.conic = {g[2], g[3], g[4]};
341451 pg.opacity = g[5];
342452 pg.rgb = {g[6], g[7], g[8]};
343453 float3 dmean, dscale;
344454 float4 dquat;
345455 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),
346456 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);
347457 grads.means[3 * i] = dmean.x;
348458 grads.means[3 * i + 1] = dmean.y;
349459 grads.means[3 * i + 2] = dmean.z;
350460 grads.scales[3 * i] = dscale.x;
351461 grads.scales[3 * i + 1] = dscale.y;
352462 grads.scales[3 * i + 2] = dscale.z;
353463 reinterpret_cast<float4*>(grads.quats)[i] = dquat;
354464 grads.opacities[i] = g[5];
355465 }
356466
357467 template <typename ShT>
358468 cudaError_t LaunchPreprocessBackward(const GaussianParams& params,
359469 const Frame& frame, const Splat* splats,
360470 const float* splat_grads,
361471 const GaussianGrads& grads,
362472 cudaStream_t stream) {
363473 const int n = params.n;
364474 // As Inria's PyTorch wrapper (`torch::zeros`): bulk-zero every parameter
365475 // gradient up front; culled Gaussians then exit early in the kernel.
366476 if (auto e = cudaMemsetAsync(grads.means, 0, sizeof(float) * 3 * n, stream);
367477 e != cudaSuccess)
368478 return e;
369479 if (auto e = cudaMemsetAsync(grads.scales, 0, sizeof(float) * 3 * n, stream);
370480 e != cudaSuccess)
371481 return e;
372482 if (auto e = cudaMemsetAsync(grads.quats, 0, sizeof(float) * 4 * n, stream);
373483 e != cudaSuccess)
374484 return e;
375485 if (auto e = cudaMemsetAsync(grads.opacities, 0, sizeof(float) * n, stream);
376486 e != cudaSuccess)
377487 return e;
378488 if (auto e =
379489 cudaMemsetAsync(grads.sh, 0, sizeof(ShT) * 3 * kShCoeffs * n, stream);
380490 e != cudaSuccess)
381491 return e;
382492 PreprocessBackwardKernel<ShT>
383493 <<<GaussianBlocks(n), kGaussianThreads, 0, stream>>>(
384494 params, frame, splats, splat_grads, grads);
385495 return cudaSuccess;
386496 }
387497
388498 template __global__ void PreprocessBackwardKernel<float>(
389499 GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__,
390500 GaussianGrads);
391501 template __global__ void PreprocessBackwardKernel<ShBf16>(
392502 GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__,
393503 GaussianGrads);
394504 template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,
395505 const Frame&, const Splat*,
396506 const float*,
397507 const GaussianGrads&,
398508 cudaStream_t);
399509 template cudaError_t LaunchPreprocessBackward<ShBf16>(
400510 const GaussianParams&, const Frame&, const Splat*, const float*,
401511 const GaussianGrads&, cudaStream_t);
402512
403513 } // namespace gs
Unchanged files in s02_reduce_scatter (identical to s01_literature): csrc/config.h, csrc/forward.h, csrc/forward.cu.cc, csrc/backward.h, csrc/rasterizer_impl.h, csrc/rasterizer_impl.cu.cc