← Back to Step 5 in blog post

s04_raster_pruning → s05_branchless_predication

Step 5: Branchless α=0 Predication & _rn-Pinned Separable Power2Column  ·  +101 / −71 lines across 3 changed files

csrc/auxiliary.h +46 −15
s04_raster_pruning/csrc/auxiliary.h
s05_branchless_predication/csrc/auxiliary.h
↕ Expand 145 unchanged lines (lines 1–145) · in num_tiles
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, 0)
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}; }
↕ Expand 520 unchanged lines (lines 155–674) · in LoadCamera
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// Reduce-scatter of an array of `N` <= 32 per-lane values over the warp.302// Reduce-scatter of an array of `N` <= 32 per-lane values over the warp.
303//303//
304// A butterfly where each step halves the payload: at offset `o`, lanes with304// 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, the305// 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 single306// 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)`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 the308// (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,309// 45 of nine independent butterflies, and the totals end in distinct lanes,
310// so writing them out is one instruction.310// so writing them out is one instruction.
311311
312namespace internal {312namespace internal {
313template <int N, int kOffset>313template <int N, int kOffset>
314GS_D void HalvingStep(float* v, int lane) {314GS_D void HalvingStep(float* v, int lane) {
315 constexpr int kHalf = (N + 1) / 2;315 constexpr int kHalf = (N + 1) / 2;
316 const bool upper = lane & kOffset;316 const bool upper = lane & kOffset;
317#pragma unroll317#pragma unroll
318 for (int i = 0; i < kHalf; ++i) {318 for (int i = 0; i < kHalf; ++i) {
319 const float lo = v[i];319 const float lo = v[i];
320 const float hi = i + kHalf < N ? v[i + kHalf] : 0.0f;320 const float hi = i + kHalf < N ? v[i + kHalf] : 0.0f;
321 const float send = upper ? lo : hi;321 const float send = upper ? lo : hi;
322 const float keep = upper ? hi : lo;322 const float keep = upper ? hi : lo;
323 v[i] = keep + __shfl_xor_sync(kFullMask, send, kOffset);323 v[i] = keep + __shfl_xor_sync(kFullMask, send, kOffset);
324 }324 }
325}325}
326326
327template <int N, int kOffset>327template <int N, int kOffset>
328GS_D void ReduceScatterImpl(float* v, int lane) {328GS_D void ReduceScatterImpl(float* v, int lane) {
329 if constexpr (kOffset > 0) {329 if constexpr (kOffset > 0) {
330 HalvingStep<N, kOffset>(v, lane);330 HalvingStep<N, kOffset>(v, lane);
331 ReduceScatterImpl<(N + 1) / 2, kOffset / 2>(v, lane);331 ReduceScatterImpl<(N + 1) / 2, kOffset / 2>(v, lane);
332 }332 }
333}333}
334} // namespace internal334} // namespace internal
335335
336template <int N>336template <int N>
337GS_D void ReduceScatter(float (&v)[N], int lane) {337GS_D void ReduceScatter(float (&v)[N], int lane) {
338 static_assert(N >= 1 && N <= 32);338 static_assert(N >= 1 && N <= 32);
339 internal::ReduceScatterImpl<N, 16>(v, lane);339 internal::ReduceScatterImpl<N, 16>(v, lane);
340}340}
341341
342template <int N>342template <int N>
343GS_D void ReduceScatter16(float (&v)[N], int lane) {343GS_D void ReduceScatter16(float (&v)[N], int lane) {
344 static_assert(N >= 1 && N <= 16);344 static_assert(N >= 1 && N <= 16);
345 internal::ReduceScatterImpl<N, 8>(v, lane);345 internal::ReduceScatterImpl<N, 8>(v, lane);
346}346}
347347
348// Element whose warp total lane `lane` holds after `ReduceScatter<N>`, or -1.348// Element whose warp total lane `lane` holds after `ReduceScatter<N>`, or -1.
349GS_HD constexpr int ScatterIndex(int n, int lane) {349GS_HD constexpr int ScatterIndex(int n, int lane) {
350 // Payload sizes and halves per step, offset 16 first.350 // Payload sizes and halves per step, offset 16 first.
351 int sizes[5] = {};351 int sizes[5] = {};
352 int size = n;352 int size = n;
353 for (int s = 0; s < 5; ++s) {353 for (int s = 0; s < 5; ++s) {
354 sizes[s] = size;354 sizes[s] = size;
355 size = (size + 1) / 2;355 size = (size + 1) / 2;
356 }356 }
357 int index = 0; // Slot in the final (size 1) array.357 int index = 0; // Slot in the final (size 1) array.
358 for (int s = 4; s >= 0; --s) {358 for (int s = 4; s >= 0; --s) {
359 const int half = (sizes[s] + 1) / 2;359 const int half = (sizes[s] + 1) / 2;
360 if (lane & (16 >> s)) index += half;360 if (lane & (16 >> s)) index += half;
361 if (index >= sizes[s]) return -1;361 if (index >= sizes[s]) return -1;
362 }362 }
363 return index;363 return index;
364}364}
365365
366// Element whose 16-lane half-warp total lane `lane` holds after366// Element whose 16-lane half-warp total lane `lane` holds after
367// `ReduceScatter16<N>`, or -1.367// `ReduceScatter16<N>`, or -1.
368GS_HD constexpr int ScatterIndex16(int n, int lane) {368GS_HD constexpr int ScatterIndex16(int n, int lane) {
369 const int half_lane = lane & 15;369 const int half_lane = lane & 15;
370 int sizes[4] = {};370 int sizes[4] = {};
371 int size = n;371 int size = n;
372 for (int s = 0; s < 4; ++s) {372 for (int s = 0; s < 4; ++s) {
373 sizes[s] = size;373 sizes[s] = size;
374 size = (size + 1) / 2;374 size = (size + 1) / 2;
375 }375 }
376 int index = 0;376 int index = 0;
377 for (int s = 3; s >= 0; --s) {377 for (int s = 3; s >= 0; --s) {
378 const int half = (sizes[s] + 1) / 2;378 const int half = (sizes[s] + 1) / 2;
379 if (half_lane & (8 >> s)) index += half;379 if (half_lane & (8 >> s)) index += half;
380 if (index >= sizes[s]) return -1;380 if (index >= sizes[s]) return -1;
381 }381 }
382 return index;382 return index;
383}383}
384384
385} // namespace warp385} // namespace warp
386386
387inline constexpr int kShCoeffs = 16; // Degree 3.387inline constexpr int kShCoeffs = 16; // Degree 3.
388inline constexpr float kNearPlane = 0.2f;388inline constexpr float kNearPlane = 0.2f;
389inline constexpr float kLowPass = 0.3f;389inline constexpr float kLowPass = 0.3f;
390inline constexpr float kFovClamp = 1.3f;390inline constexpr float kFovClamp = 1.3f;
391391
392// Pointers to the trainable parameters, all activated, row-major.392// Pointers to the trainable parameters, all activated, row-major.
393struct GaussianParams {393struct GaussianParams {
394 const float* means; // (N, 3)394 const float* means; // (N, 3)
395 const float* scales; // (N, 3)395 const float* scales; // (N, 3)
396 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.
397 const float* opacities; // (N,)397 const float* opacities; // (N,)
398 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`).
399 int n;399 int n;
400};400};
401401
402// Gradients w.r.t. `GaussianParams`, same layout and SH storage type.402// Gradients w.r.t. `GaussianParams`, same layout and SH storage type.
403struct GaussianGrads {403struct GaussianGrads {
404 float* means;404 float* means;
405 float* scales;405 float* scales;
406 float* quats;406 float* quats;
407 float* opacities;407 float* opacities;
408 void* sh;408 void* sh;
409};409};
410410
411// Screen-space quantities needed by rasterization. `conic` is the inverse 2D411// Screen-space quantities needed by rasterization. `conic` is the inverse 2D
412// 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.
413struct Projection {413struct Projection {
414 float2 xy;414 float2 xy;
415 float depth;415 float depth;
416 float3 conic;416 float3 conic;
417 float opacity;417 float opacity;
418 float limit;418 float limit;
419 float3 rgb;419 float3 rgb;
420 int radius; // 0 means culled.420 int radius; // 0 means culled.
421};421};
422422
423// ----------------------------------------------------------------------------423// ----------------------------------------------------------------------------
424// 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
425// and is accessed in 4 groups of 4 coefficients (12 values). The coefficients425// and is accessed in 4 groups of 4 coefficients (12 values). The coefficients
426// may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32.426// may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32.
427427
428using ShBf16 = uint16_t; // bfloat16 bits.428using ShBf16 = uint16_t; // bfloat16 bits.
429429
430template <typename T>430template <typename T>
431struct ShIo;431struct ShIo;
432432
433template <>433template <>
434struct ShIo<float> {434struct ShIo<float> {
435 static constexpr int kRowVec16 = 12; // 16-byte vectors per row.435 static constexpr int kRowVec16 = 12; // 16-byte vectors per row.
436 GS_D static void LoadGroup(const float* row, int grp, float v[12]) {436 GS_D static void LoadGroup(const float* row, int grp, float v[12]) {
437 const float4* p = reinterpret_cast<const float4*>(row) + 3 * grp;437 const float4* p = reinterpret_cast<const float4*>(row) + 3 * grp;
438 const float4 a = __ldg(p), b = __ldg(p + 1), c = __ldg(p + 2);438 const float4 a = __ldg(p), b = __ldg(p + 1), c = __ldg(p + 2);
439 v[0] = a.x, v[1] = a.y, v[2] = a.z, v[3] = a.w;439 v[0] = a.x, v[1] = a.y, v[2] = a.z, v[3] = a.w;
440 v[4] = b.x, v[5] = b.y, v[6] = b.z, v[7] = b.w;440 v[4] = b.x, v[5] = b.y, v[6] = b.z, v[7] = b.w;
441 v[8] = c.x, v[9] = c.y, v[10] = c.z, v[11] = c.w;441 v[8] = c.x, v[9] = c.y, v[10] = c.z, v[11] = c.w;
442 }442 }
443 GS_D static void StoreGroup(float* row, int grp, const float v[12]) {443 GS_D static void StoreGroup(float* row, int grp, const float v[12]) {
444 float4* p = reinterpret_cast<float4*>(row) + 3 * grp;444 float4* p = reinterpret_cast<float4*>(row) + 3 * grp;
445 p[0] = {v[0], v[1], v[2], v[3]};445 p[0] = {v[0], v[1], v[2], v[3]};
446 p[1] = {v[4], v[5], v[6], v[7]};446 p[1] = {v[4], v[5], v[6], v[7]};
447 p[2] = {v[8], v[9], v[10], v[11]};447 p[2] = {v[8], v[9], v[10], v[11]};
448 }448 }
449};449};
450450
451template <>451template <>
452struct ShIo<ShBf16> {452struct ShIo<ShBf16> {
453 static constexpr int kRowVec16 = 6;453 static constexpr int kRowVec16 = 6;
454 GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); }454 GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); }
455 GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); }455 GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); }
456 // Round to nearest even (inputs are finite).456 // Round to nearest even (inputs are finite).
457 GS_D static uint32_t Round(float f) {457 GS_D static uint32_t Round(float f) {
458 const uint32_t u = __float_as_uint(f);458 const uint32_t u = __float_as_uint(f);
459 return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16;459 return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16;
460 }460 }
461 GS_D static uint32_t Pack(float lo, float hi) {461 GS_D static uint32_t Pack(float lo, float hi) {
462 return Round(lo) | (Round(hi) << 16);462 return Round(lo) | (Round(hi) << 16);
463 }463 }
464 GS_D static void LoadGroup(const ShBf16* row, int grp, float v[12]) {464 GS_D static void LoadGroup(const ShBf16* row, int grp, float v[12]) {
465 // 24-byte groups: three 8-byte loads.465 // 24-byte groups: three 8-byte loads.
466 const uint2* p = reinterpret_cast<const uint2*>(row) + 3 * grp;466 const uint2* p = reinterpret_cast<const uint2*>(row) + 3 * grp;
467#pragma unroll467#pragma unroll
468 for (int k = 0; k < 3; ++k) {468 for (int k = 0; k < 3; ++k) {
469 const uint2 w = __ldg(p + k);469 const uint2 w = __ldg(p + k);
470 v[4 * k] = Lo(w.x), v[4 * k + 1] = Hi(w.x);470 v[4 * k] = Lo(w.x), v[4 * k + 1] = Hi(w.x);
471 v[4 * k + 2] = Lo(w.y), v[4 * k + 3] = Hi(w.y);471 v[4 * k + 2] = Lo(w.y), v[4 * k + 3] = Hi(w.y);
472 }472 }
473 }473 }
474 GS_D static void StoreGroup(ShBf16* row, int grp, const float v[12]) {474 GS_D static void StoreGroup(ShBf16* row, int grp, const float v[12]) {
475 uint2* p = reinterpret_cast<uint2*>(row) + 3 * grp;475 uint2* p = reinterpret_cast<uint2*>(row) + 3 * grp;
476#pragma unroll476#pragma unroll
477 for (int k = 0; k < 3; ++k) {477 for (int k = 0; k < 3; ++k) {
478 p[k] = {Pack(v[4 * k], v[4 * k + 1]), Pack(v[4 * k + 2], v[4 * k + 3])};478 p[k] = {Pack(v[4 * k], v[4 * k + 1]), Pack(v[4 * k + 2], v[4 * k + 3])};
479 }479 }
480 }480 }
481};481};
482482
483template <typename ShT>483template <typename ShT>
484GS_D const ShT* ShRow(const void* sh, int i) {484GS_D const ShT* ShRow(const void* sh, int i) {
485 return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;485 return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;
486}486}
487template <typename ShT>487template <typename ShT>
488GS_D ShT* ShRow(void* sh, int i) {488GS_D ShT* ShRow(void* sh, int i) {
489 return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;489 return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;
490}490}
491491
492// ----------------------------------------------------------------------------492// ----------------------------------------------------------------------------
493// Spherical harmonics (degree 3, Inria sign convention).493// Spherical harmonics (degree 3, Inria sign convention).
494494
495namespace sh {495namespace sh {
496inline constexpr float kC0 = 0.28209479177387814f;496inline constexpr float kC0 = 0.28209479177387814f;
497inline constexpr float kC1 = 0.4886025119029199f;497inline constexpr float kC1 = 0.4886025119029199f;
498inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f,498inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f,
499 0.31539156525252005f, -1.0925484305920792f,499 0.31539156525252005f, -1.0925484305920792f,
500 0.5462742152960396f};500 0.5462742152960396f};
501inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f,501inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f,
502 -0.4570457994644658f, 0.3731763325901154f,502 -0.4570457994644658f, 0.3731763325901154f,
503 -0.4570457994644658f, 1.445305721320277f,503 -0.4570457994644658f, 1.445305721320277f,
504 -0.5900435899266435f};504 -0.5900435899266435f};
505505
506// Basis values for a unit direction.506// Basis values for a unit direction.
507GS_D void Basis(float3 d, float b[kShCoeffs]) {507GS_D void Basis(float3 d, float b[kShCoeffs]) {
508 const float x = d.x, y = d.y, z = d.z;508 const float x = d.x, y = d.y, z = d.z;
509 const float xx = x * x, yy = y * y, zz = z * z;509 const float xx = x * x, yy = y * y, zz = z * z;
510 b[0] = kC0;510 b[0] = kC0;
511 b[1] = -kC1 * y;511 b[1] = -kC1 * y;
512 b[2] = kC1 * z;512 b[2] = kC1 * z;
513 b[3] = -kC1 * x;513 b[3] = -kC1 * x;
514 b[4] = kC2[0] * x * y;514 b[4] = kC2[0] * x * y;
515 b[5] = kC2[1] * y * z;515 b[5] = kC2[1] * y * z;
516 b[6] = kC2[2] * (2.0f * zz - xx - yy);516 b[6] = kC2[2] * (2.0f * zz - xx - yy);
517 b[7] = kC2[3] * x * z;517 b[7] = kC2[3] * x * z;
518 b[8] = kC2[4] * (xx - yy);518 b[8] = kC2[4] * (xx - yy);
519 b[9] = kC3[0] * y * (3.0f * xx - yy);519 b[9] = kC3[0] * y * (3.0f * xx - yy);
520 b[10] = kC3[1] * x * y * z;520 b[10] = kC3[1] * x * y * z;
521 b[11] = kC3[2] * y * (4.0f * zz - xx - yy);521 b[11] = kC3[2] * y * (4.0f * zz - xx - yy);
522 b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy);522 b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy);
523 b[13] = kC3[4] * x * (4.0f * zz - xx - yy);523 b[13] = kC3[4] * x * (4.0f * zz - xx - yy);
524 b[14] = kC3[5] * z * (xx - yy);524 b[14] = kC3[5] * z * (xx - yy);
525 b[15] = kC3[6] * x * (xx - 3.0f * yy);525 b[15] = kC3[6] * x * (xx - 3.0f * yy);
526}526}
527527
528// Pre-clamp color 0.5 + sum_k b_k sh_k, reading directly from an SH row.528// Pre-clamp color 0.5 + sum_k b_k sh_k, reading directly from an SH row.
529template <typename ShT>529template <typename ShT>
530GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) {530GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) {
531 float3 c = {0.5f, 0.5f, 0.5f};531 float3 c = {0.5f, 0.5f, 0.5f};
532#pragma unroll532#pragma unroll
533 for (int grp = 0; grp < 4; ++grp) {533 for (int grp = 0; grp < 4; ++grp) {
534 const int k0 = 4 * grp;534 const int k0 = 4 * grp;
535 float v[12];535 float v[12];
536 ShIo<ShT>::LoadGroup(sh_row, grp, v);536 ShIo<ShT>::LoadGroup(sh_row, grp, v);
537 c += b[k0] * float3{v[0], v[1], v[2]};537 c += b[k0] * float3{v[0], v[1], v[2]};
538 c += b[k0 + 1] * float3{v[3], v[4], v[5]};538 c += b[k0 + 1] * float3{v[3], v[4], v[5]};
539 c += b[k0 + 2] * float3{v[6], v[7], v[8]};539 c += b[k0 + 2] * float3{v[6], v[7], v[8]};
540 c += b[k0 + 3] * float3{v[9], v[10], v[11]};540 c += b[k0 + 3] * float3{v[9], v[10], v[11]};
541 }541 }
542 return c;542 return c;
543}543}
544} // namespace sh544} // namespace sh
545545
546// ----------------------------------------------------------------------------546// ----------------------------------------------------------------------------
547// Covariance helpers.547// Covariance helpers.
548548
549// Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z).549// Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z).
550GS_D Mat3 QuatToRotation(float4 q) {550GS_D Mat3 QuatToRotation(float4 q) {
551 const float w = q.x, x = q.y, y = q.z, z = q.w;551 const float w = q.x, x = q.y, y = q.z, z = q.w;
552 Mat3 r;552 Mat3 r;
553 r.m[0][0] = 1.0f - 2.0f * (y * y + z * z);553 r.m[0][0] = 1.0f - 2.0f * (y * y + z * z);
554 r.m[0][1] = 2.0f * (x * y - w * z);554 r.m[0][1] = 2.0f * (x * y - w * z);
555 r.m[0][2] = 2.0f * (x * z + w * y);555 r.m[0][2] = 2.0f * (x * z + w * y);
556 r.m[1][0] = 2.0f * (x * y + w * z);556 r.m[1][0] = 2.0f * (x * y + w * z);
557 r.m[1][1] = 1.0f - 2.0f * (x * x + z * z);557 r.m[1][1] = 1.0f - 2.0f * (x * x + z * z);
558 r.m[1][2] = 2.0f * (y * z - w * x);558 r.m[1][2] = 2.0f * (y * z - w * x);
559 r.m[2][0] = 2.0f * (x * z - w * y);559 r.m[2][0] = 2.0f * (x * z - w * y);
560 r.m[2][1] = 2.0f * (y * z + w * x);560 r.m[2][1] = 2.0f * (y * z + w * x);
561 r.m[2][2] = 1.0f - 2.0f * (x * x + y * y);561 r.m[2][2] = 1.0f - 2.0f * (x * x + y * y);
562 return r;562 return r;
563}563}
564564
565// Symmetric 3x3 matrix.565// Symmetric 3x3 matrix.
566struct Sym3 {566struct Sym3 {
567 float xx, xy, xz, yy, yz, zz;567 float xx, xy, xz, yy, yz, zz;
568 GS_D float3 Mul(float3 v) const {568 GS_D float3 Mul(float3 v) const {
569 return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z,569 return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z,
570 xz * v.x + yz * v.y + zz * v.z};570 xz * v.x + yz * v.y + zz * v.z};
571 }571 }
572};572};
573573
574// Sigma = A A^T with A = R diag(s).574// Sigma = A A^T with A = R diag(s).
575GS_D Sym3 Covariance3D(const Mat3& r, float3 s) {575GS_D Sym3 Covariance3D(const Mat3& r, float3 s) {
576 Mat3 a;576 Mat3 a;
577 const float sv[3] = {s.x, s.y, s.z};577 const float sv[3] = {s.x, s.y, s.z};
578#pragma unroll578#pragma unroll
579 for (int i = 0; i < 3; ++i)579 for (int i = 0; i < 3; ++i)
580#pragma unroll580#pragma unroll
581 for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j];581 for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j];
582 return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)),582 return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)),
583 dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)),583 dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)),
584 dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))};584 dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))};
585}585}
586586
587// Intermediate values of the projection shared by forward and VJP.587// Intermediate values of the projection shared by forward and VJP.
588struct ProjectionState {588struct ProjectionState {
589 float3 t; // View-space mean.589 float3 t; // View-space mean.
590 float tx_c, ty_c; // Clamped x, y used by the Jacobian.590 float tx_c, ty_c; // Clamped x, y used by the Jacobian.
591 bool x_clamped, y_clamped;591 bool x_clamped, y_clamped;
592 float3 m0, m1; // Rows of M = J W (2x3).592 float3 m0, m1; // Rows of M = J W (2x3).
593 Sym3 sigma; // World-space 3D covariance.593 Sym3 sigma; // World-space 3D covariance.
594 float a, b, c; // Dilated 2D covariance.594 float a, b, c; // Dilated 2D covariance.
595};595};
596596
597// Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane).597// Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane).
598GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale,598GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale,
599 float4 quat, ProjectionState& st) {599 float4 quat, ProjectionState& st) {
600 st.t = t;600 st.t = t;
601 const float tz = t.z;601 const float tz = t.z;
602 const float limx = kFovClamp * cam.TanHalfFovX();602 const float limx = kFovClamp * cam.TanHalfFovX();
603 const float limy = kFovClamp * cam.TanHalfFovY();603 const float limy = kFovClamp * cam.TanHalfFovY();
604 const float txtz = t.x / tz, tytz = t.y / tz;604 const float txtz = t.x / tz, tytz = t.y / tz;
605 st.x_clamped = txtz < -limx || txtz > limx;605 st.x_clamped = txtz < -limx || txtz > limx;
606 st.y_clamped = tytz < -limy || tytz > limy;606 st.y_clamped = tytz < -limy || tytz > limy;
607 st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;607 st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;
608 st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;608 st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;
609 const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz);609 const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz);
610 const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz);610 const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz);
611 const Mat3& w = cam.rotation;611 const Mat3& w = cam.rotation;
612 st.m0 = j00 * w.row(0) + j02 * w.row(2);612 st.m0 = j00 * w.row(0) + j02 * w.row(2);
613 st.m1 = j11 * w.row(1) + j12 * w.row(2);613 st.m1 = j11 * w.row(1) + j12 * w.row(2);
614 st.sigma = Covariance3D(QuatToRotation(quat), scale);614 st.sigma = Covariance3D(QuatToRotation(quat), scale);
615 const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1);615 const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1);
616 st.a = dot(st.m0, s0) + kLowPass;616 st.a = dot(st.m0, s0) + kLowPass;
617 st.b = dot(st.m0, s1);617 st.b = dot(st.m0, s1);
618 st.c = dot(st.m1, s1) + kLowPass;618 st.c = dot(st.m1, s1) + kLowPass;
619}619}
620620
621// Computes everything up to the 2D covariance. Returns false if culled by the621// Computes everything up to the 2D covariance. Returns false if culled by the
622// near plane.622// near plane.
623GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale,623GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale,
624 float4 quat, ProjectionState& st) {624 float4 quat, ProjectionState& st) {
625 const float3 t = cam.ToView(mean);625 const float3 t = cam.ToView(mean);
626 if (t.z <= kNearPlane) return false;626 if (t.z <= kNearPlane) return false;
627 ProjectCovarianceFromView(cam, t, scale, quat, st);627 ProjectCovarianceFromView(cam, t, scale, quat, st);
628 return true;628 return true;
629}629}
630630
631// Integer bounding radius (pixels) of the 2D covariance, as in the reference.631// Integer bounding radius (pixels) of the 2D covariance, as in the reference.
632GS_D int BoundingRadius(float a, float b, float c) {632GS_D int BoundingRadius(float a, float b, float c) {
633 const float det = a * c - b * b;633 const float det = a * c - b * b;
634 const float mid = 0.5f * (a + c);634 const float mid = 0.5f * (a + c);
635 const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det));635 const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det));
636 return static_cast<int>(ceilf(3.0f * sqrtf(lambda)));636 return static_cast<int>(ceilf(3.0f * sqrtf(lambda)));
637}637}
638638
639GS_D float2 ProjectMean(const Camera& cam, float3 t) {639GS_D float2 ProjectMean(const Camera& cam, float3 t) {
640 return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy};640 return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy};
641}641}
642642
643GS_D float3 LoadMean(const GaussianParams& p, int i) {643GS_D float3 LoadMean(const GaussianParams& p, int i) {
644 return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]};644 return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]};
645}645}
646646
647// Unit direction from the camera center to `mean`, and 1 / distance.647// Unit direction from the camera center to `mean`, and 1 / distance.
648GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) {648GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) {
649 const float3 v = mean - cam.position;649 const float3 v = mean - cam.position;
650 inv_len = rsqrtf(dot(v, v));650 inv_len = rsqrtf(dot(v, v));
651 return inv_len * v;651 return inv_len * v;
652}652}
653653
654// Gradient of the loss w.r.t. one splat's screen-space quantities.654// Gradient of the loss w.r.t. one splat's screen-space quantities.
655struct ProjectionGrad {655struct ProjectionGrad {
656 float2 xy;656 float2 xy;
657 float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`.657 float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`.
658 float opacity;658 float opacity;
659 float3 rgb;659 float3 rgb;
660};660};
661661
662// Image geometry passed by value to every kernel.662// Image geometry passed by value to every kernel.
663struct Frame {663struct Frame {
664 const float* camera; // Device pointer to the packed camera.664 const float* camera; // Device pointer to the packed camera.
665 int width, height;665 int width, height;
666 TileGrid grid;666 TileGrid grid;
667 GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); }667 GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); }
668};668};
669669
670// ---------------------------------------------------------------------------670// ---------------------------------------------------------------------------
671// Per-Gaussian kernels run one thread per Gaussian.671// Per-Gaussian kernels run one thread per Gaussian.
672672
673inline constexpr int kGaussianThreads = 128;673inline constexpr int kGaussianThreads = 128;
674674
675inline int GaussianBlocks(int64_t n) {675inline int GaussianBlocks(int64_t n) {
676 return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads);676 return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads);
677}677}
678678
679// Base-2 exponent scale: power2 = kPower2Scale * q = (-log2(e)/2) * q.
680inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2
681inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;
682
683GS_HD float3 Power2Conic(float a, float b, float c) {
684 return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};
685}
686
687GS_D float Exp2Approx(float x) {
688 float y;
689 asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
690 return y;
691}
692
693// ---------------------------------------------------------------------------679// ---------------------------------------------------------------------------
694// Rasterization. One block per 16x16 tile. Each thread shades `kRows`680// Rasterization. One block per 16x16 tile. Each thread shades `kRows`
695// vertically adjacent pixels, so that per-splat costs (shared-memory loads,681// vertically adjacent pixels, so that per-splat costs (shared-memory loads,
696// barriers, the warp reduction in the backward pass) are paid once per682// barriers, the warp reduction in the backward pass) are paid once per
697// `kRows` pixels; more rows per thread also means fewer resident threads and683// `kRows` pixels; more rows per thread also means fewer resident threads and
698// more waiting on the slowest pixel. A warp covers a compact684// more waiting on the slowest pixel. A warp covers a compact
699// kWarpWidth x kWarpRows region (as square as possible, so per-warp culling685// kWarpWidth x kWarpRows region (as square as possible, so per-warp culling
700// is tight); warps tile the 16x16 tile row-major. Splats are staged through686// is tight); warps tile the 16x16 tile row-major. Splats are staged through
701// shared memory, one per thread per batch.687// shared memory, one per thread per batch.
702688
703inline constexpr int kRasterRowChoices[] = {1, 2, 4};689inline constexpr int kRasterRowChoices[] = {1, 2, 4};
690
691// Per-pixel splat evaluation shared by the forward and backward rasterizers.
692// They must agree bit for bit on every alpha (the backward recovers
693// transmittance by dividing alphas back out), so the rounding of every step
694// is pinned with explicit `_rn` intrinsics.
695//
696// The falloff is evaluated in log2 units, power2 = A dx^2 + B dx dy + C dy^2
697// with (A, B, C) = -log2(e)/2 (a, 2b, c), so that the Gaussian is 2^power2:
698// two FMAs and one MUFU.EX2 per pixel. A pixel receives the splat iff
699// power2 <= 0 and alpha >= kMinAlpha (the latter implies power2 >= the
700// splat's culling bound, see `AlphaLimit`).
701inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2
702inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;
703
704GS_HD float3 Power2Conic(float a, float b, float c) {
705 return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};
706}
707
708// Splat terms of one pixel column at dx = splat.x - pixel.x.
709struct Power2Column {
710 float pa, pb; // A dx^2, B dx.
711 GS_D static Power2Column At(float a2, float b2, float dx) {
712 return {__fmul_rn(__fmul_rn(a2, dx), dx), __fmul_rn(b2, dx)};
713 }
714 GS_D float Row(float c2, float dy) const {
715 return __fmaf_rn(__fmaf_rn(c2, dy, pb), dy, pa);
716 }
717};
718
719// 2^x and 1/x without the denormal range fix-ups of exp2f/__fdividef (their
720// inputs and results here are never denormal where it matters).
721GS_D float Exp2Approx(float x) {
722 float y;
723 asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
724 return y;
725}
726GS_D float RcpApprox(float x) {
727 float y;
728 asm("rcp.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
729 return y;
730}
731
732GS_D float SplatAlpha(float opacity, float gaussian) {
733 return fminf(kMaxAlpha, __fmul_rn(opacity, gaussian));
734}
704735
705template <int kRows>736template <int kRows>
706struct RasterLayout {737struct RasterLayout {
707 static_assert(kRows == 1 || kRows == 2 || kRows == 4);738 static_assert(kRows == 1 || kRows == 2 || kRows == 4);
↕ Expand 105 unchanged lines (lines 739–843) · in WarpMask
708 static constexpr int kThreads = kTilePixels / kRows;739 static constexpr int kThreads = kTilePixels / kRows;
709 static constexpr int kWarps = kThreads / 32;740 static constexpr int kWarps = kThreads / 32;
710 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;741 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;
711 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;742 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;
712 static constexpr int kWarpsX = kTileSize / kWarpWidth;743 static constexpr int kWarpsX = kTileSize / kWarpWidth;
713 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);744 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);
714745
715 // Tile-relative origin of warp `w`'s region.746 // Tile-relative origin of warp `w`'s region.
716 GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; }747 GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; }
717 GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; }748 GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; }
718};749};
719750
720// Pixel coordinates of this thread's first pixel; the others follow in y.751// Pixel coordinates of this thread's first pixel; the others follow in y.
721struct RasterPixels {752struct RasterPixels {
722 int x, y0;753 int x, y0;
723 template <int kRows>754 template <int kRows>
724 GS_D static RasterPixels For(int tid) {755 GS_D static RasterPixels For(int tid) {
725 using L = RasterLayout<kRows>;756 using L = RasterLayout<kRows>;
726 const int warp = tid / 32, lane = tid % 32;757 const int warp = tid / 32, lane = tid % 32;
727 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +758 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +
728 lane % L::kWarpWidth,759 lane % L::kWarpWidth,
729 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +760 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +
730 (lane / L::kWarpWidth) * kRows};761 (lane / L::kWarpWidth) * kRows};
731 }762 }
732};763};
733764
734// Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the765// Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the
735// pixel region of warp w of this block's tile.766// pixel region of warp w of this block's tile.
736template <int kRows>767template <int kRows>
737GS_D uint32_t WarpMask(float4 p0, float4 p1) {768GS_D uint32_t WarpMask(float4 p0, float4 p1) {
738 using L = RasterLayout<kRows>;769 using L = RasterLayout<kRows>;
739 constexpr int kWarpsY = L::kWarps / L::kWarpsX;770 constexpr int kWarpsY = L::kWarps / L::kWarpsX;
740 float limit = p1.z;771 float limit = p1.z;
741 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;772 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;
742 const float a = p0.z, b = p0.w, c = p1.x;773 const float a = p0.z, b = p0.w, c = p1.x;
743 const float det = a * c - b * b;774 const float det = a * c - b * b;
744 if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0;775 if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0;
745 const float l_over_det = limit / det;776 const float l_over_det = limit / det;
746 const float rx = sqrtf(c * l_over_det);777 const float rx = sqrtf(c * l_over_det);
747 const float ry = sqrtf(a * l_over_det);778 const float ry = sqrtf(a * l_over_det);
748 const float2 xy = {p0.x, p0.y};779 const float2 xy = {p0.x, p0.y};
749 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));780 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));
750 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));781 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));
751 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));782 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));
752 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));783 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));
753 if (x0 > x1 || y0 > y1) return 0;784 if (x0 > x1 || y0 > y1) return 0;
754 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;785 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;
755 uint32_t mx = 0u;786 uint32_t mx = 0u;
756#pragma unroll787#pragma unroll
757 for (int wx = 0; wx < L::kWarpsX; ++wx) {788 for (int wx = 0; wx < L::kWarpsX; ++wx) {
758 const int wx0 = tx + wx * L::kWarpWidth;789 const int wx0 = tx + wx * L::kWarpWidth;
759 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);790 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);
760 }791 }
761 if (mx == 0u) return 0;792 if (mx == 0u) return 0;
762 uint32_t my = 0u;793 uint32_t my = 0u;
763#pragma unroll794#pragma unroll
764 for (int wy = 0; wy < kWarpsY; ++wy) {795 for (int wy = 0; wy < kWarpsY; ++wy) {
765 const int wy0 = ty + wy * L::kWarpRows;796 const int wy0 = ty + wy * L::kWarpRows;
766 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);797 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);
767 }798 }
768 if (my == 0u) return 0;799 if (my == 0u) return 0;
769 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {800 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {
770 uint32_t mask = 0u;801 uint32_t mask = 0u;
771#pragma unroll802#pragma unroll
772 for (int wy = 0; wy < kWarpsY; ++wy) {803 for (int wy = 0; wy < kWarpsY; ++wy) {
773 if (!((my >> wy) & 1u)) continue;804 if (!((my >> wy) & 1u)) continue;
774#pragma unroll805#pragma unroll
775 for (int wx = 0; wx < L::kWarpsX; ++wx) {806 for (int wx = 0; wx < L::kWarpsX; ++wx) {
776 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));807 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));
777 }808 }
778 }809 }
779 return mask;810 return mask;
780 }811 }
781 EllipseBounds eb;812 EllipseBounds eb;
782 eb.a = a;813 eb.a = a;
783 eb.b = b;814 eb.b = b;
784 eb.c = c;815 eb.c = c;
785 eb.det = det;816 eb.det = det;
786 eb.inv_a = 1.0f / a;817 eb.inv_a = 1.0f / a;
787 eb.a_limit = a * limit;818 eb.a_limit = a * limit;
788 eb.y_ext = ry;819 eb.y_ext = ry;
789 eb.dy_peak = b * sqrtf(l_over_det / c);820 eb.dy_peak = b * sqrtf(l_over_det / c);
790 uint32_t mask = 0u;821 uint32_t mask = 0u;
791#pragma unroll822#pragma unroll
792 for (int wy = 0; wy < kWarpsY; ++wy) {823 for (int wy = 0; wy < kWarpsY; ++wy) {
793 if (!((my >> wy) & 1u)) continue;824 if (!((my >> wy) & 1u)) continue;
794 const int wy0 = ty + wy * L::kWarpRows;825 const int wy0 = ty + wy * L::kWarpRows;
795 int x0_px, x1_px;826 int x0_px, x1_px;
796 if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) {827 if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) {
797 continue;828 continue;
798 }829 }
799#pragma unroll830#pragma unroll
800 for (int wx = 0; wx < L::kWarpsX; ++wx) {831 for (int wx = 0; wx < L::kWarpsX; ++wx) {
801 const int wx0 = tx + wx * L::kWarpWidth;832 const int wx0 = tx + wx * L::kWarpWidth;
802 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {833 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {
803 mask |= 1u << (wy * L::kWarpsX + wx);834 mask |= 1u << (wy * L::kWarpsX + wx);
804 }835 }
805 }836 }
806 }837 }
807 return mask;838 return mask;
808}839}
809840
810} // namespace gs841} // namespace gs
811842
812#endif843#endif
↕ Expand 145 unchanged lines (lines 1–145) · in num_tiles
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
150−// p2 = (r, g, b, min_power)
150+// p2 = (r, g, b, 0)
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}; }
↕ Expand 520 unchanged lines (lines 155–674) · in LoadCamera
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
302302 // Reduce-scatter of an array of `N` <= 32 per-lane values over the warp.
303303 //
304304 // A butterfly where each step halves the payload: at offset `o`, lanes with
305305 // bit `o` clear keep the low half of the array and send the high half, the
306306 // others do the opposite. After log2(32) steps every lane holds a single
307307 // value, v[0], which is the warp total of element `ScatterIndex(N, lane)`
308308 // (or junk if that is -1). For N = 9 this takes 12 shuffles instead of the
309309 // 45 of nine independent butterflies, and the totals end in distinct lanes,
310310 // so writing them out is one instruction.
311311
312312 namespace internal {
313313 template <int N, int kOffset>
314314 GS_D void HalvingStep(float* v, int lane) {
315315 constexpr int kHalf = (N + 1) / 2;
316316 const bool upper = lane & kOffset;
317317 #pragma unroll
318318 for (int i = 0; i < kHalf; ++i) {
319319 const float lo = v[i];
320320 const float hi = i + kHalf < N ? v[i + kHalf] : 0.0f;
321321 const float send = upper ? lo : hi;
322322 const float keep = upper ? hi : lo;
323323 v[i] = keep + __shfl_xor_sync(kFullMask, send, kOffset);
324324 }
325325 }
326326
327327 template <int N, int kOffset>
328328 GS_D void ReduceScatterImpl(float* v, int lane) {
329329 if constexpr (kOffset > 0) {
330330 HalvingStep<N, kOffset>(v, lane);
331331 ReduceScatterImpl<(N + 1) / 2, kOffset / 2>(v, lane);
332332 }
333333 }
334334 } // namespace internal
335335
336336 template <int N>
337337 GS_D void ReduceScatter(float (&v)[N], int lane) {
338338 static_assert(N >= 1 && N <= 32);
339339 internal::ReduceScatterImpl<N, 16>(v, lane);
340340 }
341341
342342 template <int N>
343343 GS_D void ReduceScatter16(float (&v)[N], int lane) {
344344 static_assert(N >= 1 && N <= 16);
345345 internal::ReduceScatterImpl<N, 8>(v, lane);
346346 }
347347
348348 // Element whose warp total lane `lane` holds after `ReduceScatter<N>`, or -1.
349349 GS_HD constexpr int ScatterIndex(int n, int lane) {
350350 // Payload sizes and halves per step, offset 16 first.
351351 int sizes[5] = {};
352352 int size = n;
353353 for (int s = 0; s < 5; ++s) {
354354 sizes[s] = size;
355355 size = (size + 1) / 2;
356356 }
357357 int index = 0; // Slot in the final (size 1) array.
358358 for (int s = 4; s >= 0; --s) {
359359 const int half = (sizes[s] + 1) / 2;
360360 if (lane & (16 >> s)) index += half;
361361 if (index >= sizes[s]) return -1;
362362 }
363363 return index;
364364 }
365365
366366 // Element whose 16-lane half-warp total lane `lane` holds after
367367 // `ReduceScatter16<N>`, or -1.
368368 GS_HD constexpr int ScatterIndex16(int n, int lane) {
369369 const int half_lane = lane & 15;
370370 int sizes[4] = {};
371371 int size = n;
372372 for (int s = 0; s < 4; ++s) {
373373 sizes[s] = size;
374374 size = (size + 1) / 2;
375375 }
376376 int index = 0;
377377 for (int s = 3; s >= 0; --s) {
378378 const int half = (sizes[s] + 1) / 2;
379379 if (half_lane & (8 >> s)) index += half;
380380 if (index >= sizes[s]) return -1;
381381 }
382382 return index;
383383 }
384384
385385 } // namespace warp
386386
387387 inline constexpr int kShCoeffs = 16; // Degree 3.
388388 inline constexpr float kNearPlane = 0.2f;
389389 inline constexpr float kLowPass = 0.3f;
390390 inline constexpr float kFovClamp = 1.3f;
391391
392392 // Pointers to the trainable parameters, all activated, row-major.
393393 struct GaussianParams {
394394 const float* means; // (N, 3)
395395 const float* scales; // (N, 3)
396396 const float* quats; // (N, 4) as (w, x, y, z), not normalized.
397397 const float* opacities; // (N,)
398398 const void* sh; // (N, 16, 3) of `float` or `ShBf16` (see `ShIo`).
399399 int n;
400400 };
401401
402402 // Gradients w.r.t. `GaussianParams`, same layout and SH storage type.
403403 struct GaussianGrads {
404404 float* means;
405405 float* scales;
406406 float* quats;
407407 float* opacities;
408408 void* sh;
409409 };
410410
411411 // Screen-space quantities needed by rasterization. `conic` is the inverse 2D
412412 // covariance (a, b, c) with power = -0.5 (a dx^2 + c dy^2) - b dx dy.
413413 struct Projection {
414414 float2 xy;
415415 float depth;
416416 float3 conic;
417417 float opacity;
418418 float limit;
419419 float3 rgb;
420420 int radius; // 0 means culled.
421421 };
422422
423423 // ----------------------------------------------------------------------------
424424 // SH coefficient storage. A row holds the 16 x 3 coefficients of one Gaussian
425425 // and is accessed in 4 groups of 4 coefficients (12 values). The coefficients
426426 // may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32.
427427
428428 using ShBf16 = uint16_t; // bfloat16 bits.
429429
430430 template <typename T>
431431 struct ShIo;
432432
433433 template <>
434434 struct ShIo<float> {
435435 static constexpr int kRowVec16 = 12; // 16-byte vectors per row.
436436 GS_D static void LoadGroup(const float* row, int grp, float v[12]) {
437437 const float4* p = reinterpret_cast<const float4*>(row) + 3 * grp;
438438 const float4 a = __ldg(p), b = __ldg(p + 1), c = __ldg(p + 2);
439439 v[0] = a.x, v[1] = a.y, v[2] = a.z, v[3] = a.w;
440440 v[4] = b.x, v[5] = b.y, v[6] = b.z, v[7] = b.w;
441441 v[8] = c.x, v[9] = c.y, v[10] = c.z, v[11] = c.w;
442442 }
443443 GS_D static void StoreGroup(float* row, int grp, const float v[12]) {
444444 float4* p = reinterpret_cast<float4*>(row) + 3 * grp;
445445 p[0] = {v[0], v[1], v[2], v[3]};
446446 p[1] = {v[4], v[5], v[6], v[7]};
447447 p[2] = {v[8], v[9], v[10], v[11]};
448448 }
449449 };
450450
451451 template <>
452452 struct ShIo<ShBf16> {
453453 static constexpr int kRowVec16 = 6;
454454 GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); }
455455 GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); }
456456 // Round to nearest even (inputs are finite).
457457 GS_D static uint32_t Round(float f) {
458458 const uint32_t u = __float_as_uint(f);
459459 return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16;
460460 }
461461 GS_D static uint32_t Pack(float lo, float hi) {
462462 return Round(lo) | (Round(hi) << 16);
463463 }
464464 GS_D static void LoadGroup(const ShBf16* row, int grp, float v[12]) {
465465 // 24-byte groups: three 8-byte loads.
466466 const uint2* p = reinterpret_cast<const uint2*>(row) + 3 * grp;
467467 #pragma unroll
468468 for (int k = 0; k < 3; ++k) {
469469 const uint2 w = __ldg(p + k);
470470 v[4 * k] = Lo(w.x), v[4 * k + 1] = Hi(w.x);
471471 v[4 * k + 2] = Lo(w.y), v[4 * k + 3] = Hi(w.y);
472472 }
473473 }
474474 GS_D static void StoreGroup(ShBf16* row, int grp, const float v[12]) {
475475 uint2* p = reinterpret_cast<uint2*>(row) + 3 * grp;
476476 #pragma unroll
477477 for (int k = 0; k < 3; ++k) {
478478 p[k] = {Pack(v[4 * k], v[4 * k + 1]), Pack(v[4 * k + 2], v[4 * k + 3])};
479479 }
480480 }
481481 };
482482
483483 template <typename ShT>
484484 GS_D const ShT* ShRow(const void* sh, int i) {
485485 return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;
486486 }
487487 template <typename ShT>
488488 GS_D ShT* ShRow(void* sh, int i) {
489489 return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs;
490490 }
491491
492492 // ----------------------------------------------------------------------------
493493 // Spherical harmonics (degree 3, Inria sign convention).
494494
495495 namespace sh {
496496 inline constexpr float kC0 = 0.28209479177387814f;
497497 inline constexpr float kC1 = 0.4886025119029199f;
498498 inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f,
499499 0.31539156525252005f, -1.0925484305920792f,
500500 0.5462742152960396f};
501501 inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f,
502502 -0.4570457994644658f, 0.3731763325901154f,
503503 -0.4570457994644658f, 1.445305721320277f,
504504 -0.5900435899266435f};
505505
506506 // Basis values for a unit direction.
507507 GS_D void Basis(float3 d, float b[kShCoeffs]) {
508508 const float x = d.x, y = d.y, z = d.z;
509509 const float xx = x * x, yy = y * y, zz = z * z;
510510 b[0] = kC0;
511511 b[1] = -kC1 * y;
512512 b[2] = kC1 * z;
513513 b[3] = -kC1 * x;
514514 b[4] = kC2[0] * x * y;
515515 b[5] = kC2[1] * y * z;
516516 b[6] = kC2[2] * (2.0f * zz - xx - yy);
517517 b[7] = kC2[3] * x * z;
518518 b[8] = kC2[4] * (xx - yy);
519519 b[9] = kC3[0] * y * (3.0f * xx - yy);
520520 b[10] = kC3[1] * x * y * z;
521521 b[11] = kC3[2] * y * (4.0f * zz - xx - yy);
522522 b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy);
523523 b[13] = kC3[4] * x * (4.0f * zz - xx - yy);
524524 b[14] = kC3[5] * z * (xx - yy);
525525 b[15] = kC3[6] * x * (xx - 3.0f * yy);
526526 }
527527
528528 // Pre-clamp color 0.5 + sum_k b_k sh_k, reading directly from an SH row.
529529 template <typename ShT>
530530 GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) {
531531 float3 c = {0.5f, 0.5f, 0.5f};
532532 #pragma unroll
533533 for (int grp = 0; grp < 4; ++grp) {
534534 const int k0 = 4 * grp;
535535 float v[12];
536536 ShIo<ShT>::LoadGroup(sh_row, grp, v);
537537 c += b[k0] * float3{v[0], v[1], v[2]};
538538 c += b[k0 + 1] * float3{v[3], v[4], v[5]};
539539 c += b[k0 + 2] * float3{v[6], v[7], v[8]};
540540 c += b[k0 + 3] * float3{v[9], v[10], v[11]};
541541 }
542542 return c;
543543 }
544544 } // namespace sh
545545
546546 // ----------------------------------------------------------------------------
547547 // Covariance helpers.
548548
549549 // Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z).
550550 GS_D Mat3 QuatToRotation(float4 q) {
551551 const float w = q.x, x = q.y, y = q.z, z = q.w;
552552 Mat3 r;
553553 r.m[0][0] = 1.0f - 2.0f * (y * y + z * z);
554554 r.m[0][1] = 2.0f * (x * y - w * z);
555555 r.m[0][2] = 2.0f * (x * z + w * y);
556556 r.m[1][0] = 2.0f * (x * y + w * z);
557557 r.m[1][1] = 1.0f - 2.0f * (x * x + z * z);
558558 r.m[1][2] = 2.0f * (y * z - w * x);
559559 r.m[2][0] = 2.0f * (x * z - w * y);
560560 r.m[2][1] = 2.0f * (y * z + w * x);
561561 r.m[2][2] = 1.0f - 2.0f * (x * x + y * y);
562562 return r;
563563 }
564564
565565 // Symmetric 3x3 matrix.
566566 struct Sym3 {
567567 float xx, xy, xz, yy, yz, zz;
568568 GS_D float3 Mul(float3 v) const {
569569 return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z,
570570 xz * v.x + yz * v.y + zz * v.z};
571571 }
572572 };
573573
574574 // Sigma = A A^T with A = R diag(s).
575575 GS_D Sym3 Covariance3D(const Mat3& r, float3 s) {
576576 Mat3 a;
577577 const float sv[3] = {s.x, s.y, s.z};
578578 #pragma unroll
579579 for (int i = 0; i < 3; ++i)
580580 #pragma unroll
581581 for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j];
582582 return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)),
583583 dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)),
584584 dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))};
585585 }
586586
587587 // Intermediate values of the projection shared by forward and VJP.
588588 struct ProjectionState {
589589 float3 t; // View-space mean.
590590 float tx_c, ty_c; // Clamped x, y used by the Jacobian.
591591 bool x_clamped, y_clamped;
592592 float3 m0, m1; // Rows of M = J W (2x3).
593593 Sym3 sigma; // World-space 3D covariance.
594594 float a, b, c; // Dilated 2D covariance.
595595 };
596596
597597 // Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane).
598598 GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale,
599599 float4 quat, ProjectionState& st) {
600600 st.t = t;
601601 const float tz = t.z;
602602 const float limx = kFovClamp * cam.TanHalfFovX();
603603 const float limy = kFovClamp * cam.TanHalfFovY();
604604 const float txtz = t.x / tz, tytz = t.y / tz;
605605 st.x_clamped = txtz < -limx || txtz > limx;
606606 st.y_clamped = tytz < -limy || tytz > limy;
607607 st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;
608608 st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;
609609 const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz);
610610 const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz);
611611 const Mat3& w = cam.rotation;
612612 st.m0 = j00 * w.row(0) + j02 * w.row(2);
613613 st.m1 = j11 * w.row(1) + j12 * w.row(2);
614614 st.sigma = Covariance3D(QuatToRotation(quat), scale);
615615 const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1);
616616 st.a = dot(st.m0, s0) + kLowPass;
617617 st.b = dot(st.m0, s1);
618618 st.c = dot(st.m1, s1) + kLowPass;
619619 }
620620
621621 // Computes everything up to the 2D covariance. Returns false if culled by the
622622 // near plane.
623623 GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale,
624624 float4 quat, ProjectionState& st) {
625625 const float3 t = cam.ToView(mean);
626626 if (t.z <= kNearPlane) return false;
627627 ProjectCovarianceFromView(cam, t, scale, quat, st);
628628 return true;
629629 }
630630
631631 // Integer bounding radius (pixels) of the 2D covariance, as in the reference.
632632 GS_D int BoundingRadius(float a, float b, float c) {
633633 const float det = a * c - b * b;
634634 const float mid = 0.5f * (a + c);
635635 const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det));
636636 return static_cast<int>(ceilf(3.0f * sqrtf(lambda)));
637637 }
638638
639639 GS_D float2 ProjectMean(const Camera& cam, float3 t) {
640640 return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy};
641641 }
642642
643643 GS_D float3 LoadMean(const GaussianParams& p, int i) {
644644 return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]};
645645 }
646646
647647 // Unit direction from the camera center to `mean`, and 1 / distance.
648648 GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) {
649649 const float3 v = mean - cam.position;
650650 inv_len = rsqrtf(dot(v, v));
651651 return inv_len * v;
652652 }
653653
654654 // Gradient of the loss w.r.t. one splat's screen-space quantities.
655655 struct ProjectionGrad {
656656 float2 xy;
657657 float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`.
658658 float opacity;
659659 float3 rgb;
660660 };
661661
662662 // Image geometry passed by value to every kernel.
663663 struct Frame {
664664 const float* camera; // Device pointer to the packed camera.
665665 int width, height;
666666 TileGrid grid;
667667 GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); }
668668 };
669669
670670 // ---------------------------------------------------------------------------
671671 // Per-Gaussian kernels run one thread per Gaussian.
672672
673673 inline constexpr int kGaussianThreads = 128;
674674
675675 inline int GaussianBlocks(int64_t n) {
676676 return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads);
677677 }
678678
679−// Base-2 exponent scale: power2 = kPower2Scale * q = (-log2(e)/2) * q.
680−inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2
681−inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;
682−
683−GS_HD float3 Power2Conic(float a, float b, float c) {
684− return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};
685−}
686−
687−GS_D float Exp2Approx(float x) {
688− float y;
689− asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
690− return y;
691−}
692−
693679 // ---------------------------------------------------------------------------
694680 // Rasterization. One block per 16x16 tile. Each thread shades `kRows`
695681 // vertically adjacent pixels, so that per-splat costs (shared-memory loads,
696682 // barriers, the warp reduction in the backward pass) are paid once per
697683 // `kRows` pixels; more rows per thread also means fewer resident threads and
698684 // more waiting on the slowest pixel. A warp covers a compact
699685 // kWarpWidth x kWarpRows region (as square as possible, so per-warp culling
700686 // is tight); warps tile the 16x16 tile row-major. Splats are staged through
701687 // shared memory, one per thread per batch.
702688
703689 inline constexpr int kRasterRowChoices[] = {1, 2, 4};
690+
691+// Per-pixel splat evaluation shared by the forward and backward rasterizers.
692+// They must agree bit for bit on every alpha (the backward recovers
693+// transmittance by dividing alphas back out), so the rounding of every step
694+// is pinned with explicit `_rn` intrinsics.
695+//
696+// The falloff is evaluated in log2 units, power2 = A dx^2 + B dx dy + C dy^2
697+// with (A, B, C) = -log2(e)/2 (a, 2b, c), so that the Gaussian is 2^power2:
698+// two FMAs and one MUFU.EX2 per pixel. A pixel receives the splat iff
699+// power2 <= 0 and alpha >= kMinAlpha (the latter implies power2 >= the
700+// splat's culling bound, see `AlphaLimit`).
701+inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2
702+inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;
703+
704+GS_HD float3 Power2Conic(float a, float b, float c) {
705+ return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};
706+}
707+
708+// Splat terms of one pixel column at dx = splat.x - pixel.x.
709+struct Power2Column {
710+ float pa, pb; // A dx^2, B dx.
711+ GS_D static Power2Column At(float a2, float b2, float dx) {
712+ return {__fmul_rn(__fmul_rn(a2, dx), dx), __fmul_rn(b2, dx)};
713+ }
714+ GS_D float Row(float c2, float dy) const {
715+ return __fmaf_rn(__fmaf_rn(c2, dy, pb), dy, pa);
716+ }
717+};
718+
719+// 2^x and 1/x without the denormal range fix-ups of exp2f/__fdividef (their
720+// inputs and results here are never denormal where it matters).
721+GS_D float Exp2Approx(float x) {
722+ float y;
723+ asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
724+ return y;
725+}
726+GS_D float RcpApprox(float x) {
727+ float y;
728+ asm("rcp.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
729+ return y;
730+}
731+
732+GS_D float SplatAlpha(float opacity, float gaussian) {
733+ return fminf(kMaxAlpha, __fmul_rn(opacity, gaussian));
734+}
704735
705736 template <int kRows>
706737 struct RasterLayout {
707738 static_assert(kRows == 1 || kRows == 2 || kRows == 4);
↕ Expand 105 unchanged lines (lines 739–843) · in WarpMask
708739 static constexpr int kThreads = kTilePixels / kRows;
709740 static constexpr int kWarps = kThreads / 32;
710741 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;
711742 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;
712743 static constexpr int kWarpsX = kTileSize / kWarpWidth;
713744 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);
714745
715746 // Tile-relative origin of warp `w`'s region.
716747 GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; }
717748 GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; }
718749 };
719750
720751 // Pixel coordinates of this thread's first pixel; the others follow in y.
721752 struct RasterPixels {
722753 int x, y0;
723754 template <int kRows>
724755 GS_D static RasterPixels For(int tid) {
725756 using L = RasterLayout<kRows>;
726757 const int warp = tid / 32, lane = tid % 32;
727758 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +
728759 lane % L::kWarpWidth,
729760 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +
730761 (lane / L::kWarpWidth) * kRows};
731762 }
732763 };
733764
734765 // Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the
735766 // pixel region of warp w of this block's tile.
736767 template <int kRows>
737768 GS_D uint32_t WarpMask(float4 p0, float4 p1) {
738769 using L = RasterLayout<kRows>;
739770 constexpr int kWarpsY = L::kWarps / L::kWarpsX;
740771 float limit = p1.z;
741772 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;
742773 const float a = p0.z, b = p0.w, c = p1.x;
743774 const float det = a * c - b * b;
744775 if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0;
745776 const float l_over_det = limit / det;
746777 const float rx = sqrtf(c * l_over_det);
747778 const float ry = sqrtf(a * l_over_det);
748779 const float2 xy = {p0.x, p0.y};
749780 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));
750781 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));
751782 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));
752783 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));
753784 if (x0 > x1 || y0 > y1) return 0;
754785 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;
755786 uint32_t mx = 0u;
756787 #pragma unroll
757788 for (int wx = 0; wx < L::kWarpsX; ++wx) {
758789 const int wx0 = tx + wx * L::kWarpWidth;
759790 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);
760791 }
761792 if (mx == 0u) return 0;
762793 uint32_t my = 0u;
763794 #pragma unroll
764795 for (int wy = 0; wy < kWarpsY; ++wy) {
765796 const int wy0 = ty + wy * L::kWarpRows;
766797 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);
767798 }
768799 if (my == 0u) return 0;
769800 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {
770801 uint32_t mask = 0u;
771802 #pragma unroll
772803 for (int wy = 0; wy < kWarpsY; ++wy) {
773804 if (!((my >> wy) & 1u)) continue;
774805 #pragma unroll
775806 for (int wx = 0; wx < L::kWarpsX; ++wx) {
776807 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));
777808 }
778809 }
779810 return mask;
780811 }
781812 EllipseBounds eb;
782813 eb.a = a;
783814 eb.b = b;
784815 eb.c = c;
785816 eb.det = det;
786817 eb.inv_a = 1.0f / a;
787818 eb.a_limit = a * limit;
788819 eb.y_ext = ry;
789820 eb.dy_peak = b * sqrtf(l_over_det / c);
790821 uint32_t mask = 0u;
791822 #pragma unroll
792823 for (int wy = 0; wy < kWarpsY; ++wy) {
793824 if (!((my >> wy) & 1u)) continue;
794825 const int wy0 = ty + wy * L::kWarpRows;
795826 int x0_px, x1_px;
796827 if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) {
797828 continue;
798829 }
799830 #pragma unroll
800831 for (int wx = 0; wx < L::kWarpsX; ++wx) {
801832 const int wx0 = tx + wx * L::kWarpWidth;
802833 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {
803834 mask |= 1u << (wy * L::kWarpsX + wx);
804835 }
805836 }
806837 }
807838 return mask;
808839 }
809840
810841 } // namespace gs
811842
812843 #endif
csrc/forward.cu.cc +23 −26
s04_raster_pruning/csrc/forward.cu.cc
s05_branchless_predication/csrc/forward.cu.cc
↕ Expand 76 unchanged lines (lines 1–76) · in PreprocessKernel
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 "forward.h"12#include "forward.h"
1313
14#include "auxiliary.h"14#include "auxiliary.h"
1515
16namespace gs {16namespace gs {
1717
18// Screen-space geometry of Gaussian `i`: everything in `Projection` except18// Screen-space geometry of Gaussian `i`: everything in `Projection` except
19// `rgb`. `radius` = 0 marks a culled Gaussian (the caller also applies the19// `rgb`. `radius` = 0 marks a culled Gaussian (the caller also applies the
20// tile-rectangle cull).20// tile-rectangle cull).
21template <Culling kCulling>21template <Culling kCulling>
22GS_D Projection ProjectGeometry(const GaussianParams& p, const Camera& cam,22GS_D Projection ProjectGeometry(const GaussianParams& p, const Camera& cam,
23 float3 mean, int i) {23 float3 mean, int i) {
24 Projection out;24 Projection out;
25 out.radius = 0;25 out.radius = 0;
26 const float3 t = cam.ToView(mean);26 const float3 t = cam.ToView(mean);
27 if (t.z <= kNearPlane) return out;27 if (t.z <= kNearPlane) return out;
28 const float opacity = p.opacities[i];28 const float opacity = p.opacities[i];
29 float limit = 0.0f;29 float limit = 0.0f;
30 if (!AlphaLimit(opacity, limit)) {30 if (!AlphaLimit(opacity, limit)) {
31 if constexpr (kCulling == Culling::kEllipse) return out;31 if constexpr (kCulling == Culling::kEllipse) return out;
32 }32 }
33 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],33 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],
34 p.scales[3 * i + 2]};34 p.scales[3 * i + 2]};
35 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];35 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];
36 ProjectionState st;36 ProjectionState st;
37 ProjectCovarianceFromView(cam, t, scale, quat, st);37 ProjectCovarianceFromView(cam, t, scale, quat, st);
38 const float det = st.a * st.c - st.b * st.b;38 const float det = st.a * st.c - st.b * st.b;
39 if (det == 0.0f) return out;39 if (det == 0.0f) return out;
40 const float inv = 1.0f / det;40 const float inv = 1.0f / det;
41 out.conic = {st.c * inv, -st.b * inv, st.a * inv};41 out.conic = {st.c * inv, -st.b * inv, st.a * inv};
42 out.xy = ProjectMean(cam, st.t);42 out.xy = ProjectMean(cam, st.t);
43 out.depth = st.t.z;43 out.depth = st.t.z;
44 out.opacity = opacity;44 out.opacity = opacity;
45 out.limit = limit;45 out.limit = limit;
46 out.radius = BoundingRadius(st.a, st.b, st.c);46 out.radius = BoundingRadius(st.a, st.b, st.c);
47 return out;47 return out;
48}48}
4949
50// Color seen along `dir`, clamped at zero, reading from an SH row.50// Color seen along `dir`, clamped at zero, reading from an SH row.
51template <typename ShT>51template <typename ShT>
52GS_D float3 ShadeColorRow(const ShT* sh_row, float3 dir) {52GS_D float3 ShadeColorRow(const ShT* sh_row, float3 dir) {
53 float basis[kShCoeffs];53 float basis[kShCoeffs];
54 sh::Basis(dir, basis);54 sh::Basis(dir, basis);
55 const float3 raw = sh::EvalRow(sh_row, basis);55 const float3 raw = sh::EvalRow(sh_row, basis);
56 return {fmaxf(raw.x, 0.0f), fmaxf(raw.y, 0.0f), fmaxf(raw.z, 0.0f)};56 return {fmaxf(raw.x, 0.0f), fmaxf(raw.y, 0.0f), fmaxf(raw.z, 0.0f)};
57}57}
5858
59// ---------------------------------------------------------------------------59// ---------------------------------------------------------------------------
60// Preprocess: project every Gaussian, count its tiles via `ForEachTileRow`,60// Preprocess: project every Gaussian, count its tiles via `ForEachTileRow`,
61// evaluate SH only for visible Gaussians, and emit depth keys for the 32-bit61// evaluate SH only for visible Gaussians, and emit depth keys for the 32-bit
62// depth sort via per-thread `atomicAdd`.62// depth sort via per-thread `atomicAdd`.
6363
64template <Culling kCulling, typename ShT>64template <Culling kCulling, typename ShT>
65__global__ void __launch_bounds__(kGaussianThreads, 8)65__global__ void __launch_bounds__(kGaussianThreads, 8)
66 PreprocessKernel(GaussianParams params, Frame frame,66 PreprocessKernel(GaussianParams params, Frame frame,
67 Splat* __restrict__ splats,67 Splat* __restrict__ splats,
68 int32_t* __restrict__ tile_counts,68 int32_t* __restrict__ tile_counts,
69 uint32_t* __restrict__ depth_keys,69 uint32_t* __restrict__ depth_keys,
70 int32_t* __restrict__ ids,70 int32_t* __restrict__ ids,
71 int32_t* __restrict__ num_visible) {71 int32_t* __restrict__ num_visible) {
72 const int i = blockIdx.x * blockDim.x + threadIdx.x;72 const int i = blockIdx.x * blockDim.x + threadIdx.x;
73 if (i >= params.n) return;73 if (i >= params.n) return;
74 const Camera cam = frame.LoadCamera();74 const Camera cam = frame.LoadCamera();
75 // Geometry first: only Gaussians that reach a tile need their SH row.75 // Geometry first: only Gaussians that reach a tile need their SH row.
76 const float3 mean = LoadMean(params, i);76 const float3 mean = LoadMean(params, i);
77 const Projection pr = ProjectGeometry<kCulling>(params, cam, mean, i);77 const Projection pr = ProjectGeometry<kCulling>(params, cam, mean, i);
78 Splat s;78 Splat s;
79 int count = 0;79 int count = 0;
80 if (pr.radius > 0 && RectFor(pr.xy, pr.radius, frame.grid).area() > 0) {80 if (pr.radius > 0 && RectFor(pr.xy, pr.radius, frame.grid).area() > 0) {
81 const float min_power =
82 pr.limit > 0.0f ? (kPower2Scale * pr.limit - 1e-3f) : -8.01f;
83 s.p0 = {pr.xy.x, pr.xy.y, pr.conic.x, pr.conic.y};81 s.p0 = {pr.xy.x, pr.xy.y, pr.conic.x, pr.conic.y};
84 s.p1 = {pr.conic.z, pr.opacity, pr.limit, static_cast<float>(pr.radius)};82 s.p1 = {pr.conic.z, pr.opacity, pr.limit, static_cast<float>(pr.radius)};
85 s.p2 = {0.0f, 0.0f, 0.0f, min_power};83 s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};
86 ForEachTileRow<kCulling>(84 ForEachTileRow<kCulling>(
87 s, frame.grid, frame.width, frame.height,85 s, frame.grid, frame.width, frame.height,
88 [&](int, int tx0, int tx1) { count += tx1 - tx0; });86 [&](int, int tx0, int tx1) { count += tx1 - tx0; });
89 }87 }
↕ Expand 49 unchanged lines (lines 88–136) · in RasterForwardKernel
90 if (count == 0) {88 if (count == 0) {
91 s.p0 = s.p1 = s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};89 s.p0 = s.p1 = s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};
92 splats[i] = s;90 splats[i] = s;
93 tile_counts[i] = 0;91 tile_counts[i] = 0;
94 return;92 return;
95 }93 }
96 const int vis_idx = atomicAdd(num_visible, 1);94 const int vis_idx = atomicAdd(num_visible, 1);
97 float inv_len;95 float inv_len;
98 const float3 rgb =96 const float3 rgb =
99 ShadeColorRow(ShRow<ShT>(params.sh, i), ViewDir(cam, mean, inv_len));97 ShadeColorRow(ShRow<ShT>(params.sh, i), ViewDir(cam, mean, inv_len));
100 s.p2 = {rgb.x, rgb.y, rgb.z, s.p2.w};98 s.p2 = {rgb.x, rgb.y, rgb.z, s.p2.w};
101 splats[i] = s;99 splats[i] = s;
102 tile_counts[i] = count;100 tile_counts[i] = count;
103 depth_keys[vis_idx] = AsUint(pr.depth);101 depth_keys[vis_idx] = AsUint(pr.depth);
104 ids[vis_idx] = i;102 ids[vis_idx] = i;
105}103}
106104
107template <int kRows>105template <int kRows>
108__global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 8)106__global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 8)
109 RasterForwardKernel(const int2* __restrict__ ranges,107 RasterForwardKernel(const int2* __restrict__ ranges,
110 int32_t* __restrict__ point_list,108 int32_t* __restrict__ point_list,
111 const Splat* __restrict__ splats, Frame frame,109 const Splat* __restrict__ splats, Frame frame,
112 float* __restrict__ image, float* __restrict__ final_t,110 float* __restrict__ image, float* __restrict__ final_t,
113 int32_t* __restrict__ n_contrib) {111 int32_t* __restrict__ n_contrib) {
114 constexpr int kRasterRows = kRows;112 constexpr int kRasterRows = kRows;
115 constexpr int kRasterThreads = RasterLayout<kRows>::kThreads;113 constexpr int kRasterThreads = RasterLayout<kRows>::kThreads;
116 constexpr int kWarps = RasterLayout<kRows>::kWarps;114 constexpr int kWarps = RasterLayout<kRows>::kWarps;
117 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;115 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
118 const int tid = threadIdx.x;116 const int tid = threadIdx.x;
119 const int warp = tid / 32;117 const int warp = tid / 32;
120 const int lane = tid % 32;118 const int lane = tid % 32;
121 const RasterPixels px = RasterPixels::For<kRows>(tid);119 const RasterPixels px = RasterPixels::For<kRows>(tid);
122 const float fx = static_cast<float>(px.x);120 const float fx = static_cast<float>(px.x);
123 const int2 range = ranges[tile];121 const int2 range = ranges[tile];
124122
125 float t[kRasterRows], r[kRasterRows], g[kRasterRows], b[kRasterRows];123 float t[kRasterRows], r[kRasterRows], g[kRasterRows], b[kRasterRows];
126 int last[kRasterRows];124 int last[kRasterRows];
127 bool done[kRasterRows];125 bool done[kRasterRows];
128#pragma unroll126#pragma unroll
129 for (int k = 0; k < kRasterRows; ++k) {127 for (int k = 0; k < kRasterRows; ++k) {
130 t[k] = 1.0f;128 t[k] = 1.0f;
131 r[k] = g[k] = b[k] = 0.0f;129 r[k] = g[k] = b[k] = 0.0f;
132 last[k] = 0;130 last[k] = 0;
133 done[k] = px.x >= frame.width || px.y0 + k >= frame.height;131 done[k] = px.x >= frame.width || px.y0 + k >= frame.height;
134 }132 }
135 auto all_done = [&] {133 auto all_done = [&] {
136 bool d = true;134 bool d = true;
137#pragma unroll135#pragma unroll
138 for (int k = 0; k < kRasterRows; ++k) d = d && done[k];136 for (int k = 0; k < kRasterRows; ++k) d = d && done[k];
139 return d;137 return d;
140 };138 };
141139
142 __shared__ float4 batch_geom0[kRasterThreads];140 __shared__ float4 batch_geom0[kRasterThreads];
143 __shared__ float2 batch_geom1[kRasterThreads];141 __shared__ float batch_geom1[kRasterThreads];
144 __shared__ float4 batch_rgb_o[kRasterThreads];142 __shared__ float4 batch_rgb_o[kRasterThreads];
145 __shared__ uint8_t batch_mask[kRasterThreads];143 __shared__ uint8_t batch_mask[kRasterThreads];
146 __shared__ uint32_t warp_contrib[kWarps][kWarps];144 __shared__ uint32_t warp_contrib[kWarps][kWarps];
147 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);145 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
↕ Expand 8 unchanged lines (lines 146–153) · in RasterForwardKernel
148 bool thread_done = all_done();146 bool thread_done = all_done();
149 int tile_compact = 0;147 int tile_compact = 0;
150 for (int start = range.x; start < range.y; start += kRasterThreads) {148 for (int start = range.x; start < range.y; start += kRasterThreads) {
151 const int idx = start + tid;149 const int idx = start + tid;
152 int32_t id = 0;150 int32_t id = 0;
153 if (idx < range.y) {151 if (idx < range.y) {
154 id = point_list[idx];152 id = point_list[idx];
155 const float4 p0 = __ldg(&splats[id].p0);153 const float4 p0 = __ldg(&splats[id].p0);
156 const float4 p1 = __ldg(&splats[id].p1);154 const float4 p1 = __ldg(&splats[id].p1);
157 const float4 p2 = __ldg(&splats[id].p2);155 const float4 p2 = __ldg(&splats[id].p2);
158 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);156 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
159 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};157 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
160 batch_geom1[tid] = {c2.z, p2.w};158 batch_geom1[tid] = c2.z;
161 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};159 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};
162 batch_mask[tid] = static_cast<uint8_t>(WarpMask<kRows>(p0, p1));160 batch_mask[tid] = static_cast<uint8_t>(WarpMask<kRows>(p0, p1));
163 } else {161 } else {
164 batch_mask[tid] = 0u;162 batch_mask[tid] = 0u;
↕ Expand 12 unchanged lines (lines 163–174) · in RasterForwardKernel
165 }163 }
166 __syncthreads();164 __syncthreads();
167 const int count = min(kRasterThreads, range.y - start);165 const int count = min(kRasterThreads, range.y - start);
168 bool warp_done = __all_sync(warp::kFullMask, thread_done);166 bool warp_done = __all_sync(warp::kFullMask, thread_done);
169#pragma unroll167#pragma unroll
170 for (int c = 0; c < kRasterThreads; c += 32) {168 for (int c = 0; c < kRasterThreads; c += 32) {
171 uint32_t contrib = 0u;169 uint32_t contrib = 0u;
172 if (c < count && !warp_done) {170 if (c < count && !warp_done) {
173 uint32_t mask = __ballot_sync(171 uint32_t mask = __ballot_sync(
174 warp::kFullMask,172 warp::kFullMask,
175 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);173 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
176 while (mask != 0u) {174 while (mask != 0u) {
177 const int bit = __ffs(mask) - 1;175 const int bit = __ffs(mask) - 1;
178 mask &= mask - 1u;176 mask &= mask - 1u;
179 const int j = c + bit;177 const int j = c + bit;
180 bool blended = false;178 bool blended = false;
181 if (!thread_done) {
179 {
180 // Predicated like the backward pass: rows that are done or not
181 // reached get alpha = 0 and keep their state.
182 const float4 g0 = batch_geom0[j];182 const float4 g0 = batch_geom0[j];
183 const float2 g1 = batch_geom1[j];183 const float c2 = batch_geom1[j];
184 const float dx = g0.x - fx;184 const float4 ro = batch_rgb_o[j];
185 const float cx_dx2 = g0.z * dx * dx;
186 const float cy_dx = g0.w * dx;
185 const Power2Column col =
186 Power2Column::At(g0.z, g0.w, __fsub_rn(g0.x, fx));
187#pragma unroll187#pragma unroll
188 for (int k = 0; k < kRasterRows; ++k) {188 for (int k = 0; k < kRasterRows; ++k) {
189 if (done[k]) continue;
190 const float dy = g0.y - static_cast<float>(px.y0 + k);189 const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));
191 const float power = cx_dx2 + g1.x * dy * dy + cy_dx * dy;190 const float power = col.Row(c2, dy);
192 if (power > 0.0f || power < g1.y) continue;
193 const float4 ro = batch_rgb_o[j];
194 const float alpha = fminf(kMaxAlpha, ro.w * Exp2Approx(power));191 const float alpha_k = SplatAlpha(ro.w, Exp2Approx(power));
192 const bool hit =
195 if (alpha < kMinAlpha) continue;193 !done[k] && power <= 0.0f && alpha_k >= kMinAlpha;
196 const float next_t = t[k] * (1.0f - alpha);194 const float next_t = t[k] * (1.0f - alpha_k);
197 if (next_t < kMinTransmittance) {195 const bool stop = hit && next_t < kMinTransmittance;
196 const bool blend = hit && !stop;
198 done[k] = true;197 done[k] = done[k] || stop;
199 continue;
200 }
201 const float w = alpha * t[k];198 const float w = blend ? alpha_k * t[k] : 0.0f;
202 r[k] += w * ro.x;199 r[k] += w * ro.x;
203 g[k] += w * ro.y;200 g[k] += w * ro.y;
204 b[k] += w * ro.z;201 b[k] += w * ro.z;
205 t[k] = next_t;202 t[k] = blend ? next_t : t[k];
206 last[k] = -1 - j;203 last[k] = blend ? -1 - j : last[k];
207 blended = true;204 blended = blended || blend;
208 }205 }
209 thread_done = all_done();206 thread_done = all_done();
210 }207 }
211 if (__any_sync(warp::kFullMask, blended)) {208 if (__any_sync(warp::kFullMask, blended)) {
↕ Expand 91 unchanged lines (lines 209–299) · in RasterForwardKernel
212 contrib |= (1u << bit);209 contrib |= (1u << bit);
213 }210 }
214 if (__all_sync(warp::kFullMask, thread_done)) {211 if (__all_sync(warp::kFullMask, thread_done)) {
215 warp_done = true;212 warp_done = true;
216 break;213 break;
217 }214 }
218 }215 }
219 warp_done = __all_sync(warp::kFullMask, thread_done);216 warp_done = __all_sync(warp::kFullMask, thread_done);
220 }217 }
221 if (lane == 0) {218 if (lane == 0) {
222 warp_contrib[warp][c / 32] = contrib;219 warp_contrib[warp][c / 32] = contrib;
223 }220 }
224 }221 }
225 const bool block_done =222 const bool block_done =
226 (__syncthreads_count(thread_done) == kRasterThreads);223 (__syncthreads_count(thread_done) == kRasterThreads);
227 uint32_t chunk_any[kWarps];224 uint32_t chunk_any[kWarps];
228 int chunk_prefix[kWarps];225 int chunk_prefix[kWarps];
229 int batch_active = 0;226 int batch_active = 0;
230#pragma unroll227#pragma unroll
231 for (int u = 0; u < kWarps; ++u) {228 for (int u = 0; u < kWarps; ++u) {
232 uint32_t any_u = 0u;229 uint32_t any_u = 0u;
233#pragma unroll230#pragma unroll
234 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {231 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {
235 any_u |= warp_contrib[w_idx][u];232 any_u |= warp_contrib[w_idx][u];
236 }233 }
237 chunk_any[u] = any_u;234 chunk_any[u] = any_u;
238 chunk_prefix[u] = tile_compact + batch_active;235 chunk_prefix[u] = tile_compact + batch_active;
239 batch_active += __popc(any_u);236 batch_active += __popc(any_u);
240 }237 }
241 if ((chunk_any[warp] >> lane) & 1u) {238 if ((chunk_any[warp] >> lane) & 1u) {
242 uint32_t cmask = 0u;239 uint32_t cmask = 0u;
243#pragma unroll240#pragma unroll
244 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {241 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {
245 cmask |= ((warp_contrib[w_idx][warp] >> lane) & 1u) << w_idx;242 cmask |= ((warp_contrib[w_idx][warp] >> lane) & 1u) << w_idx;
246 }243 }
247 const int dst =244 const int dst =
248 chunk_prefix[warp] + __popc(chunk_any[warp] & ((1u << lane) - 1u));245 chunk_prefix[warp] + __popc(chunk_any[warp] & ((1u << lane) - 1u));
249 point_list[range.x + dst] =246 point_list[range.x + dst] =
250 static_cast<int32_t>((cmask << 24) | static_cast<uint32_t>(id));247 static_cast<int32_t>((cmask << 24) | static_cast<uint32_t>(id));
251 }248 }
252#pragma unroll249#pragma unroll
253 for (int k = 0; k < kRasterRows; ++k) {250 for (int k = 0; k < kRasterRows; ++k) {
254 if (last[k] < 0) {251 if (last[k] < 0) {
255 const int j = -1 - last[k];252 const int j = -1 - last[k];
256 const int u = j >> 5;253 const int u = j >> 5;
257 const int b_idx = j & 31;254 const int b_idx = j & 31;
258 last[k] =255 last[k] =
259 chunk_prefix[u] + __popc(chunk_any[u] & ((1u << b_idx) - 1u)) + 1;256 chunk_prefix[u] + __popc(chunk_any[u] & ((1u << b_idx) - 1u)) + 1;
260 }257 }
261 }258 }
262 tile_compact += batch_active;259 tile_compact += batch_active;
263 if (block_done) break;260 if (block_done) break;
264 }261 }
265 const int pixels = frame.width * frame.height;262 const int pixels = frame.width * frame.height;
266#pragma unroll263#pragma unroll
267 for (int k = 0; k < kRasterRows; ++k) {264 for (int k = 0; k < kRasterRows; ++k) {
268 const int y = px.y0 + k;265 const int y = px.y0 + k;
269 if (px.x >= frame.width || y >= frame.height) continue;266 if (px.x >= frame.width || y >= frame.height) continue;
270 const int p = y * frame.width + px.x;267 const int p = y * frame.width + px.x;
271 image[0 * pixels + p] = r[k];268 image[0 * pixels + p] = r[k];
272 image[1 * pixels + p] = g[k];269 image[1 * pixels + p] = g[k];
273 image[2 * pixels + p] = b[k];270 image[2 * pixels + p] = b[k];
274 final_t[p] = t[k];271 final_t[p] = t[k];
275 n_contrib[p] = last[k];272 n_contrib[p] = last[k];
276 }273 }
277}274}
278275
279template __global__ void PreprocessKernel<Culling::kRect, float>(276template __global__ void PreprocessKernel<Culling::kRect, float>(
280 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,277 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
281 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);278 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
282template __global__ void PreprocessKernel<Culling::kRect, ShBf16>(279template __global__ void PreprocessKernel<Culling::kRect, ShBf16>(
283 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,280 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
284 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);281 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
285template __global__ void PreprocessKernel<Culling::kEllipse, float>(282template __global__ void PreprocessKernel<Culling::kEllipse, float>(
286 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,283 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
287 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);284 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
288template __global__ void PreprocessKernel<Culling::kEllipse, ShBf16>(285template __global__ void PreprocessKernel<Culling::kEllipse, ShBf16>(
289 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,286 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
290 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);287 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
291288
292template __global__ void RasterForwardKernel<1>(289template __global__ void RasterForwardKernel<1>(
293 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,290 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
294 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);291 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
295template __global__ void RasterForwardKernel<2>(292template __global__ void RasterForwardKernel<2>(
296 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,293 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
297 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);294 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
298template __global__ void RasterForwardKernel<4>(295template __global__ void RasterForwardKernel<4>(
299 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,296 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
300 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);297 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
301298
302} // namespace gs299} // namespace gs
↕ Expand 76 unchanged lines (lines 1–76) · in PreprocessKernel
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 "forward.h"
1313
1414 #include "auxiliary.h"
1515
1616 namespace gs {
1717
1818 // Screen-space geometry of Gaussian `i`: everything in `Projection` except
1919 // `rgb`. `radius` = 0 marks a culled Gaussian (the caller also applies the
2020 // tile-rectangle cull).
2121 template <Culling kCulling>
2222 GS_D Projection ProjectGeometry(const GaussianParams& p, const Camera& cam,
2323 float3 mean, int i) {
2424 Projection out;
2525 out.radius = 0;
2626 const float3 t = cam.ToView(mean);
2727 if (t.z <= kNearPlane) return out;
2828 const float opacity = p.opacities[i];
2929 float limit = 0.0f;
3030 if (!AlphaLimit(opacity, limit)) {
3131 if constexpr (kCulling == Culling::kEllipse) return out;
3232 }
3333 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],
3434 p.scales[3 * i + 2]};
3535 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];
3636 ProjectionState st;
3737 ProjectCovarianceFromView(cam, t, scale, quat, st);
3838 const float det = st.a * st.c - st.b * st.b;
3939 if (det == 0.0f) return out;
4040 const float inv = 1.0f / det;
4141 out.conic = {st.c * inv, -st.b * inv, st.a * inv};
4242 out.xy = ProjectMean(cam, st.t);
4343 out.depth = st.t.z;
4444 out.opacity = opacity;
4545 out.limit = limit;
4646 out.radius = BoundingRadius(st.a, st.b, st.c);
4747 return out;
4848 }
4949
5050 // Color seen along `dir`, clamped at zero, reading from an SH row.
5151 template <typename ShT>
5252 GS_D float3 ShadeColorRow(const ShT* sh_row, float3 dir) {
5353 float basis[kShCoeffs];
5454 sh::Basis(dir, basis);
5555 const float3 raw = sh::EvalRow(sh_row, basis);
5656 return {fmaxf(raw.x, 0.0f), fmaxf(raw.y, 0.0f), fmaxf(raw.z, 0.0f)};
5757 }
5858
5959 // ---------------------------------------------------------------------------
6060 // Preprocess: project every Gaussian, count its tiles via `ForEachTileRow`,
6161 // evaluate SH only for visible Gaussians, and emit depth keys for the 32-bit
6262 // depth sort via per-thread `atomicAdd`.
6363
6464 template <Culling kCulling, typename ShT>
6565 __global__ void __launch_bounds__(kGaussianThreads, 8)
6666 PreprocessKernel(GaussianParams params, Frame frame,
6767 Splat* __restrict__ splats,
6868 int32_t* __restrict__ tile_counts,
6969 uint32_t* __restrict__ depth_keys,
7070 int32_t* __restrict__ ids,
7171 int32_t* __restrict__ num_visible) {
7272 const int i = blockIdx.x * blockDim.x + threadIdx.x;
7373 if (i >= params.n) return;
7474 const Camera cam = frame.LoadCamera();
7575 // Geometry first: only Gaussians that reach a tile need their SH row.
7676 const float3 mean = LoadMean(params, i);
7777 const Projection pr = ProjectGeometry<kCulling>(params, cam, mean, i);
7878 Splat s;
7979 int count = 0;
8080 if (pr.radius > 0 && RectFor(pr.xy, pr.radius, frame.grid).area() > 0) {
81− const float min_power =
82− pr.limit > 0.0f ? (kPower2Scale * pr.limit - 1e-3f) : -8.01f;
8381 s.p0 = {pr.xy.x, pr.xy.y, pr.conic.x, pr.conic.y};
8482 s.p1 = {pr.conic.z, pr.opacity, pr.limit, static_cast<float>(pr.radius)};
85− s.p2 = {0.0f, 0.0f, 0.0f, min_power};
83+ s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};
8684 ForEachTileRow<kCulling>(
8785 s, frame.grid, frame.width, frame.height,
8886 [&](int, int tx0, int tx1) { count += tx1 - tx0; });
8987 }
↕ Expand 49 unchanged lines (lines 88–136) · in RasterForwardKernel
9088 if (count == 0) {
9189 s.p0 = s.p1 = s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};
9290 splats[i] = s;
9391 tile_counts[i] = 0;
9492 return;
9593 }
9694 const int vis_idx = atomicAdd(num_visible, 1);
9795 float inv_len;
9896 const float3 rgb =
9997 ShadeColorRow(ShRow<ShT>(params.sh, i), ViewDir(cam, mean, inv_len));
10098 s.p2 = {rgb.x, rgb.y, rgb.z, s.p2.w};
10199 splats[i] = s;
102100 tile_counts[i] = count;
103101 depth_keys[vis_idx] = AsUint(pr.depth);
104102 ids[vis_idx] = i;
105103 }
106104
107105 template <int kRows>
108106 __global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 8)
109107 RasterForwardKernel(const int2* __restrict__ ranges,
110108 int32_t* __restrict__ point_list,
111109 const Splat* __restrict__ splats, Frame frame,
112110 float* __restrict__ image, float* __restrict__ final_t,
113111 int32_t* __restrict__ n_contrib) {
114112 constexpr int kRasterRows = kRows;
115113 constexpr int kRasterThreads = RasterLayout<kRows>::kThreads;
116114 constexpr int kWarps = RasterLayout<kRows>::kWarps;
117115 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
118116 const int tid = threadIdx.x;
119117 const int warp = tid / 32;
120118 const int lane = tid % 32;
121119 const RasterPixels px = RasterPixels::For<kRows>(tid);
122120 const float fx = static_cast<float>(px.x);
123121 const int2 range = ranges[tile];
124122
125123 float t[kRasterRows], r[kRasterRows], g[kRasterRows], b[kRasterRows];
126124 int last[kRasterRows];
127125 bool done[kRasterRows];
128126 #pragma unroll
129127 for (int k = 0; k < kRasterRows; ++k) {
130128 t[k] = 1.0f;
131129 r[k] = g[k] = b[k] = 0.0f;
132130 last[k] = 0;
133131 done[k] = px.x >= frame.width || px.y0 + k >= frame.height;
134132 }
135133 auto all_done = [&] {
136134 bool d = true;
137135 #pragma unroll
138136 for (int k = 0; k < kRasterRows; ++k) d = d && done[k];
139137 return d;
140138 };
141139
142140 __shared__ float4 batch_geom0[kRasterThreads];
143− __shared__ float2 batch_geom1[kRasterThreads];
141+ __shared__ float batch_geom1[kRasterThreads];
144142 __shared__ float4 batch_rgb_o[kRasterThreads];
145143 __shared__ uint8_t batch_mask[kRasterThreads];
146144 __shared__ uint32_t warp_contrib[kWarps][kWarps];
147145 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
↕ Expand 8 unchanged lines (lines 146–153) · in RasterForwardKernel
148146 bool thread_done = all_done();
149147 int tile_compact = 0;
150148 for (int start = range.x; start < range.y; start += kRasterThreads) {
151149 const int idx = start + tid;
152150 int32_t id = 0;
153151 if (idx < range.y) {
154152 id = point_list[idx];
155153 const float4 p0 = __ldg(&splats[id].p0);
156154 const float4 p1 = __ldg(&splats[id].p1);
157155 const float4 p2 = __ldg(&splats[id].p2);
158156 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
159157 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
160− batch_geom1[tid] = {c2.z, p2.w};
158+ batch_geom1[tid] = c2.z;
161159 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};
162160 batch_mask[tid] = static_cast<uint8_t>(WarpMask<kRows>(p0, p1));
163161 } else {
164162 batch_mask[tid] = 0u;
↕ Expand 12 unchanged lines (lines 163–174) · in RasterForwardKernel
165163 }
166164 __syncthreads();
167165 const int count = min(kRasterThreads, range.y - start);
168166 bool warp_done = __all_sync(warp::kFullMask, thread_done);
169167 #pragma unroll
170168 for (int c = 0; c < kRasterThreads; c += 32) {
171169 uint32_t contrib = 0u;
172170 if (c < count && !warp_done) {
173171 uint32_t mask = __ballot_sync(
174172 warp::kFullMask,
175173 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
176174 while (mask != 0u) {
177175 const int bit = __ffs(mask) - 1;
178176 mask &= mask - 1u;
179177 const int j = c + bit;
180178 bool blended = false;
181− if (!thread_done) {
179+ {
180+ // Predicated like the backward pass: rows that are done or not
181+ // reached get alpha = 0 and keep their state.
182182 const float4 g0 = batch_geom0[j];
183− const float2 g1 = batch_geom1[j];
183+ const float c2 = batch_geom1[j];
184− const float dx = g0.x - fx;
184+ const float4 ro = batch_rgb_o[j];
185− const float cx_dx2 = g0.z * dx * dx;
186− const float cy_dx = g0.w * dx;
185+ const Power2Column col =
186+ Power2Column::At(g0.z, g0.w, __fsub_rn(g0.x, fx));
187187 #pragma unroll
188188 for (int k = 0; k < kRasterRows; ++k) {
189− if (done[k]) continue;
190− const float dy = g0.y - static_cast<float>(px.y0 + k);
189+ const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));
191− const float power = cx_dx2 + g1.x * dy * dy + cy_dx * dy;
190+ const float power = col.Row(c2, dy);
192− if (power > 0.0f || power < g1.y) continue;
193− const float4 ro = batch_rgb_o[j];
194− const float alpha = fminf(kMaxAlpha, ro.w * Exp2Approx(power));
191+ const float alpha_k = SplatAlpha(ro.w, Exp2Approx(power));
192+ const bool hit =
195− if (alpha < kMinAlpha) continue;
193+ !done[k] && power <= 0.0f && alpha_k >= kMinAlpha;
196− const float next_t = t[k] * (1.0f - alpha);
194+ const float next_t = t[k] * (1.0f - alpha_k);
197− if (next_t < kMinTransmittance) {
195+ const bool stop = hit && next_t < kMinTransmittance;
196+ const bool blend = hit && !stop;
198− done[k] = true;
197+ done[k] = done[k] || stop;
199− continue;
200− }
201− const float w = alpha * t[k];
198+ const float w = blend ? alpha_k * t[k] : 0.0f;
202199 r[k] += w * ro.x;
203200 g[k] += w * ro.y;
204201 b[k] += w * ro.z;
205− t[k] = next_t;
202+ t[k] = blend ? next_t : t[k];
206− last[k] = -1 - j;
203+ last[k] = blend ? -1 - j : last[k];
207− blended = true;
204+ blended = blended || blend;
208205 }
209206 thread_done = all_done();
210207 }
211208 if (__any_sync(warp::kFullMask, blended)) {
↕ Expand 91 unchanged lines (lines 209–299) · in RasterForwardKernel
212209 contrib |= (1u << bit);
213210 }
214211 if (__all_sync(warp::kFullMask, thread_done)) {
215212 warp_done = true;
216213 break;
217214 }
218215 }
219216 warp_done = __all_sync(warp::kFullMask, thread_done);
220217 }
221218 if (lane == 0) {
222219 warp_contrib[warp][c / 32] = contrib;
223220 }
224221 }
225222 const bool block_done =
226223 (__syncthreads_count(thread_done) == kRasterThreads);
227224 uint32_t chunk_any[kWarps];
228225 int chunk_prefix[kWarps];
229226 int batch_active = 0;
230227 #pragma unroll
231228 for (int u = 0; u < kWarps; ++u) {
232229 uint32_t any_u = 0u;
233230 #pragma unroll
234231 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {
235232 any_u |= warp_contrib[w_idx][u];
236233 }
237234 chunk_any[u] = any_u;
238235 chunk_prefix[u] = tile_compact + batch_active;
239236 batch_active += __popc(any_u);
240237 }
241238 if ((chunk_any[warp] >> lane) & 1u) {
242239 uint32_t cmask = 0u;
243240 #pragma unroll
244241 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {
245242 cmask |= ((warp_contrib[w_idx][warp] >> lane) & 1u) << w_idx;
246243 }
247244 const int dst =
248245 chunk_prefix[warp] + __popc(chunk_any[warp] & ((1u << lane) - 1u));
249246 point_list[range.x + dst] =
250247 static_cast<int32_t>((cmask << 24) | static_cast<uint32_t>(id));
251248 }
252249 #pragma unroll
253250 for (int k = 0; k < kRasterRows; ++k) {
254251 if (last[k] < 0) {
255252 const int j = -1 - last[k];
256253 const int u = j >> 5;
257254 const int b_idx = j & 31;
258255 last[k] =
259256 chunk_prefix[u] + __popc(chunk_any[u] & ((1u << b_idx) - 1u)) + 1;
260257 }
261258 }
262259 tile_compact += batch_active;
263260 if (block_done) break;
264261 }
265262 const int pixels = frame.width * frame.height;
266263 #pragma unroll
267264 for (int k = 0; k < kRasterRows; ++k) {
268265 const int y = px.y0 + k;
269266 if (px.x >= frame.width || y >= frame.height) continue;
270267 const int p = y * frame.width + px.x;
271268 image[0 * pixels + p] = r[k];
272269 image[1 * pixels + p] = g[k];
273270 image[2 * pixels + p] = b[k];
274271 final_t[p] = t[k];
275272 n_contrib[p] = last[k];
276273 }
277274 }
278275
279276 template __global__ void PreprocessKernel<Culling::kRect, float>(
280277 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
281278 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
282279 template __global__ void PreprocessKernel<Culling::kRect, ShBf16>(
283280 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
284281 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
285282 template __global__ void PreprocessKernel<Culling::kEllipse, float>(
286283 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
287284 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
288285 template __global__ void PreprocessKernel<Culling::kEllipse, ShBf16>(
289286 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
290287 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
291288
292289 template __global__ void RasterForwardKernel<1>(
293290 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
294291 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
295292 template __global__ void RasterForwardKernel<2>(
296293 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
297294 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
298295 template __global__ void RasterForwardKernel<4>(
299296 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
300297 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
301298
302299 } // namespace gs
csrc/backward.cu.cc +32 −30
s04_raster_pruning/csrc/backward.cu.cc
s05_branchless_predication/csrc/backward.cu.cc
↕ Expand 256 unchanged lines (lines 1–256) · in PixelState
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 reading the row `sh_in` and writing dL/dsh directly19// Evaluates SH color VJP reading the row `sh_in` and writing dL/dsh directly
20// to the row `sh_out` (same storage type), group by group.20// to the row `sh_out` (same storage type), group by group.
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 const float3 raw = EvalRow(sh_in, b);26 const float3 raw = EvalRow(sh_in, b);
27 const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x,27 const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x,
28 raw.y < 0.0f ? 0.0f : g_rgb.y,28 raw.y < 0.0f ? 0.0f : g_rgb.y,
29 raw.z < 0.0f ? 0.0f : g_rgb.z};29 raw.z < 0.0f ? 0.0f : g_rgb.z};
30 const float x = d.x, y = d.y, z = d.z;30 const float x = d.x, y = d.y, z = d.z;
31 const float xx = x * x, yy = y * y, zz = z * z;31 const float xx = x * x, yy = y * y, zz = z * z;
32 const float xy = x * y, xz = x * z, yz = y * z;32 const float xy = x * y, xz = x * z, yz = y * z;
33 float3 ddir = {0.0f, 0.0f, 0.0f};33 float3 ddir = {0.0f, 0.0f, 0.0f};
34#pragma unroll34#pragma unroll
35 for (int grp = 0; grp < 4; ++grp) {35 for (int grp = 0; grp < 4; ++grp) {
36 const int k0 = 4 * grp;36 const int k0 = 4 * grp;
37 float v[12];37 float v[12];
38 ShIo<ShT>::LoadGroup(sh_in, grp, v);38 ShIo<ShT>::LoadGroup(sh_in, grp, v);
39 const float s0 = v[0] * drgb.x + v[1] * drgb.y + v[2] * drgb.z;39 const float s0 = v[0] * drgb.x + v[1] * drgb.y + v[2] * drgb.z;
40 const float s1 = v[3] * drgb.x + v[4] * drgb.y + v[5] * drgb.z;40 const float s1 = v[3] * drgb.x + v[4] * drgb.y + v[5] * drgb.z;
41 const float s2 = v[6] * drgb.x + v[7] * drgb.y + v[8] * drgb.z;41 const float s2 = v[6] * drgb.x + v[7] * drgb.y + v[8] * drgb.z;
42 const float s3 = v[9] * drgb.x + v[10] * drgb.y + v[11] * drgb.z;42 const float s3 = v[9] * drgb.x + v[10] * drgb.y + v[11] * drgb.z;
43 float out[12];43 float out[12];
44#pragma unroll44#pragma unroll
45 for (int q = 0; q < 4; ++q) {45 for (int q = 0; q < 4; ++q) {
46 out[3 * q] = b[k0 + q] * drgb.x;46 out[3 * q] = b[k0 + q] * drgb.x;
47 out[3 * q + 1] = b[k0 + q] * drgb.y;47 out[3 * q + 1] = b[k0 + q] * drgb.y;
48 out[3 * q + 2] = b[k0 + q] * drgb.z;48 out[3 * q + 2] = b[k0 + q] * drgb.z;
49 }49 }
50 ShIo<ShT>::StoreGroup(sh_out, grp, out);50 ShIo<ShT>::StoreGroup(sh_out, grp, out);
51 if (grp == 0) {51 if (grp == 0) {
52 ddir.y += s1 * (-kC1);52 ddir.y += s1 * (-kC1);
53 ddir.z += s2 * kC1;53 ddir.z += s2 * kC1;
54 ddir.x += s3 * (-kC1);54 ddir.x += s3 * (-kC1);
55 } else if (grp == 1) {55 } else if (grp == 1) {
56 ddir += (s0 * kC2[0]) * float3{y, x, 0.0f};56 ddir += (s0 * kC2[0]) * float3{y, x, 0.0f};
57 ddir += (s1 * kC2[1]) * float3{0.0f, z, y};57 ddir += (s1 * kC2[1]) * float3{0.0f, z, y};
58 ddir += (s2 * kC2[2]) * float3{-2.0f * x, -2.0f * y, 4.0f * z};58 ddir += (s2 * kC2[2]) * float3{-2.0f * x, -2.0f * y, 4.0f * z};
59 ddir += (s3 * kC2[3]) * float3{z, 0.0f, x};59 ddir += (s3 * kC2[3]) * float3{z, 0.0f, x};
60 } else if (grp == 2) {60 } else if (grp == 2) {
61 ddir += (s0 * kC2[4]) * float3{2.0f * x, -2.0f * y, 0.0f};61 ddir += (s0 * kC2[4]) * float3{2.0f * x, -2.0f * y, 0.0f};
62 ddir += (s1 * kC3[0]) * float3{6.0f * xy, 3.0f * (xx - yy), 0.0f};62 ddir += (s1 * kC3[0]) * float3{6.0f * xy, 3.0f * (xx - yy), 0.0f};
63 ddir += (s2 * kC3[1]) * float3{yz, xz, xy};63 ddir += (s2 * kC3[1]) * float3{yz, xz, xy};
64 ddir += (s3 * kC3[2]) *64 ddir += (s3 * kC3[2]) *
65 float3{-2.0f * xy, 4.0f * zz - xx - 3.0f * yy, 8.0f * yz};65 float3{-2.0f * xy, 4.0f * zz - xx - 3.0f * yy, 8.0f * yz};
66 } else {66 } else {
67 ddir += (s0 * kC3[3]) *67 ddir += (s0 * kC3[3]) *
68 float3{-6.0f * xz, -6.0f * yz, 6.0f * zz - 3.0f * xx - 3.0f * yy};68 float3{-6.0f * xz, -6.0f * yz, 6.0f * zz - 3.0f * xx - 3.0f * yy};
69 ddir += (s1 * kC3[4]) *69 ddir += (s1 * kC3[4]) *
70 float3{4.0f * zz - 3.0f * xx - yy, -2.0f * xy, 8.0f * xz};70 float3{4.0f * zz - 3.0f * xx - yy, -2.0f * xy, 8.0f * xz};
71 ddir += (s2 * kC3[5]) * float3{2.0f * xz, -2.0f * yz, xx - yy};71 ddir += (s2 * kC3[5]) * float3{2.0f * xz, -2.0f * yz, xx - yy};
72 ddir += (s3 * kC3[6]) * float3{3.0f * (xx - yy), -6.0f * xy, 0.0f};72 ddir += (s3 * kC3[6]) * float3{3.0f * (xx - yy), -6.0f * xy, 0.0f};
73 }73 }
74 }74 }
75 return ddir;75 return ddir;
76}76}
77} // namespace sh77} // namespace sh
7878
79// VJP of the projection for a touched Gaussian `i`; reads its SH row from79// VJP of the projection for a touched Gaussian `i`; reads its SH row from
80// `sh_in` and writes dL/dsh directly to `sh_out`.80// `sh_in` and writes dL/dsh directly to `sh_out`.
81// Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`.81// Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`.
82template <typename ShT>82template <typename ShT>
83GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i,83GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i,
84 const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out,84 const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out,
85 const ProjectionGrad& g, float3& dmean_out,85 const ProjectionGrad& g, float3& dmean_out,
86 float3& dscale_out, float4& dquat_out) {86 float3& dscale_out, float4& dquat_out) {
87 const float3 mean = LoadMean(p, i);87 const float3 mean = LoadMean(p, i);
88 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],88 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],
89 p.scales[3 * i + 2]};89 p.scales[3 * i + 2]};
90 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];90 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];
9191
92 // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0).92 // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0).
93 float3 dmean = {0.0f, 0.0f, 0.0f};93 float3 dmean = {0.0f, 0.0f, 0.0f};
94 {94 {
95 float inv_len;95 float inv_len;
96 const float3 dir = ViewDir(cam, mean, inv_len);96 const float3 dir = ViewDir(cam, mean, inv_len);
97 const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out);97 const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out);
98 // d(v/|v|)/dv = (I - dir dir^T) / |v|.98 // d(v/|v|)/dv = (I - dir dir^T) / |v|.
99 dmean += inv_len * (ddir - dot(dir, ddir) * dir);99 dmean += inv_len * (ddir - dot(dir, ddir) * dir);
100 }100 }
101101
102 // --- Forward covariance intermediates (reusing R, s0, s1 in VJP).102 // --- Forward covariance intermediates (reusing R, s0, s1 in VJP).
103 const float3 t = cam.ToView(mean);103 const float3 t = cam.ToView(mean);
104 const float tz = t.z;104 const float tz = t.z;
105 const float limx = kFovClamp * cam.TanHalfFovX();105 const float limx = kFovClamp * cam.TanHalfFovX();
106 const float limy = kFovClamp * cam.TanHalfFovY();106 const float limy = kFovClamp * cam.TanHalfFovY();
107 const float txtz = t.x / tz, tytz = t.y / tz;107 const float txtz = t.x / tz, tytz = t.y / tz;
108 const bool x_clamped = txtz < -limx || txtz > limx;108 const bool x_clamped = txtz < -limx || txtz > limx;
109 const bool y_clamped = tytz < -limy || tytz > limy;109 const bool y_clamped = tytz < -limy || tytz > limy;
110 const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;110 const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;
111 const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;111 const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;
112 const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz);112 const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz);
113 const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz);113 const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz);
114 const Mat3& w = cam.rotation;114 const Mat3& w = cam.rotation;
115 const float3 m0 = j00 * w.row(0) + j02 * w.row(2);115 const float3 m0 = j00 * w.row(0) + j02 * w.row(2);
116 const float3 m1 = j11 * w.row(1) + j12 * w.row(2);116 const float3 m1 = j11 * w.row(1) + j12 * w.row(2);
117117
118 const Mat3 r = QuatToRotation(quat);118 const Mat3 r = QuatToRotation(quat);
119 const Sym3 sigma = Covariance3D(r, scale);119 const Sym3 sigma = Covariance3D(r, scale);
120 const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1);120 const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1);
121 const float cov_a = dot(m0, s0) + kLowPass;121 const float cov_a = dot(m0, s0) + kLowPass;
122 const float cov_b = dot(m0, s1);122 const float cov_b = dot(m0, s1);
123 const float cov_c = dot(m1, s1) + kLowPass;123 const float cov_c = dot(m1, s1) + kLowPass;
124124
125 // --- Conic -> 2D covariance: G_cov = -K G_K K.125 // --- Conic -> 2D covariance: G_cov = -K G_K K.
126 const float det = cov_a * cov_c - cov_b * cov_b;126 const float det = cov_a * cov_c - cov_b * cov_b;
127 const float inv = 1.0f / det;127 const float inv = 1.0f / det;
128 const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv;128 const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv;
129 const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z;129 const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z;
130 // P = G_K K.130 // P = G_K K.
131 const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc;131 const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc;
132 const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc;132 const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc;
133 // G_cov = -K P (symmetric).133 // G_cov = -K P (symmetric).
134 const float ca = -(ka * p00 + kb * p10);134 const float ca = -(ka * p00 + kb * p10);
135 const float cb = -(ka * p01 + kb * p11);135 const float cb = -(ka * p01 + kb * p11);
136 const float cc = -(kb * p01 + kc * p11);136 const float cc = -(kb * p01 + kc * p11);
137137
138 // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M.138 // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M.
139 const float3 dm0 = 2.0f * (ca * s0 + cb * s1);139 const float3 dm0 = 2.0f * (ca * s0 + cb * s1);
140 const float3 dm1 = 2.0f * (cb * s0 + cc * s1);140 const float3 dm1 = 2.0f * (cb * s0 + cc * s1);
141 // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T.141 // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T.
142 Sym3 dsig;142 Sym3 dsig;
143 dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x;143 dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x;
144 dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y;144 dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y;
145 dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z;145 dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z;
146 dsig.xy =146 dsig.xy =
147 ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y;147 ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y;
148 dsig.xz =148 dsig.xz =
149 ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z;149 ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z;
150 dsig.yz =150 dsig.yz =
151 ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z;151 ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z;
152152
153 // --- M = J W: dJ = dM W^T (only the nonzero entries of J).153 // --- M = J W: dJ = dM W^T (only the nonzero entries of J).
154 const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2));154 const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2));
155 const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2));155 const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2));
156 const float tz2 = tz * tz, tz3 = tz2 * tz;156 const float tz2 = tz * tz, tz3 = tz2 * tz;
157 float3 dt;157 float3 dt;
158 dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02;158 dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02;
159 dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12;159 dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12;
160 dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 +160 dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 +
161 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12;161 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12;
162 // --- Screen-space mean u = fx x / z + cx.162 // --- Screen-space mean u = fx x / z + cx.
163 dt.x += cam.fx / tz * g.xy.x;163 dt.x += cam.fx / tz * g.xy.x;
164 dt.y += cam.fy / tz * g.xy.y;164 dt.y += cam.fy / tz * g.xy.y;
165 dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2;165 dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2;
166 dmean += MulT(w, dt);166 dmean += MulT(w, dt);
167 dmean_out = dmean;167 dmean_out = dmean;
168168
169 // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A.169 // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A.
170 const float sv[3] = {scale.x, scale.y, scale.z};170 const float sv[3] = {scale.x, scale.y, scale.z};
171 float ds[3];171 float ds[3];
172 Mat3 dr;172 Mat3 dr;
173#pragma unroll173#pragma unroll
174 for (int j = 0; j < 3; ++j) {174 for (int j = 0; j < 3; ++j) {
175 const float3 r_col = r.col(j);175 const float3 r_col = r.col(j);
176 const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col);176 const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col);
177 ds[j] = dot(da_col, r_col);177 ds[j] = dot(da_col, r_col);
178 dr.m[0][j] = da_col.x * sv[j];178 dr.m[0][j] = da_col.x * sv[j];
179 dr.m[1][j] = da_col.y * sv[j];179 dr.m[1][j] = da_col.y * sv[j];
180 dr.m[2][j] = da_col.z * sv[j];180 dr.m[2][j] = da_col.z * sv[j];
181 }181 }
182 dscale_out = {ds[0], ds[1], ds[2]};182 dscale_out = {ds[0], ds[1], ds[2]};
183183
184 // --- R(q) -> q.184 // --- R(q) -> q.
185 const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w;185 const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w;
186 const auto& G = dr.m;186 const auto& G = dr.m;
187 dquat_out = {187 dquat_out = {
188 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] -188 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] -
189 qy * G[2][0] + qx * G[2][1]),189 qy * G[2][0] + qx * G[2][1]),
190 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] -190 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] -
191 qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]),191 qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]),
192 2.0f *192 2.0f *
193 (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] +193 (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] +
194 qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]),194 qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]),
195 2.0f *195 2.0f *
196 (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] -196 (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] -
197 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])};197 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])};
198}198}
199199
200// Reverse traversal per pixel; accumulates splat gradients with atomics.200// Reverse traversal per pixel; accumulates splat gradients with atomics.
201template <int kRows>201template <int kRows>
202__global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 4)202__global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 4)
203 RasterBackwardKernel(const int2* __restrict__ ranges,203 RasterBackwardKernel(const int2* __restrict__ ranges,
204 const int32_t* __restrict__ point_list,204 const int32_t* __restrict__ point_list,
205 const Splat* __restrict__ splats, Frame frame,205 const Splat* __restrict__ splats, Frame frame,
206 const float* __restrict__ final_t,206 const float* __restrict__ final_t,
207 const int32_t* __restrict__ n_contrib,207 const int32_t* __restrict__ n_contrib,
208 const float* __restrict__ dl_dimage,208 const float* __restrict__ dl_dimage,
209 float* __restrict__ splat_grads,209 float* __restrict__ splat_grads,
210 uint8_t* __restrict__ splat_touched) {210 uint8_t* __restrict__ splat_touched) {
211 using L = RasterLayout<kRows>;211 using L = RasterLayout<kRows>;
212 constexpr int kRasterRows = kRows;212 constexpr int kRasterRows = kRows;
213 constexpr int kRasterThreads = L::kThreads;213 constexpr int kRasterThreads = L::kThreads;
214 constexpr int kWarps = L::kWarps;214 constexpr int kWarps = L::kWarps;
215 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;215 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
216 const int tid = threadIdx.x;216 const int tid = threadIdx.x;
217 const int warp = tid / 32;217 const int warp = tid / 32;
218 const int lane = tid % 32;218 const int lane = tid % 32;
219 const RasterPixels px = RasterPixels::For<kRows>(tid);219 const RasterPixels px = RasterPixels::For<kRows>(tid);
220 const float fx = static_cast<float>(px.x);220 const float fx = static_cast<float>(px.x);
221 const int2 range = ranges[tile];221 const int2 range = ranges[tile];
222222
223 struct PixelState {223 struct PixelState {
224 float t;224 float t;
225 float3 dpix;225 float3 dpix;
226 float accum; // dot(color accumulated behind, dpix)226 float accum; // dot(color accumulated behind, dpix)
227 int last;227 int last;
228 };228 };
229 PixelState st[kRasterRows];229 PixelState st[kRasterRows];
230 const int pixels = frame.width * frame.height;230 const int pixels = frame.width * frame.height;
231 int thread_last = 0;231 int thread_last = 0;
232#pragma unroll232#pragma unroll
233 for (int k = 0; k < kRasterRows; ++k) {233 for (int k = 0; k < kRasterRows; ++k) {
234 const int y = px.y0 + k;234 const int y = px.y0 + k;
235 const bool inside = px.x < frame.width && y < frame.height;235 const bool inside = px.x < frame.width && y < frame.height;
236 const int p = y * frame.width + px.x;236 const int p = y * frame.width + px.x;
237 st[k].t = inside ? final_t[p] : 0.0f;237 st[k].t = inside ? final_t[p] : 0.0f;
238 st[k].dpix =238 st[k].dpix =
239 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],239 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],
240 dl_dimage[2 * pixels + p]}240 dl_dimage[2 * pixels + p]}
241 : float3{0.0f, 0.0f, 0.0f};241 : float3{0.0f, 0.0f, 0.0f};
242 st[k].accum = 0.0f;242 st[k].accum = 0.0f;
243 st[k].last = inside ? n_contrib[p] : 0;243 st[k].last = inside ? n_contrib[p] : 0;
244 thread_last = max(thread_last, st[k].last);244 thread_last = max(thread_last, st[k].last);
245 }245 }
246246
247 int warp_max_last = thread_last;247 int warp_max_last = thread_last;
248#pragma unroll248#pragma unroll
249 for (int offset = 16; offset > 0; offset /= 2) {249 for (int offset = 16; offset > 0; offset /= 2) {
250 warp_max_last = max(250 warp_max_last = max(
251 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));251 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));
252 }252 }
253 __shared__ int shared_max_last[kWarps];253 __shared__ int shared_max_last[kWarps];
254 if (lane == 0) shared_max_last[warp] = warp_max_last;254 if (lane == 0) shared_max_last[warp] = warp_max_last;
255 __syncthreads();255 __syncthreads();
256 int block_max_last = 0;256 int block_max_last = 0;
257#pragma unroll257#pragma unroll
258 for (int w = 0; w < kWarps; ++w) {258 for (int w = 0; w < kWarps; ++w) {
259 block_max_last = max(block_max_last, shared_max_last[w]);259 block_max_last = max(block_max_last, shared_max_last[w]);
260 }260 }
261 if (block_max_last <= 0) return;261 if (block_max_last <= 0) {
262 return;
263 }
262264
263 __shared__ float4 batch_geom0[kRasterThreads];265 __shared__ float4 batch_geom0[kRasterThreads];
264 __shared__ float2 batch_geom1[kRasterThreads];266 __shared__ float batch_geom1[kRasterThreads];
265 __shared__ float4 batch_rgb_o[kRasterThreads];267 __shared__ float4 batch_rgb_o[kRasterThreads];
266 __shared__ uint32_t batch_id[kRasterThreads];268 __shared__ uint32_t batch_id[kRasterThreads];
267 __shared__ uint8_t batch_mask[kRasterThreads];269 __shared__ uint8_t batch_mask[kRasterThreads];
268 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);270 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
↕ Expand 24 unchanged lines (lines 271–294) · in PixelState
269 int scatter = -1;271 int scatter = -1;
270 int scatter2 = -1;272 int scatter2 = -1;
271 if constexpr (L::kWarpWidth == 8) {273 if constexpr (L::kWarpWidth == 8) {
272 const int sub_lane = lane & 15;274 const int sub_lane = lane & 15;
273 const int row = sub_lane >> 3, col = sub_lane & 7;275 const int row = sub_lane >> 3, col = sub_lane & 7;
274 if (col == 0) {276 if (col == 0) {
275 scatter2 = row;277 scatter2 = row;
276 } else if (col == 2) {278 } else if (col == 2) {
277 scatter2 = 2 + row;279 scatter2 = 2 + row;
278 } else if (col == 4) {280 } else if (col == 4) {
279 scatter2 = row == 0 ? 4 : 7;281 scatter2 = row == 0 ? 4 : 7;
280 } else if (col == 6) {282 } else if (col == 6) {
281 scatter2 = row == 0 ? 6 : 8;283 scatter2 = row == 0 ? 6 : 8;
282 } else if (col == 1 && row == 0) {284 } else if (col == 1 && row == 0) {
283 scatter2 = 5;285 scatter2 = 5;
284 }286 }
285 const int full_row = lane >> 3, full_col = lane & 7;287 const int full_row = lane >> 3, full_col = lane & 7;
286 if (full_col == 0) {288 if (full_col == 0) {
287 scatter = full_row;289 scatter = full_row;
288 } else if (full_col == 2) {290 } else if (full_col == 2) {
289 scatter = full_row == 0 ? 4 : 5 + full_row;291 scatter = full_row == 0 ? 4 : 5 + full_row;
290 } else if (full_col == 4 && full_row == 0) {292 } else if (full_col == 4 && full_row == 0) {
291 scatter = 5;293 scatter = 5;
292 }294 }
293 } else {295 } else {
294 scatter = warp::ScatterIndex(kSplatGradFloats, lane);296 scatter = warp::ScatterIndex(kSplatGradFloats, lane);
295 }297 }
296298
299 // Evaluates one splat at this thread's pixels and accumulates the per-thread
300 // partial gradient sums. Branch-free: lanes of a warp almost never agree on
301 // which pixels a splat reaches, so predication is cheaper than branching.
302 // Pixels the splat does not reach get alpha = 0, which leaves t and accum
303 // exactly unchanged (t * rcp(1) == t) and contributes zero gradient.
297 auto eval_splat = [&](int j, float4 g0, float2 g1, float4 ro, int entry,304 auto eval_splat = [&](float4 g0, float c2, float4 ro, int entry, float& dx,
298 float& dx, float& ga_sum, float& ga_dy, float& ga_dy2,305 float& ga_sum, float& ga_dy, float& ga_dy2,
299 float3& drgb) {306 float3& drgb) {
300 dx = g0.x - fx;307 dx = __fsub_rn(g0.x, fx);
308 const Power2Column col = Power2Column::At(g0.z, g0.w, dx);
301 ga_sum = 0.0f;309 ga_sum = 0.0f;
302 ga_dy = 0.0f;310 ga_dy = 0.0f;
303 ga_dy2 = 0.0f;311 ga_dy2 = 0.0f;
304 drgb = {0.0f, 0.0f, 0.0f};312 drgb = {0.0f, 0.0f, 0.0f};
305 if (entry < thread_last) {
306 const float cx_dx2 = g0.z * dx * dx;
307 const float cy_dx = g0.w * dx;
308#pragma unroll313#pragma unroll
309 for (int k = 0; k < kRasterRows; ++k) {314 for (int k = 0; k < kRasterRows; ++k) {
310 PixelState& ps = st[k];315 PixelState& ps = st[k];
311 if (entry >= ps.last) continue;
312 const float dy = g0.y - static_cast<float>(px.y0 + k);316 const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));
313 const float power = cx_dx2 + g1.x * dy * dy + cy_dx * dy;317 const float power = col.Row(c2, dy);
314 if (power > 0.0f || power < g1.y) continue;
315 const float g_val = Exp2Approx(power);318 const float g_val = Exp2Approx(power);
316 const float alpha = fminf(kMaxAlpha, ro.w * g_val);319 const float alpha_k = SplatAlpha(ro.w, g_val);
320 const bool hit = entry < ps.last && power <= 0.0f && alpha_k >= kMinAlpha;
317 if (alpha < kMinAlpha) continue;321 const float alpha = hit ? alpha_k : 0.0f;
318 ps.t = __fdividef(ps.t, 1.0f - alpha);322 ps.t = ps.t * RcpApprox(1.0f - alpha);
319 const float w = alpha * ps.t;323 const float w = alpha * ps.t;
320 const float color_dot =324 const float color_dot =
321 ro.x * ps.dpix.x + ro.y * ps.dpix.y + ro.z * ps.dpix.z;325 ro.x * ps.dpix.x + ro.y * ps.dpix.y + ro.z * ps.dpix.z;
322 const float diff = color_dot - ps.accum;326 const float diff = color_dot - ps.accum;
323 const float dl_dalpha = ps.t * diff;
324 ps.accum += alpha * diff;327 ps.accum += alpha * diff;
325 const float ga = g_val * dl_dalpha;328 const float ga = hit ? g_val * (ps.t * diff) : 0.0f;
326 ga_sum += ga;329 ga_sum += ga;
327 const float ga_y = ga * dy;330 const float ga_y = ga * dy;
328 ga_dy += ga_y;331 ga_dy += ga_y;
329 ga_dy2 += ga_y * dy;332 ga_dy2 += ga_y * dy;
330 drgb += w * ps.dpix;333 drgb += w * ps.dpix;
331 }334 }
332 }
333 };335 };
336 // Conic (a, b, c) from the staged log2-scaled one.
334 auto raw_conic = [](float4 g0, float2 g1) {337 auto raw_conic = [](float4 g0, float c2) {
335 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,338 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,
336 kInvPower2Scale * g1.x};339 kInvPower2Scale * c2};
337 };340 };
338341
339 for (int end = block_max_last; end > 0; end -= kRasterThreads) {342 for (int end = block_max_last; end > 0; end -= kRasterThreads) {
340 const int e = end - 1 - tid;343 const int e = end - 1 - tid;
341 if (e >= 0) {344 if (e >= 0) {
342 const uint32_t packed = static_cast<uint32_t>(point_list[range.x + e]);345 const uint32_t packed = static_cast<uint32_t>(point_list[range.x + e]);
343 const uint32_t id = packed & 0x00ffffffu;346 const uint32_t id = packed & 0x00ffffffu;
344 splat_touched[id] = 1u;347 splat_touched[id] = 1u;
345 const float4 p0 = __ldg(&splats[id].p0);348 const float4 p0 = __ldg(&splats[id].p0);
346 const float4 p1 = __ldg(&splats[id].p1);349 const float4 p1 = __ldg(&splats[id].p1);
347 const float4 p2 = __ldg(&splats[id].p2);350 const float4 p2 = __ldg(&splats[id].p2);
348 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);351 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
349 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};352 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
350 batch_geom1[tid] = {c2.z, p2.w};353 batch_geom1[tid] = c2.z;
351 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};354 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};
352 batch_id[tid] = id;355 batch_id[tid] = id;
353 batch_mask[tid] = static_cast<uint8_t>(packed >> 24);356 batch_mask[tid] = static_cast<uint8_t>(packed >> 24);
354 } else {357 } else {
↕ Expand 14 unchanged lines (lines 358–371) · in PixelState
355 batch_mask[tid] = 0u;358 batch_mask[tid] = 0u;
356 }359 }
357 __syncthreads();360 __syncthreads();
358 const int count = min(kRasterThreads, end);361 const int count = min(kRasterThreads, end);
359#pragma unroll362#pragma unroll
360 for (int c = 0; c < kRasterThreads; c += 32) {363 for (int c = 0; c < kRasterThreads; c += 32) {
361 if (c < count && end - 32 - c < warp_max_last) {364 if (c < count && end - 32 - c < warp_max_last) {
362 uint32_t mask = __ballot_sync(365 uint32_t mask = __ballot_sync(
363 warp::kFullMask,366 warp::kFullMask,
364 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);367 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
365 if constexpr (L::kWarpWidth == 8) {368 if constexpr (L::kWarpWidth == 8) {
366 while ((mask & (mask - 1u)) != 0u) {369 while ((mask & (mask - 1u)) != 0u) {
367 const int bit0 = __ffs(mask) - 1;370 const int bit0 = __ffs(mask) - 1;
368 mask &= mask - 1u;371 mask &= mask - 1u;
369 const int bit1 = __ffs(mask) - 1;372 const int bit1 = __ffs(mask) - 1;
370 mask &= mask - 1u;373 mask &= mask - 1u;
371 const int j0 = c + bit0, j1 = c + bit1;374 const int j0 = c + bit0, j1 = c + bit1;
372 const float4 g0_0 = batch_geom0[j0];375 const float4 g0_0 = batch_geom0[j0];
373 const float2 g1_0 = batch_geom1[j0];376 const float c2_0 = batch_geom1[j0];
374 const float4 ro_0 = batch_rgb_o[j0];377 const float4 ro_0 = batch_rgb_o[j0];
375 const float4 g0_1 = batch_geom0[j1];378 const float4 g0_1 = batch_geom0[j1];
376 const float2 g1_1 = batch_geom1[j1];379 const float c2_1 = batch_geom1[j1];
377 const float4 ro_1 = batch_rgb_o[j1];380 const float4 ro_1 = batch_rgb_o[j1];
378381
379 float dx0, ga_sum0, ga_dy0, ga_dy2_0;382 float dx0, ga_sum0, ga_dy0, ga_dy2_0;
380 float3 drgb0;383 float3 drgb0;
381 eval_splat(j0, g0_0, g1_0, ro_0, end - 1 - j0, dx0, ga_sum0, ga_dy0,384 eval_splat(g0_0, c2_0, ro_0, end - 1 - j0, dx0, ga_sum0, ga_dy0,
382 ga_dy2_0, drgb0);385 ga_dy2_0, drgb0);
383386
384 float dx1, ga_sum1, ga_dy1, ga_dy2_1;387 float dx1, ga_sum1, ga_dy1, ga_dy2_1;
385 float3 drgb1;388 float3 drgb1;
386 eval_splat(j1, g0_1, g1_1, ro_1, end - 1 - j1, dx1, ga_sum1, ga_dy1,389 eval_splat(g0_1, c2_1, ro_1, end - 1 - j1, dx1, ga_sum1, ga_dy1,
387 ga_dy2_1, drgb1);390 ga_dy2_1, drgb1);
388391
389 const bool hi16 = (lane & 16) != 0;392 const bool hi16 = (lane & 16) != 0;
390 const float opacity = hi16 ? ro_1.w : ro_0.w;393 const float opacity = hi16 ? ro_1.w : ro_0.w;
391 const float dx = hi16 ? dx1 : dx0;394 const float dx = hi16 ? dx1 : dx0;
392 const float3 cn =395 const float3 cn =
393 hi16 ? raw_conic(g0_1, g1_1) : raw_conic(g0_0, g1_0);396 hi16 ? raw_conic(g0_1, c2_1) : raw_conic(g0_0, c2_0);
394397
395 auto fold16 = [&](float v0, float v1) {398 auto fold16 = [&](float v0, float v1) {
396 const float send = hi16 ? v0 : v1;399 const float send = hi16 ? v0 : v1;
397 const float recv = __shfl_xor_sync(warp::kFullMask, send, 16);400 const float recv = __shfl_xor_sync(warp::kFullMask, send, 16);
↕ Expand 34 unchanged lines (lines 401–434) · in PixelState
398 return (hi16 ? v1 : v0) + recv;401 return (hi16 ? v1 : v0) + recv;
399 };402 };
400 float ga_sum = fold16(ga_sum0, ga_sum1);403 float ga_sum = fold16(ga_sum0, ga_sum1);
401 float ga_dy = fold16(ga_dy0, ga_dy1);404 float ga_dy = fold16(ga_dy0, ga_dy1);
402 float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1),405 float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1),
403 fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y),406 fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y),
404 fold16(drgb0.z, drgb1.z)};407 fold16(drgb0.z, drgb1.z)};
405 warp::internal::HalvingStep<4, 8>(b4, lane);408 warp::internal::HalvingStep<4, 8>(b4, lane);
406 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);409 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
407 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);410 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
408411
409 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;412 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
410 const float p_dx = dx * p_sum;413 const float p_dx = dx * p_sum;
411 const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)414 const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
412 : -(cn.y * p_dx + cn.z * p_dy);415 : -(cn.y * p_dx + cn.z * p_dy);
413 const float v_dx1 =416 const float v_dx1 =
414 (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;417 (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
415 float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]};418 float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]};
416 warp::internal::HalvingStep<4, 4>(v4, lane);419 warp::internal::HalvingStep<4, 4>(v4, lane);
417 warp::internal::HalvingStep<2, 2>(v4, lane);420 warp::internal::HalvingStep<2, 2>(v4, lane);
418 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4);421 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4);
419 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2);422 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2);
420 const float v_sum =423 const float v_sum =
421 v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1);424 v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1);
422 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1);425 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1);
423 const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum;426 const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum;
424 if (scatter2 >= 0 && out_grad != 0.0f) {427 if (scatter2 >= 0 && out_grad != 0.0f) {
425 const uint32_t target_id = batch_id[hi16 ? j1 : j0];428 const uint32_t target_id = batch_id[hi16 ? j1 : j0];
426 atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2,429 atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2,
427 out_grad);430 out_grad);
428 }431 }
429 }432 }
430 }433 }
431 while (mask != 0u) {434 while (mask != 0u) {
432 const int bit = __ffs(mask) - 1;435 const int bit = __ffs(mask) - 1;
433 mask &= mask - 1u;436 mask &= mask - 1u;
434 const int j = c + bit;437 const int j = c + bit;
435 const float4 g0 = batch_geom0[j];438 const float4 g0 = batch_geom0[j];
436 const float2 g1 = batch_geom1[j];439 const float c2 = batch_geom1[j];
437 const float4 ro = batch_rgb_o[j];440 const float4 ro = batch_rgb_o[j];
438 float dx, ga_sum, ga_dy, ga_dy2;441 float dx, ga_sum, ga_dy, ga_dy2;
439 float3 drgb;442 float3 drgb;
440 eval_splat(j, g0, g1, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2,443 eval_splat(g0, c2, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2, drgb);
441 drgb);
442 const float opacity = ro.w;444 const float opacity = ro.w;
443 const float3 cn = raw_conic(g0, g1);445 const float3 cn = raw_conic(g0, c2);
444 float out_grad = 0.0f;446 float out_grad = 0.0f;
445 if constexpr (L::kWarpWidth == 8) {447 if constexpr (L::kWarpWidth == 8) {
446 float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z};448 float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z};
447 warp::internal::HalvingStep<4, 16>(b4, lane);449 warp::internal::HalvingStep<4, 16>(b4, lane);
↕ Expand 197 unchanged lines (lines 450–646) · in PreprocessBackwardKernel
448 warp::internal::HalvingStep<2, 8>(b4, lane);450 warp::internal::HalvingStep<2, 8>(b4, lane);
449 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16);451 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16);
450 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16);452 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16);
451 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);453 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
452 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);454 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
453 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;455 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
454 const float p_dx = dx * p_sum;456 const float p_dx = dx * p_sum;
455 float v_dx;457 float v_dx;
456 if ((lane & 16) == 0) {458 if ((lane & 16) == 0) {
457 v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)459 v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
458 : -(cn.y * p_dx + cn.z * p_dy);460 : -(cn.y * p_dx + cn.z * p_dy);
459 } else {461 } else {
460 v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;462 v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
461 }463 }
462 float v[3] = {v_dx, b4[0], ga_sum};464 float v[3] = {v_dx, b4[0], ga_sum};
463 warp::internal::HalvingStep<3, 4>(v, lane);465 warp::internal::HalvingStep<3, 4>(v, lane);
464 warp::internal::HalvingStep<2, 2>(v, lane);466 warp::internal::HalvingStep<2, 2>(v, lane);
465 out_grad = v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1);467 out_grad = v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1);
466 } else {468 } else {
467 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;469 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
468 const float p_dx = dx * p_sum;470 const float p_dx = dx * p_sum;
469 float gr[kSplatGradFloats] = {-(cn.x * p_dx + cn.y * p_dy), // xy471 float gr[kSplatGradFloats] = {-(cn.x * p_dx + cn.y * p_dy), // xy
470 -(cn.y * p_dx + cn.z * p_dy),472 -(cn.y * p_dx + cn.z * p_dy),
471 -0.5f * dx * p_dx, // conic473 -0.5f * dx * p_dx, // conic
472 -dx * p_dy,474 -dx * p_dy,
473 -0.5f * opacity * ga_dy2,475 -0.5f * opacity * ga_dy2,
474 ga_sum, // opacity476 ga_sum, // opacity
475 drgb.x,477 drgb.x,
476 drgb.y,478 drgb.y,
477 drgb.z}; // rgb479 drgb.z}; // rgb
478 warp::ReduceScatter(gr, lane);480 warp::ReduceScatter(gr, lane);
479 out_grad = gr[0];481 out_grad = gr[0];
480 }482 }
481 if (scatter >= 0 && out_grad != 0.0f) {483 if (scatter >= 0 && out_grad != 0.0f) {
482 atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter,484 atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter,
483 out_grad);485 out_grad);
484 }486 }
485 }487 }
486 }488 }
487 }489 }
488 __syncthreads();490 __syncthreads();
489 }491 }
490}492}
491493
492// ---------------------------------------------------------------------------494// ---------------------------------------------------------------------------
493// Preprocess backward: splat gradients -> parameter gradients (dense).495// Preprocess backward: splat gradients -> parameter gradients (dense).
494// Uses the 1-byte `splat_touched` flag written by `RasterBackwardKernel` to496// Uses the 1-byte `splat_touched` flag written by `RasterBackwardKernel` to
495// skip reading `splat_grads` and `params.sh` for untouched Gaussians, and497// skip reading `splat_grads` and `params.sh` for untouched Gaussians, and
496// zeros untouched SH rows with warp-coalesced float4 stores (0 shared memory).498// zeros untouched SH rows with warp-coalesced float4 stores (0 shared memory).
497499
498template <typename ShT>500template <typename ShT>
499__global__ void __launch_bounds__(kGaussianThreads)501__global__ void __launch_bounds__(kGaussianThreads)
500 PreprocessBackwardKernel(GaussianParams params, Frame frame,502 PreprocessBackwardKernel(GaussianParams params, Frame frame,
501 const float* __restrict__ splat_grads,503 const float* __restrict__ splat_grads,
502 const uint8_t* __restrict__ splat_touched,504 const uint8_t* __restrict__ splat_touched,
503 GaussianGrads grads) {505 GaussianGrads grads) {
504 constexpr int kShVec16 = ShIo<ShT>::kRowVec16; // 16-byte stores per row.506 constexpr int kShVec16 = ShIo<ShT>::kRowVec16; // 16-byte stores per row.
505 const int i = blockIdx.x * blockDim.x + threadIdx.x;507 const int i = blockIdx.x * blockDim.x + threadIdx.x;
506 const int lane = threadIdx.x % 32;508 const int lane = threadIdx.x % 32;
507 bool touched = (i < params.n && splat_touched[i] != 0u);509 bool touched = (i < params.n && splat_touched[i] != 0u);
508 const uint32_t init_mask = __ballot_sync(warp::kFullMask, touched);510 const uint32_t init_mask = __ballot_sync(warp::kFullMask, touched);
509 if (init_mask == 0u) {511 if (init_mask == 0u) {
510 if (i < params.n) {512 if (i < params.n) {
511 grads.means[3 * i] = 0.0f;513 grads.means[3 * i] = 0.0f;
512 grads.means[3 * i + 1] = 0.0f;514 grads.means[3 * i + 1] = 0.0f;
513 grads.means[3 * i + 2] = 0.0f;515 grads.means[3 * i + 2] = 0.0f;
514 grads.scales[3 * i] = 0.0f;516 grads.scales[3 * i] = 0.0f;
515 grads.scales[3 * i + 1] = 0.0f;517 grads.scales[3 * i + 1] = 0.0f;
516 grads.scales[3 * i + 2] = 0.0f;518 grads.scales[3 * i + 2] = 0.0f;
517 reinterpret_cast<float4*>(grads.quats)[i] = {0.0f, 0.0f, 0.0f, 0.0f};519 reinterpret_cast<float4*>(grads.quats)[i] = {0.0f, 0.0f, 0.0f, 0.0f};
518 grads.opacities[i] = 0.0f;520 grads.opacities[i] = 0.0f;
519 }521 }
520 const int warp_i0 =522 const int warp_i0 =
521 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;523 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;
522 const int warp_n = max(0, min(32, params.n - warp_i0));524 const int warp_n = max(0, min(32, params.n - warp_i0));
523 uint4* warp_sh_dst =525 uint4* warp_sh_dst =
524 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));526 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));
525 const uint4 zero = {0u, 0u, 0u, 0u};527 const uint4 zero = {0u, 0u, 0u, 0u};
526 if (warp_n == 32) {528 if (warp_n == 32) {
527#pragma unroll529#pragma unroll
528 for (int s = 0; s < kShVec16; ++s) {530 for (int s = 0; s < kShVec16; ++s) {
529 warp_sh_dst[s * 32 + lane] = zero;531 warp_sh_dst[s * 32 + lane] = zero;
530 }532 }
531 } else {533 } else {
532 for (int k = lane; k < warp_n * kShVec16; k += 32) {534 for (int k = lane; k < warp_n * kShVec16; k += 32) {
533 warp_sh_dst[k] = zero;535 warp_sh_dst[k] = zero;
534 }536 }
535 }537 }
536 return;538 return;
537 }539 }
538540
539 float3 dmean = {0.0f, 0.0f, 0.0f};541 float3 dmean = {0.0f, 0.0f, 0.0f};
540 float3 dscale = {0.0f, 0.0f, 0.0f};542 float3 dscale = {0.0f, 0.0f, 0.0f};
541 float4 dquat = {0.0f, 0.0f, 0.0f, 0.0f};543 float4 dquat = {0.0f, 0.0f, 0.0f, 0.0f};
542 float dopacity = 0.0f;544 float dopacity = 0.0f;
543 if (touched) {545 if (touched) {
544 float g[kSplatGradFloats];546 float g[kSplatGradFloats];
545 bool nonzero = false;547 bool nonzero = false;
546#pragma unroll548#pragma unroll
547 for (int q = 0; q < kSplatGradFloats; ++q) {549 for (int q = 0; q < kSplatGradFloats; ++q) {
548 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);550 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);
549 nonzero = nonzero || g[q] != 0.0f;551 nonzero = nonzero || g[q] != 0.0f;
550 }552 }
551 touched = nonzero;553 touched = nonzero;
552 if (touched) {554 if (touched) {
553 ProjectionGrad pg;555 ProjectionGrad pg;
554 pg.xy = {g[0], g[1]};556 pg.xy = {g[0], g[1]};
555 pg.conic = {g[2], g[3], g[4]};557 pg.conic = {g[2], g[3], g[4]};
556 pg.opacity = g[5];558 pg.opacity = g[5];
557 pg.rgb = {g[6], g[7], g[8]};559 pg.rgb = {g[6], g[7], g[8]};
558 dopacity = g[5];560 dopacity = g[5];
559 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),561 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),
560 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);562 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);
561 }563 }
562 }564 }
563 if (i < params.n) {565 if (i < params.n) {
564 grads.means[3 * i] = dmean.x;566 grads.means[3 * i] = dmean.x;
565 grads.means[3 * i + 1] = dmean.y;567 grads.means[3 * i + 1] = dmean.y;
566 grads.means[3 * i + 2] = dmean.z;568 grads.means[3 * i + 2] = dmean.z;
567 grads.scales[3 * i] = dscale.x;569 grads.scales[3 * i] = dscale.x;
568 grads.scales[3 * i + 1] = dscale.y;570 grads.scales[3 * i + 1] = dscale.y;
569 grads.scales[3 * i + 2] = dscale.z;571 grads.scales[3 * i + 2] = dscale.z;
570 reinterpret_cast<float4*>(grads.quats)[i] = dquat;572 reinterpret_cast<float4*>(grads.quats)[i] = dquat;
571 grads.opacities[i] = dopacity;573 grads.opacities[i] = dopacity;
572 }574 }
573 const uint32_t mask = __ballot_sync(warp::kFullMask, touched);575 const uint32_t mask = __ballot_sync(warp::kFullMask, touched);
574 if (mask != warp::kFullMask) {576 if (mask != warp::kFullMask) {
575 const int warp_i0 =577 const int warp_i0 =
576 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;578 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;
577 const int warp_n = max(0, min(32, params.n - warp_i0));579 const int warp_n = max(0, min(32, params.n - warp_i0));
578 uint4* warp_sh_dst =580 uint4* warp_sh_dst =
579 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));581 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));
580 const uint4 zero = {0u, 0u, 0u, 0u};582 const uint4 zero = {0u, 0u, 0u, 0u};
581 if (warp_n == 32) {583 if (warp_n == 32) {
582#pragma unroll584#pragma unroll
583 for (int s = 0; s < kShVec16; ++s) {585 for (int s = 0; s < kShVec16; ++s) {
584 const int k = s * 32 + lane;586 const int k = s * 32 + lane;
585 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;587 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;
586 if (((mask >> row_in_warp) & 1u) == 0u) {588 if (((mask >> row_in_warp) & 1u) == 0u) {
587 warp_sh_dst[k] = zero;589 warp_sh_dst[k] = zero;
588 }590 }
589 }591 }
590 } else {592 } else {
591 for (int k = lane; k < warp_n * kShVec16; k += 32) {593 for (int k = lane; k < warp_n * kShVec16; k += 32) {
592 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;594 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;
593 if (((mask >> row_in_warp) & 1u) == 0u) {595 if (((mask >> row_in_warp) & 1u) == 0u) {
594 warp_sh_dst[k] = zero;596 warp_sh_dst[k] = zero;
595 }597 }
596 }598 }
597 }599 }
598 }600 }
599}601}
600602
601template <typename ShT>603template <typename ShT>
602cudaError_t LaunchPreprocessBackward(const GaussianParams& params,604cudaError_t LaunchPreprocessBackward(const GaussianParams& params,
603 const Frame& frame,605 const Frame& frame,
604 const float* splat_grads,606 const float* splat_grads,
605 const uint8_t* splat_touched,607 const uint8_t* splat_touched,
606 const GaussianGrads& grads,608 const GaussianGrads& grads,
607 cudaStream_t stream) {609 cudaStream_t stream) {
608 PreprocessBackwardKernel<ShT>610 PreprocessBackwardKernel<ShT>
609 <<<GaussianBlocks(params.n), kGaussianThreads, 0, stream>>>(611 <<<GaussianBlocks(params.n), kGaussianThreads, 0, stream>>>(
610 params, frame, splat_grads, splat_touched, grads);612 params, frame, splat_grads, splat_touched, grads);
611 return cudaSuccess;613 return cudaSuccess;
612}614}
613615
614template __global__ void RasterBackwardKernel<1>(616template __global__ void RasterBackwardKernel<1>(
615 const int2* __restrict__, const int32_t* __restrict__,617 const int2* __restrict__, const int32_t* __restrict__,
616 const Splat* __restrict__, Frame, const float* __restrict__,618 const Splat* __restrict__, Frame, const float* __restrict__,
617 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,619 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
618 uint8_t* __restrict__);620 uint8_t* __restrict__);
619template __global__ void RasterBackwardKernel<2>(621template __global__ void RasterBackwardKernel<2>(
620 const int2* __restrict__, const int32_t* __restrict__,622 const int2* __restrict__, const int32_t* __restrict__,
621 const Splat* __restrict__, Frame, const float* __restrict__,623 const Splat* __restrict__, Frame, const float* __restrict__,
622 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,624 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
623 uint8_t* __restrict__);625 uint8_t* __restrict__);
624template __global__ void RasterBackwardKernel<4>(626template __global__ void RasterBackwardKernel<4>(
625 const int2* __restrict__, const int32_t* __restrict__,627 const int2* __restrict__, const int32_t* __restrict__,
626 const Splat* __restrict__, Frame, const float* __restrict__,628 const Splat* __restrict__, Frame, const float* __restrict__,
627 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,629 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
628 uint8_t* __restrict__);630 uint8_t* __restrict__);
629template __global__ void PreprocessBackwardKernel<float>(631template __global__ void PreprocessBackwardKernel<float>(
630 GaussianParams, Frame, const float* __restrict__,632 GaussianParams, Frame, const float* __restrict__,
631 const uint8_t* __restrict__, GaussianGrads);633 const uint8_t* __restrict__, GaussianGrads);
632template __global__ void PreprocessBackwardKernel<ShBf16>(634template __global__ void PreprocessBackwardKernel<ShBf16>(
633 GaussianParams, Frame, const float* __restrict__,635 GaussianParams, Frame, const float* __restrict__,
634 const uint8_t* __restrict__, GaussianGrads);636 const uint8_t* __restrict__, GaussianGrads);
635template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,637template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,
636 const Frame&, const float*,638 const Frame&, const float*,
637 const uint8_t*,639 const uint8_t*,
638 const GaussianGrads&,640 const GaussianGrads&,
639 cudaStream_t);641 cudaStream_t);
640template cudaError_t LaunchPreprocessBackward<ShBf16>(642template cudaError_t LaunchPreprocessBackward<ShBf16>(
641 const GaussianParams&, const Frame&, const float*, const uint8_t*,643 const GaussianParams&, const Frame&, const float*, const uint8_t*,
642 const GaussianGrads&, cudaStream_t);644 const GaussianGrads&, cudaStream_t);
643645
644} // namespace gs646} // namespace gs
↕ Expand 256 unchanged lines (lines 1–256) · in PixelState
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 reading the row `sh_in` and writing dL/dsh directly
2020 // to the row `sh_out` (same storage type), group by group.
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 const float3 raw = EvalRow(sh_in, b);
2727 const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x,
2828 raw.y < 0.0f ? 0.0f : g_rgb.y,
2929 raw.z < 0.0f ? 0.0f : g_rgb.z};
3030 const float x = d.x, y = d.y, z = d.z;
3131 const float xx = x * x, yy = y * y, zz = z * z;
3232 const float xy = x * y, xz = x * z, yz = y * z;
3333 float3 ddir = {0.0f, 0.0f, 0.0f};
3434 #pragma unroll
3535 for (int grp = 0; grp < 4; ++grp) {
3636 const int k0 = 4 * grp;
3737 float v[12];
3838 ShIo<ShT>::LoadGroup(sh_in, grp, v);
3939 const float s0 = v[0] * drgb.x + v[1] * drgb.y + v[2] * drgb.z;
4040 const float s1 = v[3] * drgb.x + v[4] * drgb.y + v[5] * drgb.z;
4141 const float s2 = v[6] * drgb.x + v[7] * drgb.y + v[8] * drgb.z;
4242 const float s3 = v[9] * drgb.x + v[10] * drgb.y + v[11] * drgb.z;
4343 float out[12];
4444 #pragma unroll
4545 for (int q = 0; q < 4; ++q) {
4646 out[3 * q] = b[k0 + q] * drgb.x;
4747 out[3 * q + 1] = b[k0 + q] * drgb.y;
4848 out[3 * q + 2] = b[k0 + q] * drgb.z;
4949 }
5050 ShIo<ShT>::StoreGroup(sh_out, grp, out);
5151 if (grp == 0) {
5252 ddir.y += s1 * (-kC1);
5353 ddir.z += s2 * kC1;
5454 ddir.x += s3 * (-kC1);
5555 } else if (grp == 1) {
5656 ddir += (s0 * kC2[0]) * float3{y, x, 0.0f};
5757 ddir += (s1 * kC2[1]) * float3{0.0f, z, y};
5858 ddir += (s2 * kC2[2]) * float3{-2.0f * x, -2.0f * y, 4.0f * z};
5959 ddir += (s3 * kC2[3]) * float3{z, 0.0f, x};
6060 } else if (grp == 2) {
6161 ddir += (s0 * kC2[4]) * float3{2.0f * x, -2.0f * y, 0.0f};
6262 ddir += (s1 * kC3[0]) * float3{6.0f * xy, 3.0f * (xx - yy), 0.0f};
6363 ddir += (s2 * kC3[1]) * float3{yz, xz, xy};
6464 ddir += (s3 * kC3[2]) *
6565 float3{-2.0f * xy, 4.0f * zz - xx - 3.0f * yy, 8.0f * yz};
6666 } else {
6767 ddir += (s0 * kC3[3]) *
6868 float3{-6.0f * xz, -6.0f * yz, 6.0f * zz - 3.0f * xx - 3.0f * yy};
6969 ddir += (s1 * kC3[4]) *
7070 float3{4.0f * zz - 3.0f * xx - yy, -2.0f * xy, 8.0f * xz};
7171 ddir += (s2 * kC3[5]) * float3{2.0f * xz, -2.0f * yz, xx - yy};
7272 ddir += (s3 * kC3[6]) * float3{3.0f * (xx - yy), -6.0f * xy, 0.0f};
7373 }
7474 }
7575 return ddir;
7676 }
7777 } // namespace sh
7878
7979 // VJP of the projection for a touched Gaussian `i`; reads its SH row from
8080 // `sh_in` and writes dL/dsh directly to `sh_out`.
8181 // Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`.
8282 template <typename ShT>
8383 GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i,
8484 const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out,
8585 const ProjectionGrad& g, float3& dmean_out,
8686 float3& dscale_out, float4& dquat_out) {
8787 const float3 mean = LoadMean(p, i);
8888 const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1],
8989 p.scales[3 * i + 2]};
9090 const float4 quat = reinterpret_cast<const float4*>(p.quats)[i];
9191
9292 // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0).
9393 float3 dmean = {0.0f, 0.0f, 0.0f};
9494 {
9595 float inv_len;
9696 const float3 dir = ViewDir(cam, mean, inv_len);
9797 const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out);
9898 // d(v/|v|)/dv = (I - dir dir^T) / |v|.
9999 dmean += inv_len * (ddir - dot(dir, ddir) * dir);
100100 }
101101
102102 // --- Forward covariance intermediates (reusing R, s0, s1 in VJP).
103103 const float3 t = cam.ToView(mean);
104104 const float tz = t.z;
105105 const float limx = kFovClamp * cam.TanHalfFovX();
106106 const float limy = kFovClamp * cam.TanHalfFovY();
107107 const float txtz = t.x / tz, tytz = t.y / tz;
108108 const bool x_clamped = txtz < -limx || txtz > limx;
109109 const bool y_clamped = tytz < -limy || tytz > limy;
110110 const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz;
111111 const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz;
112112 const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz);
113113 const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz);
114114 const Mat3& w = cam.rotation;
115115 const float3 m0 = j00 * w.row(0) + j02 * w.row(2);
116116 const float3 m1 = j11 * w.row(1) + j12 * w.row(2);
117117
118118 const Mat3 r = QuatToRotation(quat);
119119 const Sym3 sigma = Covariance3D(r, scale);
120120 const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1);
121121 const float cov_a = dot(m0, s0) + kLowPass;
122122 const float cov_b = dot(m0, s1);
123123 const float cov_c = dot(m1, s1) + kLowPass;
124124
125125 // --- Conic -> 2D covariance: G_cov = -K G_K K.
126126 const float det = cov_a * cov_c - cov_b * cov_b;
127127 const float inv = 1.0f / det;
128128 const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv;
129129 const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z;
130130 // P = G_K K.
131131 const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc;
132132 const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc;
133133 // G_cov = -K P (symmetric).
134134 const float ca = -(ka * p00 + kb * p10);
135135 const float cb = -(ka * p01 + kb * p11);
136136 const float cc = -(kb * p01 + kc * p11);
137137
138138 // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M.
139139 const float3 dm0 = 2.0f * (ca * s0 + cb * s1);
140140 const float3 dm1 = 2.0f * (cb * s0 + cc * s1);
141141 // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T.
142142 Sym3 dsig;
143143 dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x;
144144 dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y;
145145 dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z;
146146 dsig.xy =
147147 ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y;
148148 dsig.xz =
149149 ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z;
150150 dsig.yz =
151151 ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z;
152152
153153 // --- M = J W: dJ = dM W^T (only the nonzero entries of J).
154154 const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2));
155155 const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2));
156156 const float tz2 = tz * tz, tz3 = tz2 * tz;
157157 float3 dt;
158158 dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02;
159159 dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12;
160160 dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 +
161161 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12;
162162 // --- Screen-space mean u = fx x / z + cx.
163163 dt.x += cam.fx / tz * g.xy.x;
164164 dt.y += cam.fy / tz * g.xy.y;
165165 dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2;
166166 dmean += MulT(w, dt);
167167 dmean_out = dmean;
168168
169169 // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A.
170170 const float sv[3] = {scale.x, scale.y, scale.z};
171171 float ds[3];
172172 Mat3 dr;
173173 #pragma unroll
174174 for (int j = 0; j < 3; ++j) {
175175 const float3 r_col = r.col(j);
176176 const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col);
177177 ds[j] = dot(da_col, r_col);
178178 dr.m[0][j] = da_col.x * sv[j];
179179 dr.m[1][j] = da_col.y * sv[j];
180180 dr.m[2][j] = da_col.z * sv[j];
181181 }
182182 dscale_out = {ds[0], ds[1], ds[2]};
183183
184184 // --- R(q) -> q.
185185 const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w;
186186 const auto& G = dr.m;
187187 dquat_out = {
188188 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] -
189189 qy * G[2][0] + qx * G[2][1]),
190190 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] -
191191 qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]),
192192 2.0f *
193193 (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] +
194194 qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]),
195195 2.0f *
196196 (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] -
197197 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])};
198198 }
199199
200200 // Reverse traversal per pixel; accumulates splat gradients with atomics.
201201 template <int kRows>
202202 __global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 4)
203203 RasterBackwardKernel(const int2* __restrict__ ranges,
204204 const int32_t* __restrict__ point_list,
205205 const Splat* __restrict__ splats, Frame frame,
206206 const float* __restrict__ final_t,
207207 const int32_t* __restrict__ n_contrib,
208208 const float* __restrict__ dl_dimage,
209209 float* __restrict__ splat_grads,
210210 uint8_t* __restrict__ splat_touched) {
211211 using L = RasterLayout<kRows>;
212212 constexpr int kRasterRows = kRows;
213213 constexpr int kRasterThreads = L::kThreads;
214214 constexpr int kWarps = L::kWarps;
215215 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
216216 const int tid = threadIdx.x;
217217 const int warp = tid / 32;
218218 const int lane = tid % 32;
219219 const RasterPixels px = RasterPixels::For<kRows>(tid);
220220 const float fx = static_cast<float>(px.x);
221221 const int2 range = ranges[tile];
222222
223223 struct PixelState {
224224 float t;
225225 float3 dpix;
226226 float accum; // dot(color accumulated behind, dpix)
227227 int last;
228228 };
229229 PixelState st[kRasterRows];
230230 const int pixels = frame.width * frame.height;
231231 int thread_last = 0;
232232 #pragma unroll
233233 for (int k = 0; k < kRasterRows; ++k) {
234234 const int y = px.y0 + k;
235235 const bool inside = px.x < frame.width && y < frame.height;
236236 const int p = y * frame.width + px.x;
237237 st[k].t = inside ? final_t[p] : 0.0f;
238238 st[k].dpix =
239239 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],
240240 dl_dimage[2 * pixels + p]}
241241 : float3{0.0f, 0.0f, 0.0f};
242242 st[k].accum = 0.0f;
243243 st[k].last = inside ? n_contrib[p] : 0;
244244 thread_last = max(thread_last, st[k].last);
245245 }
246246
247247 int warp_max_last = thread_last;
248248 #pragma unroll
249249 for (int offset = 16; offset > 0; offset /= 2) {
250250 warp_max_last = max(
251251 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));
252252 }
253253 __shared__ int shared_max_last[kWarps];
254254 if (lane == 0) shared_max_last[warp] = warp_max_last;
255255 __syncthreads();
256256 int block_max_last = 0;
257257 #pragma unroll
258258 for (int w = 0; w < kWarps; ++w) {
259259 block_max_last = max(block_max_last, shared_max_last[w]);
260260 }
261− if (block_max_last <= 0) return;
261+ if (block_max_last <= 0) {
262+ return;
263+ }
262264
263265 __shared__ float4 batch_geom0[kRasterThreads];
264− __shared__ float2 batch_geom1[kRasterThreads];
266+ __shared__ float batch_geom1[kRasterThreads];
265267 __shared__ float4 batch_rgb_o[kRasterThreads];
266268 __shared__ uint32_t batch_id[kRasterThreads];
267269 __shared__ uint8_t batch_mask[kRasterThreads];
268270 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
↕ Expand 24 unchanged lines (lines 271–294) · in PixelState
269271 int scatter = -1;
270272 int scatter2 = -1;
271273 if constexpr (L::kWarpWidth == 8) {
272274 const int sub_lane = lane & 15;
273275 const int row = sub_lane >> 3, col = sub_lane & 7;
274276 if (col == 0) {
275277 scatter2 = row;
276278 } else if (col == 2) {
277279 scatter2 = 2 + row;
278280 } else if (col == 4) {
279281 scatter2 = row == 0 ? 4 : 7;
280282 } else if (col == 6) {
281283 scatter2 = row == 0 ? 6 : 8;
282284 } else if (col == 1 && row == 0) {
283285 scatter2 = 5;
284286 }
285287 const int full_row = lane >> 3, full_col = lane & 7;
286288 if (full_col == 0) {
287289 scatter = full_row;
288290 } else if (full_col == 2) {
289291 scatter = full_row == 0 ? 4 : 5 + full_row;
290292 } else if (full_col == 4 && full_row == 0) {
291293 scatter = 5;
292294 }
293295 } else {
294296 scatter = warp::ScatterIndex(kSplatGradFloats, lane);
295297 }
296298
299+ // Evaluates one splat at this thread's pixels and accumulates the per-thread
300+ // partial gradient sums. Branch-free: lanes of a warp almost never agree on
301+ // which pixels a splat reaches, so predication is cheaper than branching.
302+ // Pixels the splat does not reach get alpha = 0, which leaves t and accum
303+ // exactly unchanged (t * rcp(1) == t) and contributes zero gradient.
297− auto eval_splat = [&](int j, float4 g0, float2 g1, float4 ro, int entry,
304+ auto eval_splat = [&](float4 g0, float c2, float4 ro, int entry, float& dx,
298− float& dx, float& ga_sum, float& ga_dy, float& ga_dy2,
305+ float& ga_sum, float& ga_dy, float& ga_dy2,
299306 float3& drgb) {
300− dx = g0.x - fx;
307+ dx = __fsub_rn(g0.x, fx);
308+ const Power2Column col = Power2Column::At(g0.z, g0.w, dx);
301309 ga_sum = 0.0f;
302310 ga_dy = 0.0f;
303311 ga_dy2 = 0.0f;
304312 drgb = {0.0f, 0.0f, 0.0f};
305− if (entry < thread_last) {
306− const float cx_dx2 = g0.z * dx * dx;
307− const float cy_dx = g0.w * dx;
308313 #pragma unroll
309314⇥ for (int k = 0; k < kRasterRows; ++k) {
310315⇥ PixelState& ps = st[k];
311− if (entry >= ps.last) continue;
312− const float dy = g0.y - static_cast<float>(px.y0 + k);
316+ const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));
313− const float power = cx_dx2 + g1.x * dy * dy + cy_dx * dy;
317+ const float power = col.Row(c2, dy);
314− if (power > 0.0f || power < g1.y) continue;
315318⇥ const float g_val = Exp2Approx(power);
316− const float alpha = fminf(kMaxAlpha, ro.w * g_val);
319+ const float alpha_k = SplatAlpha(ro.w, g_val);
320+ const bool hit = entry < ps.last && power <= 0.0f && alpha_k >= kMinAlpha;
317− if (alpha < kMinAlpha) continue;
321+ const float alpha = hit ? alpha_k : 0.0f;
318− ps.t = __fdividef(ps.t, 1.0f - alpha);
322+ ps.t = ps.t * RcpApprox(1.0f - alpha);
319323⇥ const float w = alpha * ps.t;
320324⇥ const float color_dot =
321325⇥ ro.x * ps.dpix.x + ro.y * ps.dpix.y + ro.z * ps.dpix.z;
322326⇥ const float diff = color_dot - ps.accum;
323− const float dl_dalpha = ps.t * diff;
324327⇥ ps.accum += alpha * diff;
325− const float ga = g_val * dl_dalpha;
328+ const float ga = hit ? g_val * (ps.t * diff) : 0.0f;
326329⇥ ga_sum += ga;
327330⇥ const float ga_y = ga * dy;
328331⇥ ga_dy += ga_y;
329332⇥ ga_dy2 += ga_y * dy;
330333⇥ drgb += w * ps.dpix;
331334⇥ }
332− }
333335 };
336+ // Conic (a, b, c) from the staged log2-scaled one.
334− auto raw_conic = [](float4 g0, float2 g1) {
337+ auto raw_conic = [](float4 g0, float c2) {
335338 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,
336− kInvPower2Scale * g1.x};
339+ kInvPower2Scale * c2};
337340 };
338341
339342 for (int end = block_max_last; end > 0; end -= kRasterThreads) {
340343 const int e = end - 1 - tid;
341344 if (e >= 0) {
342345 const uint32_t packed = static_cast<uint32_t>(point_list[range.x + e]);
343346 const uint32_t id = packed & 0x00ffffffu;
344347 splat_touched[id] = 1u;
345348 const float4 p0 = __ldg(&splats[id].p0);
346349 const float4 p1 = __ldg(&splats[id].p1);
347350 const float4 p2 = __ldg(&splats[id].p2);
348351 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
349352 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
350− batch_geom1[tid] = {c2.z, p2.w};
353+ batch_geom1[tid] = c2.z;
351354 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};
352355 batch_id[tid] = id;
353356 batch_mask[tid] = static_cast<uint8_t>(packed >> 24);
354357 } else {
↕ Expand 14 unchanged lines (lines 358–371) · in PixelState
355358 batch_mask[tid] = 0u;
356359 }
357360 __syncthreads();
358361 const int count = min(kRasterThreads, end);
359362 #pragma unroll
360363 for (int c = 0; c < kRasterThreads; c += 32) {
361364 if (c < count && end - 32 - c < warp_max_last) {
362365 uint32_t mask = __ballot_sync(
363366 warp::kFullMask,
364367 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
365368 if constexpr (L::kWarpWidth == 8) {
366369 while ((mask & (mask - 1u)) != 0u) {
367370 const int bit0 = __ffs(mask) - 1;
368371 mask &= mask - 1u;
369372 const int bit1 = __ffs(mask) - 1;
370373 mask &= mask - 1u;
371374 const int j0 = c + bit0, j1 = c + bit1;
372375 const float4 g0_0 = batch_geom0[j0];
373− const float2 g1_0 = batch_geom1[j0];
376+ const float c2_0 = batch_geom1[j0];
374377 const float4 ro_0 = batch_rgb_o[j0];
375378 const float4 g0_1 = batch_geom0[j1];
376− const float2 g1_1 = batch_geom1[j1];
379+ const float c2_1 = batch_geom1[j1];
377380 const float4 ro_1 = batch_rgb_o[j1];
378381
379382 float dx0, ga_sum0, ga_dy0, ga_dy2_0;
380383 float3 drgb0;
381− eval_splat(j0, g0_0, g1_0, ro_0, end - 1 - j0, dx0, ga_sum0, ga_dy0,
384+ eval_splat(g0_0, c2_0, ro_0, end - 1 - j0, dx0, ga_sum0, ga_dy0,
382385 ga_dy2_0, drgb0);
383386
384387 float dx1, ga_sum1, ga_dy1, ga_dy2_1;
385388 float3 drgb1;
386− eval_splat(j1, g0_1, g1_1, ro_1, end - 1 - j1, dx1, ga_sum1, ga_dy1,
389+ eval_splat(g0_1, c2_1, ro_1, end - 1 - j1, dx1, ga_sum1, ga_dy1,
387390 ga_dy2_1, drgb1);
388391
389392 const bool hi16 = (lane & 16) != 0;
390393 const float opacity = hi16 ? ro_1.w : ro_0.w;
391394 const float dx = hi16 ? dx1 : dx0;
392395 const float3 cn =
393− hi16 ? raw_conic(g0_1, g1_1) : raw_conic(g0_0, g1_0);
396+ hi16 ? raw_conic(g0_1, c2_1) : raw_conic(g0_0, c2_0);
394397
395398 auto fold16 = [&](float v0, float v1) {
396399 const float send = hi16 ? v0 : v1;
397400 const float recv = __shfl_xor_sync(warp::kFullMask, send, 16);
↕ Expand 34 unchanged lines (lines 401–434) · in PixelState
398401 return (hi16 ? v1 : v0) + recv;
399402 };
400403 float ga_sum = fold16(ga_sum0, ga_sum1);
401404 float ga_dy = fold16(ga_dy0, ga_dy1);
402405 float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1),
403406 fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y),
404407 fold16(drgb0.z, drgb1.z)};
405408 warp::internal::HalvingStep<4, 8>(b4, lane);
406409 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
407410 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
408411
409412 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
410413 const float p_dx = dx * p_sum;
411414 const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
412415 : -(cn.y * p_dx + cn.z * p_dy);
413416 const float v_dx1 =
414417 (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
415418 float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]};
416419 warp::internal::HalvingStep<4, 4>(v4, lane);
417420 warp::internal::HalvingStep<2, 2>(v4, lane);
418421 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4);
419422 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2);
420423 const float v_sum =
421424 v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1);
422425 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1);
423426 const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum;
424427 if (scatter2 >= 0 && out_grad != 0.0f) {
425428 const uint32_t target_id = batch_id[hi16 ? j1 : j0];
426429 atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2,
427430 out_grad);
428431 }
429432 }
430433 }
431434 while (mask != 0u) {
432435 const int bit = __ffs(mask) - 1;
433436 mask &= mask - 1u;
434437 const int j = c + bit;
435438 const float4 g0 = batch_geom0[j];
436− const float2 g1 = batch_geom1[j];
439+ const float c2 = batch_geom1[j];
437440 const float4 ro = batch_rgb_o[j];
438441 float dx, ga_sum, ga_dy, ga_dy2;
439442 float3 drgb;
440− eval_splat(j, g0, g1, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2,
443+ eval_splat(g0, c2, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2, drgb);
441− drgb);
442444 const float opacity = ro.w;
443− const float3 cn = raw_conic(g0, g1);
445+ const float3 cn = raw_conic(g0, c2);
444446 float out_grad = 0.0f;
445447 if constexpr (L::kWarpWidth == 8) {
446448 float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z};
447449 warp::internal::HalvingStep<4, 16>(b4, lane);
↕ Expand 197 unchanged lines (lines 450–646) · in PreprocessBackwardKernel
448450 warp::internal::HalvingStep<2, 8>(b4, lane);
449451 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16);
450452 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16);
451453 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
452454 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
453455 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
454456 const float p_dx = dx * p_sum;
455457 float v_dx;
456458 if ((lane & 16) == 0) {
457459 v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
458460 : -(cn.y * p_dx + cn.z * p_dy);
459461 } else {
460462 v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
461463 }
462464 float v[3] = {v_dx, b4[0], ga_sum};
463465 warp::internal::HalvingStep<3, 4>(v, lane);
464466 warp::internal::HalvingStep<2, 2>(v, lane);
465467 out_grad = v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1);
466468 } else {
467469 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
468470 const float p_dx = dx * p_sum;
469471 float gr[kSplatGradFloats] = {-(cn.x * p_dx + cn.y * p_dy), // xy
470472 -(cn.y * p_dx + cn.z * p_dy),
471473 -0.5f * dx * p_dx, // conic
472474 -dx * p_dy,
473475 -0.5f * opacity * ga_dy2,
474476 ga_sum, // opacity
475477 drgb.x,
476478 drgb.y,
477479 drgb.z}; // rgb
478480 warp::ReduceScatter(gr, lane);
479481 out_grad = gr[0];
480482 }
481483 if (scatter >= 0 && out_grad != 0.0f) {
482484 atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter,
483485 out_grad);
484486 }
485487 }
486488 }
487489 }
488490 __syncthreads();
489491 }
490492 }
491493
492494 // ---------------------------------------------------------------------------
493495 // Preprocess backward: splat gradients -> parameter gradients (dense).
494496 // Uses the 1-byte `splat_touched` flag written by `RasterBackwardKernel` to
495497 // skip reading `splat_grads` and `params.sh` for untouched Gaussians, and
496498 // zeros untouched SH rows with warp-coalesced float4 stores (0 shared memory).
497499
498500 template <typename ShT>
499501 __global__ void __launch_bounds__(kGaussianThreads)
500502 PreprocessBackwardKernel(GaussianParams params, Frame frame,
501503 const float* __restrict__ splat_grads,
502504 const uint8_t* __restrict__ splat_touched,
503505 GaussianGrads grads) {
504506 constexpr int kShVec16 = ShIo<ShT>::kRowVec16; // 16-byte stores per row.
505507 const int i = blockIdx.x * blockDim.x + threadIdx.x;
506508 const int lane = threadIdx.x % 32;
507509 bool touched = (i < params.n && splat_touched[i] != 0u);
508510 const uint32_t init_mask = __ballot_sync(warp::kFullMask, touched);
509511 if (init_mask == 0u) {
510512 if (i < params.n) {
511513 grads.means[3 * i] = 0.0f;
512514 grads.means[3 * i + 1] = 0.0f;
513515 grads.means[3 * i + 2] = 0.0f;
514516 grads.scales[3 * i] = 0.0f;
515517 grads.scales[3 * i + 1] = 0.0f;
516518 grads.scales[3 * i + 2] = 0.0f;
517519 reinterpret_cast<float4*>(grads.quats)[i] = {0.0f, 0.0f, 0.0f, 0.0f};
518520 grads.opacities[i] = 0.0f;
519521 }
520522 const int warp_i0 =
521523 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;
522524 const int warp_n = max(0, min(32, params.n - warp_i0));
523525 uint4* warp_sh_dst =
524526 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));
525527 const uint4 zero = {0u, 0u, 0u, 0u};
526528 if (warp_n == 32) {
527529 #pragma unroll
528530 for (int s = 0; s < kShVec16; ++s) {
529531 warp_sh_dst[s * 32 + lane] = zero;
530532 }
531533 } else {
532534 for (int k = lane; k < warp_n * kShVec16; k += 32) {
533535 warp_sh_dst[k] = zero;
534536 }
535537 }
536538 return;
537539 }
538540
539541 float3 dmean = {0.0f, 0.0f, 0.0f};
540542 float3 dscale = {0.0f, 0.0f, 0.0f};
541543 float4 dquat = {0.0f, 0.0f, 0.0f, 0.0f};
542544 float dopacity = 0.0f;
543545 if (touched) {
544546 float g[kSplatGradFloats];
545547 bool nonzero = false;
546548 #pragma unroll
547549 for (int q = 0; q < kSplatGradFloats; ++q) {
548550 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);
549551 nonzero = nonzero || g[q] != 0.0f;
550552 }
551553 touched = nonzero;
552554 if (touched) {
553555 ProjectionGrad pg;
554556 pg.xy = {g[0], g[1]};
555557 pg.conic = {g[2], g[3], g[4]};
556558 pg.opacity = g[5];
557559 pg.rgb = {g[6], g[7], g[8]};
558560 dopacity = g[5];
559561 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),
560562 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);
561563 }
562564 }
563565 if (i < params.n) {
564566 grads.means[3 * i] = dmean.x;
565567 grads.means[3 * i + 1] = dmean.y;
566568 grads.means[3 * i + 2] = dmean.z;
567569 grads.scales[3 * i] = dscale.x;
568570 grads.scales[3 * i + 1] = dscale.y;
569571 grads.scales[3 * i + 2] = dscale.z;
570572 reinterpret_cast<float4*>(grads.quats)[i] = dquat;
571573 grads.opacities[i] = dopacity;
572574 }
573575 const uint32_t mask = __ballot_sync(warp::kFullMask, touched);
574576 if (mask != warp::kFullMask) {
575577 const int warp_i0 =
576578 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;
577579 const int warp_n = max(0, min(32, params.n - warp_i0));
578580 uint4* warp_sh_dst =
579581 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));
580582 const uint4 zero = {0u, 0u, 0u, 0u};
581583 if (warp_n == 32) {
582584 #pragma unroll
583585 for (int s = 0; s < kShVec16; ++s) {
584586 const int k = s * 32 + lane;
585587 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;
586588 if (((mask >> row_in_warp) & 1u) == 0u) {
587589 warp_sh_dst[k] = zero;
588590 }
589591 }
590592 } else {
591593 for (int k = lane; k < warp_n * kShVec16; k += 32) {
592594 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;
593595 if (((mask >> row_in_warp) & 1u) == 0u) {
594596 warp_sh_dst[k] = zero;
595597 }
596598 }
597599 }
598600 }
599601 }
600602
601603 template <typename ShT>
602604 cudaError_t LaunchPreprocessBackward(const GaussianParams& params,
603605 const Frame& frame,
604606 const float* splat_grads,
605607 const uint8_t* splat_touched,
606608 const GaussianGrads& grads,
607609 cudaStream_t stream) {
608610 PreprocessBackwardKernel<ShT>
609611 <<<GaussianBlocks(params.n), kGaussianThreads, 0, stream>>>(
610612 params, frame, splat_grads, splat_touched, grads);
611613 return cudaSuccess;
612614 }
613615
614616 template __global__ void RasterBackwardKernel<1>(
615617 const int2* __restrict__, const int32_t* __restrict__,
616618 const Splat* __restrict__, Frame, const float* __restrict__,
617619 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
618620 uint8_t* __restrict__);
619621 template __global__ void RasterBackwardKernel<2>(
620622 const int2* __restrict__, const int32_t* __restrict__,
621623 const Splat* __restrict__, Frame, const float* __restrict__,
622624 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
623625 uint8_t* __restrict__);
624626 template __global__ void RasterBackwardKernel<4>(
625627 const int2* __restrict__, const int32_t* __restrict__,
626628 const Splat* __restrict__, Frame, const float* __restrict__,
627629 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
628630 uint8_t* __restrict__);
629631 template __global__ void PreprocessBackwardKernel<float>(
630632 GaussianParams, Frame, const float* __restrict__,
631633 const uint8_t* __restrict__, GaussianGrads);
632634 template __global__ void PreprocessBackwardKernel<ShBf16>(
633635 GaussianParams, Frame, const float* __restrict__,
634636 const uint8_t* __restrict__, GaussianGrads);
635637 template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,
636638 const Frame&, const float*,
637639 const uint8_t*,
638640 const GaussianGrads&,
639641 cudaStream_t);
640642 template cudaError_t LaunchPreprocessBackward<ShBf16>(
641643 const GaussianParams&, const Frame&, const float*, const uint8_t*,
642644 const GaussianGrads&, cudaStream_t);
643645
644646 } // namespace gs
Unchanged files in s05_branchless_predication (identical to s04_raster_pruning): csrc/config.h, csrc/forward.h, csrc/backward.h, csrc/rasterizer_impl.h, csrc/rasterizer_impl.cu.cc