← Back to Step 6 in blog post

s05_branchless_predication → s06_multirow_and_pipe_math

Step 6: Multi-Row 8×8 Square Warps, Per-Warp Rings & Pipe-Balanced Math  ·  +148 / −121 lines across 4 changed files

csrc/auxiliary.h +12 −12
s05_branchless_predication/csrc/auxiliary.h
s06_multirow_and_pipe_math/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, 0)150// p2 = (r, g, b, log2(opacity))
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 538 unchanged lines (lines 155–692) · 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// ---------------------------------------------------------------------------679// ---------------------------------------------------------------------------
680// Rasterization. One block per 16x16 tile. Each thread shades `kRows`680// Rasterization. One block per 16x16 tile. Each thread shades `kRows`
681// vertically adjacent pixels, so that per-splat costs (shared-memory loads,681// vertically adjacent pixels, so that per-splat costs (shared-memory loads,
682// barriers, the warp reduction in the backward pass) are paid once per682// barriers, the warp reduction in the backward pass) are paid once per
683// `kRows` pixels; more rows per thread also means fewer resident threads and683// `kRows` pixels; more rows per thread also means fewer resident threads and
684// more waiting on the slowest pixel. A warp covers a compact684// more waiting on the slowest pixel. A warp covers a compact
685// kWarpWidth x kWarpRows region (as square as possible, so per-warp culling685// kWarpWidth x kWarpRows region (as square as possible, so per-warp culling
686// 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
687// shared memory, one per thread per batch.687// shared memory, one per thread per batch.
688688
689inline constexpr int kRasterRowChoices[] = {1, 2, 4};689inline constexpr int kRasterRowChoices[] = {1, 2, 4};
690690
691// Per-pixel splat evaluation shared by the forward and backward rasterizers.691// Per-pixel splat evaluation shared by the forward and backward rasterizers.
692// They must agree bit for bit on every alpha (the backward recovers692// They must agree bit for bit on every alpha (the backward recovers
693// transmittance by dividing alphas back out), so the rounding of every step693// transmittance by dividing alphas back out), so the rounding of every step
694// is pinned with explicit `_rn` intrinsics.694// is pinned with explicit `_rn` intrinsics.
695//695//
696// The falloff is evaluated in log2 units, power2 = A dx^2 + B dx dy + C dy^2696// 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:697// with (A, B, C) = -log2(e)/2 (a, 2b, c), so that the Gaussian is 2^power2.
698// log2(opacity) (stored in `Splat::p2.w`) is folded into the per-column
699// constant, so 2^power is directly the unclamped alpha = opacity * Gaussian:
698// two FMAs and one MUFU.EX2 per pixel. A pixel receives the splat iff700// two FMAs and one MUFU.EX2 per pixel, no multiply. A pixel receives the
699// power2 <= 0 and alpha >= kMinAlpha (the latter implies power2 >= the701// splat iff alpha >= kMinAlpha (which implies power2 >= the splat's culling
700// splat's culling bound, see `AlphaLimit`).702// bound, see `AlphaLimit`).
701inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2703inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2
702inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;704inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;
703705
704GS_HD float3 Power2Conic(float a, float b, float c) {706GS_HD float3 Power2Conic(float a, float b, float c) {
705 return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};707 return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};
706}708}
707709
708// Splat terms of one pixel column at dx = splat.x - pixel.x.710// Splat terms of one pixel column at dx = splat.x - pixel.x.
709struct Power2Column {711struct Power2Column {
710 float pa, pb; // A dx^2, B dx.712 float pa, pb; // A dx^2 + log2(opacity), B dx.
711 GS_D static Power2Column At(float a2, float b2, float dx) {713 GS_D static Power2Column At(float a2, float b2, float log2_opacity,
714 float dx) {
712 return {__fmul_rn(__fmul_rn(a2, dx), dx), __fmul_rn(b2, dx)};715 return {__fmaf_rn(__fmul_rn(a2, dx), dx, log2_opacity), __fmul_rn(b2, dx)};
713 }716 }
717 // log2 of the unclamped alpha at dy = splat.y - pixel.y.
714 GS_D float Row(float c2, float dy) const {718 GS_D float Row(float c2, float dy) const {
715 return __fmaf_rn(__fmaf_rn(c2, dy, pb), dy, pa);719 return __fmaf_rn(__fmaf_rn(c2, dy, pb), dy, pa);
716 }720 }
717};721};
↕ Expand 8 unchanged lines (lines 722–729) · in Exp2Approx
718722
719// 2^x and 1/x without the denormal range fix-ups of exp2f/__fdividef (their723// 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).724// inputs and results here are never denormal where it matters).
721GS_D float Exp2Approx(float x) {725GS_D float Exp2Approx(float x) {
722 float y;726 float y;
723 asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));727 asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
724 return y;728 return y;
725}729}
726GS_D float RcpApprox(float x) {730GS_D float RcpApprox(float x) {
727 float y;731 float y;
728 asm("rcp.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));732 asm("rcp.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
729 return y;733 return y;
730}
731
732GS_D float SplatAlpha(float opacity, float gaussian) {
733 return fminf(kMaxAlpha, __fmul_rn(opacity, gaussian));
734}734}
735735
736template <int kRows>736template <int kRows>
737struct RasterLayout {737struct RasterLayout {
↕ Expand 106 unchanged lines (lines 738–843) · in WarpMask
738 static_assert(kRows == 1 || kRows == 2 || kRows == 4);738 static_assert(kRows == 1 || kRows == 2 || kRows == 4);
739 static constexpr int kThreads = kTilePixels / kRows;739 static constexpr int kThreads = kTilePixels / kRows;
740 static constexpr int kWarps = kThreads / 32;740 static constexpr int kWarps = kThreads / 32;
741 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;741 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;
742 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;742 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;
743 static constexpr int kWarpsX = kTileSize / kWarpWidth;743 static constexpr int kWarpsX = kTileSize / kWarpWidth;
744 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);744 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);
745745
746 // Tile-relative origin of warp `w`'s region.746 // Tile-relative origin of warp `w`'s region.
747 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; }
748 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; }
749};749};
750750
751// 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.
752struct RasterPixels {752struct RasterPixels {
753 int x, y0;753 int x, y0;
754 template <int kRows>754 template <int kRows>
755 GS_D static RasterPixels For(int tid) {755 GS_D static RasterPixels For(int tid) {
756 using L = RasterLayout<kRows>;756 using L = RasterLayout<kRows>;
757 const int warp = tid / 32, lane = tid % 32;757 const int warp = tid / 32, lane = tid % 32;
758 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +758 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +
759 lane % L::kWarpWidth,759 lane % L::kWarpWidth,
760 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +760 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +
761 (lane / L::kWarpWidth) * kRows};761 (lane / L::kWarpWidth) * kRows};
762 }762 }
763};763};
764764
765// 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
766// pixel region of warp w of this block's tile.766// pixel region of warp w of this block's tile.
767template <int kRows>767template <int kRows>
768GS_D uint32_t WarpMask(float4 p0, float4 p1) {768GS_D uint32_t WarpMask(float4 p0, float4 p1) {
769 using L = RasterLayout<kRows>;769 using L = RasterLayout<kRows>;
770 constexpr int kWarpsY = L::kWarps / L::kWarpsX;770 constexpr int kWarpsY = L::kWarps / L::kWarpsX;
771 float limit = p1.z;771 float limit = p1.z;
772 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;772 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;
773 const float a = p0.z, b = p0.w, c = p1.x;773 const float a = p0.z, b = p0.w, c = p1.x;
774 const float det = a * c - b * b;774 const float det = a * c - b * b;
775 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;
776 const float l_over_det = limit / det;776 const float l_over_det = limit / det;
777 const float rx = sqrtf(c * l_over_det);777 const float rx = sqrtf(c * l_over_det);
778 const float ry = sqrtf(a * l_over_det);778 const float ry = sqrtf(a * l_over_det);
779 const float2 xy = {p0.x, p0.y};779 const float2 xy = {p0.x, p0.y};
780 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));780 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));
781 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));781 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));
782 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));782 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));
783 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));783 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));
784 if (x0 > x1 || y0 > y1) return 0;784 if (x0 > x1 || y0 > y1) return 0;
785 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;785 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;
786 uint32_t mx = 0u;786 uint32_t mx = 0u;
787#pragma unroll787#pragma unroll
788 for (int wx = 0; wx < L::kWarpsX; ++wx) {788 for (int wx = 0; wx < L::kWarpsX; ++wx) {
789 const int wx0 = tx + wx * L::kWarpWidth;789 const int wx0 = tx + wx * L::kWarpWidth;
790 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);790 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);
791 }791 }
792 if (mx == 0u) return 0;792 if (mx == 0u) return 0;
793 uint32_t my = 0u;793 uint32_t my = 0u;
794#pragma unroll794#pragma unroll
795 for (int wy = 0; wy < kWarpsY; ++wy) {795 for (int wy = 0; wy < kWarpsY; ++wy) {
796 const int wy0 = ty + wy * L::kWarpRows;796 const int wy0 = ty + wy * L::kWarpRows;
797 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);797 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);
798 }798 }
799 if (my == 0u) return 0;799 if (my == 0u) return 0;
800 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {800 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {
801 uint32_t mask = 0u;801 uint32_t mask = 0u;
802#pragma unroll802#pragma unroll
803 for (int wy = 0; wy < kWarpsY; ++wy) {803 for (int wy = 0; wy < kWarpsY; ++wy) {
804 if (!((my >> wy) & 1u)) continue;804 if (!((my >> wy) & 1u)) continue;
805#pragma unroll805#pragma unroll
806 for (int wx = 0; wx < L::kWarpsX; ++wx) {806 for (int wx = 0; wx < L::kWarpsX; ++wx) {
807 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));807 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));
808 }808 }
809 }809 }
810 return mask;810 return mask;
811 }811 }
812 EllipseBounds eb;812 EllipseBounds eb;
813 eb.a = a;813 eb.a = a;
814 eb.b = b;814 eb.b = b;
815 eb.c = c;815 eb.c = c;
816 eb.det = det;816 eb.det = det;
817 eb.inv_a = 1.0f / a;817 eb.inv_a = 1.0f / a;
818 eb.a_limit = a * limit;818 eb.a_limit = a * limit;
819 eb.y_ext = ry;819 eb.y_ext = ry;
820 eb.dy_peak = b * sqrtf(l_over_det / c);820 eb.dy_peak = b * sqrtf(l_over_det / c);
821 uint32_t mask = 0u;821 uint32_t mask = 0u;
822#pragma unroll822#pragma unroll
823 for (int wy = 0; wy < kWarpsY; ++wy) {823 for (int wy = 0; wy < kWarpsY; ++wy) {
824 if (!((my >> wy) & 1u)) continue;824 if (!((my >> wy) & 1u)) continue;
825 const int wy0 = ty + wy * L::kWarpRows;825 const int wy0 = ty + wy * L::kWarpRows;
826 int x0_px, x1_px;826 int x0_px, x1_px;
827 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)) {
828 continue;828 continue;
829 }829 }
830#pragma unroll830#pragma unroll
831 for (int wx = 0; wx < L::kWarpsX; ++wx) {831 for (int wx = 0; wx < L::kWarpsX; ++wx) {
832 const int wx0 = tx + wx * L::kWarpWidth;832 const int wx0 = tx + wx * L::kWarpWidth;
833 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {833 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {
834 mask |= 1u << (wy * L::kWarpsX + wx);834 mask |= 1u << (wy * L::kWarpsX + wx);
835 }835 }
836 }836 }
837 }837 }
838 return mask;838 return mask;
839}839}
840840
841} // namespace gs841} // namespace gs
842842
843#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, 0)
150+// p2 = (r, g, b, log2(opacity))
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 538 unchanged lines (lines 155–692) · 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
679679 // ---------------------------------------------------------------------------
680680 // Rasterization. One block per 16x16 tile. Each thread shades `kRows`
681681 // vertically adjacent pixels, so that per-splat costs (shared-memory loads,
682682 // barriers, the warp reduction in the backward pass) are paid once per
683683 // `kRows` pixels; more rows per thread also means fewer resident threads and
684684 // more waiting on the slowest pixel. A warp covers a compact
685685 // kWarpWidth x kWarpRows region (as square as possible, so per-warp culling
686686 // is tight); warps tile the 16x16 tile row-major. Splats are staged through
687687 // shared memory, one per thread per batch.
688688
689689 inline constexpr int kRasterRowChoices[] = {1, 2, 4};
690690
691691 // Per-pixel splat evaluation shared by the forward and backward rasterizers.
692692 // They must agree bit for bit on every alpha (the backward recovers
693693 // transmittance by dividing alphas back out), so the rounding of every step
694694 // is pinned with explicit `_rn` intrinsics.
695695 //
696696 // 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:
697+// with (A, B, C) = -log2(e)/2 (a, 2b, c), so that the Gaussian is 2^power2.
698+// log2(opacity) (stored in `Splat::p2.w`) is folded into the per-column
699+// constant, so 2^power is directly the unclamped alpha = opacity * Gaussian:
698−// two FMAs and one MUFU.EX2 per pixel. A pixel receives the splat iff
700+// two FMAs and one MUFU.EX2 per pixel, no multiply. A pixel receives the
699−// power2 <= 0 and alpha >= kMinAlpha (the latter implies power2 >= the
701+// splat iff alpha >= kMinAlpha (which implies power2 >= the splat's culling
700−// splat's culling bound, see `AlphaLimit`).
702+// bound, see `AlphaLimit`).
701703 inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2
702704 inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale;
703705
704706 GS_HD float3 Power2Conic(float a, float b, float c) {
705707 return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c};
706708 }
707709
708710 // Splat terms of one pixel column at dx = splat.x - pixel.x.
709711 struct Power2Column {
710− float pa, pb; // A dx^2, B dx.
712+ float pa, pb; // A dx^2 + log2(opacity), B dx.
711− GS_D static Power2Column At(float a2, float b2, float dx) {
713+ GS_D static Power2Column At(float a2, float b2, float log2_opacity,
714+ float dx) {
712− return {__fmul_rn(__fmul_rn(a2, dx), dx), __fmul_rn(b2, dx)};
715+ return {__fmaf_rn(__fmul_rn(a2, dx), dx, log2_opacity), __fmul_rn(b2, dx)};
713716 }
717+ // log2 of the unclamped alpha at dy = splat.y - pixel.y.
714718 GS_D float Row(float c2, float dy) const {
715719 return __fmaf_rn(__fmaf_rn(c2, dy, pb), dy, pa);
716720 }
717721 };
↕ Expand 8 unchanged lines (lines 722–729) · in Exp2Approx
718722
719723 // 2^x and 1/x without the denormal range fix-ups of exp2f/__fdividef (their
720724 // inputs and results here are never denormal where it matters).
721725 GS_D float Exp2Approx(float x) {
722726 float y;
723727 asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
724728 return y;
725729 }
726730 GS_D float RcpApprox(float x) {
727731 float y;
728732 asm("rcp.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x));
729733 return y;
730−}
731−
732−GS_D float SplatAlpha(float opacity, float gaussian) {
733− return fminf(kMaxAlpha, __fmul_rn(opacity, gaussian));
734734 }
735735
736736 template <int kRows>
737737 struct RasterLayout {
↕ Expand 106 unchanged lines (lines 738–843) · in WarpMask
738738 static_assert(kRows == 1 || kRows == 2 || kRows == 4);
739739 static constexpr int kThreads = kTilePixels / kRows;
740740 static constexpr int kWarps = kThreads / 32;
741741 static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8;
742742 static constexpr int kWarpRows = (32 / kWarpWidth) * kRows;
743743 static constexpr int kWarpsX = kTileSize / kWarpWidth;
744744 static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps);
745745
746746 // Tile-relative origin of warp `w`'s region.
747747 GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; }
748748 GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; }
749749 };
750750
751751 // Pixel coordinates of this thread's first pixel; the others follow in y.
752752 struct RasterPixels {
753753 int x, y0;
754754 template <int kRows>
755755 GS_D static RasterPixels For(int tid) {
756756 using L = RasterLayout<kRows>;
757757 const int warp = tid / 32, lane = tid % 32;
758758 return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) +
759759 lane % L::kWarpWidth,
760760 static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) +
761761 (lane / L::kWarpWidth) * kRows};
762762 }
763763 };
764764
765765 // Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the
766766 // pixel region of warp w of this block's tile.
767767 template <int kRows>
768768 GS_D uint32_t WarpMask(float4 p0, float4 p1) {
769769 using L = RasterLayout<kRows>;
770770 constexpr int kWarpsY = L::kWarps / L::kWarpsX;
771771 float limit = p1.z;
772772 if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0;
773773 const float a = p0.z, b = p0.w, c = p1.x;
774774 const float det = a * c - b * b;
775775 if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0;
776776 const float l_over_det = limit / det;
777777 const float rx = sqrtf(c * l_over_det);
778778 const float ry = sqrtf(a * l_over_det);
779779 const float2 xy = {p0.x, p0.y};
780780 const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx));
781781 const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx));
782782 const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx));
783783 const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx));
784784 if (x0 > x1 || y0 > y1) return 0;
785785 const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize;
786786 uint32_t mx = 0u;
787787 #pragma unroll
788788 for (int wx = 0; wx < L::kWarpsX; ++wx) {
789789 const int wx0 = tx + wx * L::kWarpWidth;
790790 if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx);
791791 }
792792 if (mx == 0u) return 0;
793793 uint32_t my = 0u;
794794 #pragma unroll
795795 for (int wy = 0; wy < kWarpsY; ++wy) {
796796 const int wy0 = ty + wy * L::kWarpRows;
797797 if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy);
798798 }
799799 if (my == 0u) return 0;
800800 if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) {
801801 uint32_t mask = 0u;
802802 #pragma unroll
803803 for (int wy = 0; wy < kWarpsY; ++wy) {
804804 if (!((my >> wy) & 1u)) continue;
805805 #pragma unroll
806806 for (int wx = 0; wx < L::kWarpsX; ++wx) {
807807 if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx));
808808 }
809809 }
810810 return mask;
811811 }
812812 EllipseBounds eb;
813813 eb.a = a;
814814 eb.b = b;
815815 eb.c = c;
816816 eb.det = det;
817817 eb.inv_a = 1.0f / a;
818818 eb.a_limit = a * limit;
819819 eb.y_ext = ry;
820820 eb.dy_peak = b * sqrtf(l_over_det / c);
821821 uint32_t mask = 0u;
822822 #pragma unroll
823823 for (int wy = 0; wy < kWarpsY; ++wy) {
824824 if (!((my >> wy) & 1u)) continue;
825825 const int wy0 = ty + wy * L::kWarpRows;
826826 int x0_px, x1_px;
827827 if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) {
828828 continue;
829829 }
830830 #pragma unroll
831831 for (int wx = 0; wx < L::kWarpsX; ++wx) {
832832 const int wx0 = tx + wx * L::kWarpWidth;
833833 if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) {
834834 mask |= 1u << (wy * L::kWarpsX + wx);
835835 }
836836 }
837837 }
838838 return mask;
839839 }
840840
841841 } // namespace gs
842842
843843 #endif
csrc/forward.cu.cc +51 −40
s05_branchless_predication/csrc/forward.cu.cc
s06_multirow_and_pipe_math/csrc/forward.cu.cc
↕ Expand 93 unchanged lines (lines 1–93) · 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 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};
82 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)};
83 s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};83 s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};
84 ForEachTileRow<kCulling>(84 ForEachTileRow<kCulling>(
85 s, frame.grid, frame.width, frame.height,85 s, frame.grid, frame.width, frame.height,
86 [&](int, int tx0, int tx1) { count += tx1 - tx0; });86 [&](int, int tx0, int tx1) { count += tx1 - tx0; });
87 }87 }
88 if (count == 0) {88 if (count == 0) {
89 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};
90 splats[i] = s;90 splats[i] = s;
91 tile_counts[i] = 0;91 tile_counts[i] = 0;
92 return;92 return;
93 }93 }
94 const int vis_idx = atomicAdd(num_visible, 1);94 const int vis_idx = atomicAdd(num_visible, 1);
95 float inv_len;95 float inv_len;
96 const float3 rgb =96 const float3 rgb =
97 ShadeColorRow(ShRow<ShT>(params.sh, i), ViewDir(cam, mean, inv_len));97 ShadeColorRow(ShRow<ShT>(params.sh, i), ViewDir(cam, mean, inv_len));
98 // Visible splats have opacity >= ~1/255 (see `AlphaLimit`).
98 s.p2 = {rgb.x, rgb.y, rgb.z, s.p2.w};99 s.p2 = {rgb.x, rgb.y, rgb.z, log2f(s.p1.y)};
99 splats[i] = s;100 splats[i] = s;
100 tile_counts[i] = count;101 tile_counts[i] = count;
101 depth_keys[vis_idx] = AsUint(pr.depth);102 depth_keys[vis_idx] = AsUint(pr.depth);
102 ids[vis_idx] = i;103 ids[vis_idx] = i;
↕ Expand 16 unchanged lines (lines 104–119) · in RasterForwardKernel
103}104}
104105
105template <int kRows>106template <int kRows>
106__global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 8)107__global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 8)
107 RasterForwardKernel(const int2* __restrict__ ranges,108 RasterForwardKernel(const int2* __restrict__ ranges,
108 int32_t* __restrict__ point_list,109 int32_t* __restrict__ point_list,
109 const Splat* __restrict__ splats, Frame frame,110 const Splat* __restrict__ splats, Frame frame,
110 float* __restrict__ image, float* __restrict__ final_t,111 float* __restrict__ image, float* __restrict__ final_t,
111 int32_t* __restrict__ n_contrib) {112 int32_t* __restrict__ n_contrib) {
112 constexpr int kRasterRows = kRows;113 constexpr int kRasterRows = kRows;
113 constexpr int kRasterThreads = RasterLayout<kRows>::kThreads;114 constexpr int kRasterThreads = RasterLayout<kRows>::kThreads;
114 constexpr int kWarps = RasterLayout<kRows>::kWarps;115 constexpr int kWarps = RasterLayout<kRows>::kWarps;
115 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;116 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
116 const int tid = threadIdx.x;117 const int tid = threadIdx.x;
117 const int warp = tid / 32;118 const int warp = tid / 32;
118 const int lane = tid % 32;119 const int lane = tid % 32;
119 const RasterPixels px = RasterPixels::For<kRows>(tid);120 const RasterPixels px = RasterPixels::For<kRows>(tid);
120 const float fx = static_cast<float>(px.x);121 const float fx = static_cast<float>(px.x);
121 const int2 range = ranges[tile];122 const int2 range = ranges[tile];
122123
124 // t[k] >= 0: live pixel with transmittance t. t[k] < 0: the pixel is done
125 // (transmittance would drop below kMinTransmittance) and |t| is final.
126 // Done pixels then fail `next_t >= kMinTransmittance`, so they need no
127 // separate flag.
123 float t[kRasterRows], r[kRasterRows], g[kRasterRows], b[kRasterRows];128 float t[kRasterRows], r[kRasterRows], g[kRasterRows], b[kRasterRows];
124 int last[kRasterRows];129 int last[kRasterRows];
125 bool done[kRasterRows];
126#pragma unroll130#pragma unroll
127 for (int k = 0; k < kRasterRows; ++k) {131 for (int k = 0; k < kRasterRows; ++k) {
132 const bool outside = px.x >= frame.width || px.y0 + k >= frame.height;
128 t[k] = 1.0f;133 t[k] = outside ? -1.0f : 1.0f;
129 r[k] = g[k] = b[k] = 0.0f;134 r[k] = g[k] = b[k] = 0.0f;
130 last[k] = 0;135 last[k] = 0;
131 done[k] = px.x >= frame.width || px.y0 + k >= frame.height;
132 }136 }
133 auto all_done = [&] {137 auto all_done = [&] {
134 bool d = true;
138 uint32_t sign = 0xffffffffu;
135#pragma unroll139#pragma unroll
136 for (int k = 0; k < kRasterRows; ++k) d = d && done[k];140 for (int k = 0; k < kRasterRows; ++k) sign &= __float_as_uint(t[k]);
137 return d;141 return (sign >> 31) != 0u;
138 };142 };
139143
140 __shared__ float4 batch_geom0[kRasterThreads];
141 __shared__ float batch_geom1[kRasterThreads];
144 // Staged splat j: batch[0][j] = (x, y, A, B), batch[1][j] = (r, g, b,
145 // log2(opacity)), batch[2][j].x = C (see `Power2Conic`). One array so all
146 // three loads share an address.
142 __shared__ float4 batch_rgb_o[kRasterThreads];147 __shared__ float4 batch[3][kRasterThreads];
143 __shared__ uint8_t batch_mask[kRasterThreads];148 __shared__ uint8_t batch_mask[kRasterThreads];
144 __shared__ uint32_t warp_contrib[kWarps][kWarps];149 __shared__ uint32_t warp_contrib[kWarps][kWarps];
145 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);150 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
146 bool thread_done = all_done();151 bool thread_done = all_done();
↕ Expand 6 unchanged lines (lines 152–157) · in RasterForwardKernel
147 int tile_compact = 0;152 int tile_compact = 0;
148 for (int start = range.x; start < range.y; start += kRasterThreads) {153 for (int start = range.x; start < range.y; start += kRasterThreads) {
149 const int idx = start + tid;154 const int idx = start + tid;
150 int32_t id = 0;155 int32_t id = 0;
151 if (idx < range.y) {156 if (idx < range.y) {
152 id = point_list[idx];157 id = point_list[idx];
153 const float4 p0 = __ldg(&splats[id].p0);158 const float4 p0 = __ldg(&splats[id].p0);
154 const float4 p1 = __ldg(&splats[id].p1);159 const float4 p1 = __ldg(&splats[id].p1);
155 const float4 p2 = __ldg(&splats[id].p2);160 const float4 p2 = __ldg(&splats[id].p2);
156 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);161 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
157 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};162 batch[0][tid] = {p0.x, p0.y, c2.x, c2.y};
158 batch_geom1[tid] = c2.z;163 batch[1][tid] = p2;
159 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};164 batch[2][tid].x = c2.z;
160 batch_mask[tid] = static_cast<uint8_t>(WarpMask<kRows>(p0, p1));165 batch_mask[tid] = static_cast<uint8_t>(WarpMask<kRows>(p0, p1));
161 } else {166 } else {
162 batch_mask[tid] = 0u;167 batch_mask[tid] = 0u;
163 }168 }
164 __syncthreads();169 __syncthreads();
165 const int count = min(kRasterThreads, range.y - start);170 const int count = min(kRasterThreads, range.y - start);
166 bool warp_done = __all_sync(warp::kFullMask, thread_done);171 bool warp_done = __all_sync(warp::kFullMask, thread_done);
167#pragma unroll172#pragma unroll
168 for (int c = 0; c < kRasterThreads; c += 32) {173 for (int c = 0; c < kRasterThreads; c += 32) {
174 // Bit `j - c` is set if some pixel of this lane blended splat j.
169 uint32_t contrib = 0u;175 uint32_t lane_contrib = 0u;
170 if (c < count && !warp_done) {176 if (c < count && !warp_done) {
171 uint32_t mask = __ballot_sync(177 uint32_t mask = __ballot_sync(
172 warp::kFullMask,178 warp::kFullMask,
173 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);179 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
174 while (mask != 0u) {180 while (mask != 0u) {
175 const int bit = __ffs(mask) - 1;181 const uint32_t low = mask & (0u - mask);
176 mask &= mask - 1u;182 mask ^= low;
177 const int j = c + bit;183 const int j = c + (31 - __clz(low));
184 // Predicated like the backward pass: rows that are done or not
185 // reached keep their state.
186 const float4 g0 = batch[0][j];
187 const float4 ro = batch[1][j];
188 const float c2 = batch[2][j].x;
189 const Power2Column col =
190 Power2Column::At(g0.z, g0.w, ro.w, __fsub_rn(g0.x, fx));
178 bool blended = false;191 bool blended = false;
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];
183 const float c2 = batch_geom1[j];
184 const float4 ro = batch_rgb_o[j];
185 const Power2Column col =
186 Power2Column::At(g0.z, g0.w, __fsub_rn(g0.x, fx));
187#pragma unroll192#pragma unroll
188 for (int k = 0; k < kRasterRows; ++k) {193 for (int k = 0; k < kRasterRows; ++k) {
189 const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));194 const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));
190 const float power = col.Row(c2, dy);195 const float alpha_raw = Exp2Approx(col.Row(c2, dy));
191 const float alpha_k = SplatAlpha(ro.w, Exp2Approx(power));196 const float alpha_k = fminf(kMaxAlpha, alpha_raw);
192 const bool hit =
193 !done[k] && power <= 0.0f && alpha_k >= kMinAlpha;197 const bool hit = alpha_raw >= kMinAlpha;
198 // w = alpha t and next_t = t (1 - alpha) = t - alpha t.
199 const float w_k = __fmul_rn(alpha_k, t[k]);
194 const float next_t = t[k] * (1.0f - alpha_k);200 const float next_t = __fmaf_rn(-alpha_k, t[k], t[k]);
195 const bool stop = hit && next_t < kMinTransmittance;201 const bool blend = hit && next_t >= kMinTransmittance;
196 const bool blend = hit && !stop;
197 done[k] = done[k] || stop;
198 const float w = blend ? alpha_k * t[k] : 0.0f;202 const float w = blend ? w_k : 0.0f;
199 r[k] += w * ro.x;203 r[k] += w * ro.x;
200 g[k] += w * ro.y;204 g[k] += w * ro.y;
201 b[k] += w * ro.z;205 b[k] += w * ro.z;
206 // A hit that is not blended stops the pixel (t -> -|t|).
202 t[k] = blend ? next_t : t[k];207 t[k] = blend ? next_t : (hit ? -fabsf(t[k]) : t[k]);
203 last[k] = blend ? -1 - j : last[k];208 last[k] = blend ? -1 - j : last[k];
204 blended = blended || blend;209 blended = blended || blend;
205 }210 }
211 lane_contrib |= blended ? low : 0u;
206 thread_done = all_done();212 thread_done = all_done();
207 }
208 if (__any_sync(warp::kFullMask, blended)) {
209 contrib |= (1u << bit);
210 }
211 if (__all_sync(warp::kFullMask, thread_done)) {213 if (__all_sync(warp::kFullMask, thread_done)) {
212 warp_done = true;214 warp_done = true;
213 break;215 break;
214 }216 }
215 }217 }
216 warp_done = __all_sync(warp::kFullMask, thread_done);218 warp_done = __all_sync(warp::kFullMask, thread_done);
217 }219 }
220#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 800
221 const uint32_t contrib = __reduce_or_sync(warp::kFullMask, lane_contrib);
222#else
223 uint32_t contrib = lane_contrib;
224#pragma unroll
225 for (int offset = 16; offset > 0; offset /= 2) {
226 contrib |= __shfl_xor_sync(warp::kFullMask, contrib, offset);
227 }
228#endif
218 if (lane == 0) {229 if (lane == 0) {
219 warp_contrib[warp][c / 32] = contrib;230 warp_contrib[warp][c / 32] = contrib;
220 }231 }
221 }232 }
↕ Expand 45 unchanged lines (lines 233–277) · in RasterForwardKernel
222 const bool block_done =233 const bool block_done =
223 (__syncthreads_count(thread_done) == kRasterThreads);234 (__syncthreads_count(thread_done) == kRasterThreads);
224 uint32_t chunk_any[kWarps];235 uint32_t chunk_any[kWarps];
225 int chunk_prefix[kWarps];236 int chunk_prefix[kWarps];
226 int batch_active = 0;237 int batch_active = 0;
227#pragma unroll238#pragma unroll
228 for (int u = 0; u < kWarps; ++u) {239 for (int u = 0; u < kWarps; ++u) {
229 uint32_t any_u = 0u;240 uint32_t any_u = 0u;
230#pragma unroll241#pragma unroll
231 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {242 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {
232 any_u |= warp_contrib[w_idx][u];243 any_u |= warp_contrib[w_idx][u];
233 }244 }
234 chunk_any[u] = any_u;245 chunk_any[u] = any_u;
235 chunk_prefix[u] = tile_compact + batch_active;246 chunk_prefix[u] = tile_compact + batch_active;
236 batch_active += __popc(any_u);247 batch_active += __popc(any_u);
237 }248 }
238 if ((chunk_any[warp] >> lane) & 1u) {249 if ((chunk_any[warp] >> lane) & 1u) {
239 uint32_t cmask = 0u;250 uint32_t cmask = 0u;
240#pragma unroll251#pragma unroll
241 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {252 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {
242 cmask |= ((warp_contrib[w_idx][warp] >> lane) & 1u) << w_idx;253 cmask |= ((warp_contrib[w_idx][warp] >> lane) & 1u) << w_idx;
243 }254 }
244 const int dst =255 const int dst =
245 chunk_prefix[warp] + __popc(chunk_any[warp] & ((1u << lane) - 1u));256 chunk_prefix[warp] + __popc(chunk_any[warp] & ((1u << lane) - 1u));
246 point_list[range.x + dst] =257 point_list[range.x + dst] =
247 static_cast<int32_t>((cmask << 24) | static_cast<uint32_t>(id));258 static_cast<int32_t>((cmask << 24) | static_cast<uint32_t>(id));
248 }259 }
249#pragma unroll260#pragma unroll
250 for (int k = 0; k < kRasterRows; ++k) {261 for (int k = 0; k < kRasterRows; ++k) {
251 if (last[k] < 0) {262 if (last[k] < 0) {
252 const int j = -1 - last[k];263 const int j = -1 - last[k];
253 const int u = j >> 5;264 const int u = j >> 5;
254 const int b_idx = j & 31;265 const int b_idx = j & 31;
255 last[k] =266 last[k] =
256 chunk_prefix[u] + __popc(chunk_any[u] & ((1u << b_idx) - 1u)) + 1;267 chunk_prefix[u] + __popc(chunk_any[u] & ((1u << b_idx) - 1u)) + 1;
257 }268 }
258 }269 }
259 tile_compact += batch_active;270 tile_compact += batch_active;
260 if (block_done) break;271 if (block_done) break;
261 }272 }
262 const int pixels = frame.width * frame.height;273 const int pixels = frame.width * frame.height;
263#pragma unroll274#pragma unroll
264 for (int k = 0; k < kRasterRows; ++k) {275 for (int k = 0; k < kRasterRows; ++k) {
265 const int y = px.y0 + k;276 const int y = px.y0 + k;
266 if (px.x >= frame.width || y >= frame.height) continue;277 if (px.x >= frame.width || y >= frame.height) continue;
267 const int p = y * frame.width + px.x;278 const int p = y * frame.width + px.x;
268 image[0 * pixels + p] = r[k];279 image[0 * pixels + p] = r[k];
269 image[1 * pixels + p] = g[k];280 image[1 * pixels + p] = g[k];
270 image[2 * pixels + p] = b[k];281 image[2 * pixels + p] = b[k];
271 final_t[p] = t[k];282 final_t[p] = fabsf(t[k]);
272 n_contrib[p] = last[k];283 n_contrib[p] = last[k];
273 }284 }
274}285}
275286
↕ Expand 24 unchanged lines (lines 287–310) · in RasterForwardKernel
276template __global__ void PreprocessKernel<Culling::kRect, float>(287template __global__ void PreprocessKernel<Culling::kRect, float>(
277 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,288 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
278 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);289 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
279template __global__ void PreprocessKernel<Culling::kRect, ShBf16>(290template __global__ void PreprocessKernel<Culling::kRect, ShBf16>(
280 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,291 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
281 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);292 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
282template __global__ void PreprocessKernel<Culling::kEllipse, float>(293template __global__ void PreprocessKernel<Culling::kEllipse, float>(
283 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,294 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
284 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);295 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
285template __global__ void PreprocessKernel<Culling::kEllipse, ShBf16>(296template __global__ void PreprocessKernel<Culling::kEllipse, ShBf16>(
286 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,297 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
287 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);298 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
288299
289template __global__ void RasterForwardKernel<1>(300template __global__ void RasterForwardKernel<1>(
290 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,301 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
291 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);302 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
292template __global__ void RasterForwardKernel<2>(303template __global__ void RasterForwardKernel<2>(
293 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,304 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
294 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);305 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
295template __global__ void RasterForwardKernel<4>(306template __global__ void RasterForwardKernel<4>(
296 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,307 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
297 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);308 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
298309
299} // namespace gs310} // namespace gs
↕ Expand 93 unchanged lines (lines 1–93) · 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) {
8181 s.p0 = {pr.xy.x, pr.xy.y, pr.conic.x, pr.conic.y};
8282 s.p1 = {pr.conic.z, pr.opacity, pr.limit, static_cast<float>(pr.radius)};
8383 s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};
8484 ForEachTileRow<kCulling>(
8585 s, frame.grid, frame.width, frame.height,
8686 [&](int, int tx0, int tx1) { count += tx1 - tx0; });
8787 }
8888 if (count == 0) {
8989 s.p0 = s.p1 = s.p2 = {0.0f, 0.0f, 0.0f, 0.0f};
9090 splats[i] = s;
9191 tile_counts[i] = 0;
9292 return;
9393 }
9494 const int vis_idx = atomicAdd(num_visible, 1);
9595 float inv_len;
9696 const float3 rgb =
9797 ShadeColorRow(ShRow<ShT>(params.sh, i), ViewDir(cam, mean, inv_len));
98+ // Visible splats have opacity >= ~1/255 (see `AlphaLimit`).
98− s.p2 = {rgb.x, rgb.y, rgb.z, s.p2.w};
99+ s.p2 = {rgb.x, rgb.y, rgb.z, log2f(s.p1.y)};
99100 splats[i] = s;
100101 tile_counts[i] = count;
101102 depth_keys[vis_idx] = AsUint(pr.depth);
102103 ids[vis_idx] = i;
↕ Expand 16 unchanged lines (lines 104–119) · in RasterForwardKernel
103104 }
104105
105106 template <int kRows>
106107 __global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 8)
107108 RasterForwardKernel(const int2* __restrict__ ranges,
108109 int32_t* __restrict__ point_list,
109110 const Splat* __restrict__ splats, Frame frame,
110111 float* __restrict__ image, float* __restrict__ final_t,
111112 int32_t* __restrict__ n_contrib) {
112113 constexpr int kRasterRows = kRows;
113114 constexpr int kRasterThreads = RasterLayout<kRows>::kThreads;
114115 constexpr int kWarps = RasterLayout<kRows>::kWarps;
115116 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
116117 const int tid = threadIdx.x;
117118 const int warp = tid / 32;
118119 const int lane = tid % 32;
119120 const RasterPixels px = RasterPixels::For<kRows>(tid);
120121 const float fx = static_cast<float>(px.x);
121122 const int2 range = ranges[tile];
122123
124+ // t[k] >= 0: live pixel with transmittance t. t[k] < 0: the pixel is done
125+ // (transmittance would drop below kMinTransmittance) and |t| is final.
126+ // Done pixels then fail `next_t >= kMinTransmittance`, so they need no
127+ // separate flag.
123128 float t[kRasterRows], r[kRasterRows], g[kRasterRows], b[kRasterRows];
124129 int last[kRasterRows];
125− bool done[kRasterRows];
126130 #pragma unroll
127131 for (int k = 0; k < kRasterRows; ++k) {
132+ const bool outside = px.x >= frame.width || px.y0 + k >= frame.height;
128− t[k] = 1.0f;
133+ t[k] = outside ? -1.0f : 1.0f;
129134 r[k] = g[k] = b[k] = 0.0f;
130135 last[k] = 0;
131− done[k] = px.x >= frame.width || px.y0 + k >= frame.height;
132136 }
133137 auto all_done = [&] {
134− bool d = true;
138+ uint32_t sign = 0xffffffffu;
135139 #pragma unroll
136− for (int k = 0; k < kRasterRows; ++k) d = d && done[k];
140+ for (int k = 0; k < kRasterRows; ++k) sign &= __float_as_uint(t[k]);
137− return d;
141+ return (sign >> 31) != 0u;
138142 };
139143
140− __shared__ float4 batch_geom0[kRasterThreads];
141− __shared__ float batch_geom1[kRasterThreads];
144+ // Staged splat j: batch[0][j] = (x, y, A, B), batch[1][j] = (r, g, b,
145+ // log2(opacity)), batch[2][j].x = C (see `Power2Conic`). One array so all
146+ // three loads share an address.
142− __shared__ float4 batch_rgb_o[kRasterThreads];
147+ __shared__ float4 batch[3][kRasterThreads];
143148 __shared__ uint8_t batch_mask[kRasterThreads];
144149 __shared__ uint32_t warp_contrib[kWarps][kWarps];
145150 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
146151 bool thread_done = all_done();
↕ Expand 6 unchanged lines (lines 152–157) · in RasterForwardKernel
147152 int tile_compact = 0;
148153 for (int start = range.x; start < range.y; start += kRasterThreads) {
149154 const int idx = start + tid;
150155 int32_t id = 0;
151156 if (idx < range.y) {
152157 id = point_list[idx];
153158 const float4 p0 = __ldg(&splats[id].p0);
154159 const float4 p1 = __ldg(&splats[id].p1);
155160 const float4 p2 = __ldg(&splats[id].p2);
156161 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
157− batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
162+ batch[0][tid] = {p0.x, p0.y, c2.x, c2.y};
158− batch_geom1[tid] = c2.z;
163+ batch[1][tid] = p2;
159− batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};
164+ batch[2][tid].x = c2.z;
160165 batch_mask[tid] = static_cast<uint8_t>(WarpMask<kRows>(p0, p1));
161166 } else {
162167 batch_mask[tid] = 0u;
163168 }
164169 __syncthreads();
165170 const int count = min(kRasterThreads, range.y - start);
166171 bool warp_done = __all_sync(warp::kFullMask, thread_done);
167172 #pragma unroll
168173 for (int c = 0; c < kRasterThreads; c += 32) {
174+ // Bit `j - c` is set if some pixel of this lane blended splat j.
169− uint32_t contrib = 0u;
175+ uint32_t lane_contrib = 0u;
170176 if (c < count && !warp_done) {
171177 uint32_t mask = __ballot_sync(
172178 warp::kFullMask,
173179 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
174180 while (mask != 0u) {
175− const int bit = __ffs(mask) - 1;
181+ const uint32_t low = mask & (0u - mask);
176− mask &= mask - 1u;
182+ mask ^= low;
177− const int j = c + bit;
183+ const int j = c + (31 - __clz(low));
184+ // Predicated like the backward pass: rows that are done or not
185+ // reached keep their state.
186+ const float4 g0 = batch[0][j];
187+ const float4 ro = batch[1][j];
188+ const float c2 = batch[2][j].x;
189+ const Power2Column col =
190+ Power2Column::At(g0.z, g0.w, ro.w, __fsub_rn(g0.x, fx));
178191 bool blended = false;
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];
183− const float c2 = batch_geom1[j];
184− const float4 ro = batch_rgb_o[j];
185− const Power2Column col =
186− Power2Column::At(g0.z, g0.w, __fsub_rn(g0.x, fx));
187192 #pragma unroll
188193⇥ for (int k = 0; k < kRasterRows; ++k) {
189194⇥ const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));
190− const float power = col.Row(c2, dy);
195+ const float alpha_raw = Exp2Approx(col.Row(c2, dy));
191− const float alpha_k = SplatAlpha(ro.w, Exp2Approx(power));
196+ const float alpha_k = fminf(kMaxAlpha, alpha_raw);
192− const bool hit =
193− !done[k] && power <= 0.0f && alpha_k >= kMinAlpha;
197+ const bool hit = alpha_raw >= kMinAlpha;
198+ // w = alpha t and next_t = t (1 - alpha) = t - alpha t.
199+ const float w_k = __fmul_rn(alpha_k, t[k]);
194− const float next_t = t[k] * (1.0f - alpha_k);
200+ const float next_t = __fmaf_rn(-alpha_k, t[k], t[k]);
195− const bool stop = hit && next_t < kMinTransmittance;
201+ const bool blend = hit && next_t >= kMinTransmittance;
196− const bool blend = hit && !stop;
197− done[k] = done[k] || stop;
198− const float w = blend ? alpha_k * t[k] : 0.0f;
202+ const float w = blend ? w_k : 0.0f;
199203⇥ r[k] += w * ro.x;
200204⇥ g[k] += w * ro.y;
201205⇥ b[k] += w * ro.z;
206+ // A hit that is not blended stops the pixel (t -> -|t|).
202− t[k] = blend ? next_t : t[k];
207+ t[k] = blend ? next_t : (hit ? -fabsf(t[k]) : t[k]);
203208⇥ last[k] = blend ? -1 - j : last[k];
204209⇥ blended = blended || blend;
205210⇥ }
211+ lane_contrib |= blended ? low : 0u;
206212⇥ thread_done = all_done();
207− }
208− if (__any_sync(warp::kFullMask, blended)) {
209− contrib |= (1u << bit);
210− }
211213 if (__all_sync(warp::kFullMask, thread_done)) {
212214 warp_done = true;
213215 break;
214216 }
215217 }
216218 warp_done = __all_sync(warp::kFullMask, thread_done);
217219 }
220+#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 800
221+ const uint32_t contrib = __reduce_or_sync(warp::kFullMask, lane_contrib);
222+#else
223+ uint32_t contrib = lane_contrib;
224+#pragma unroll
225+ for (int offset = 16; offset > 0; offset /= 2) {
226+ contrib |= __shfl_xor_sync(warp::kFullMask, contrib, offset);
227+ }
228+#endif
218229 if (lane == 0) {
219230 warp_contrib[warp][c / 32] = contrib;
220231 }
221232 }
↕ Expand 45 unchanged lines (lines 233–277) · in RasterForwardKernel
222233 const bool block_done =
223234 (__syncthreads_count(thread_done) == kRasterThreads);
224235 uint32_t chunk_any[kWarps];
225236 int chunk_prefix[kWarps];
226237 int batch_active = 0;
227238 #pragma unroll
228239 for (int u = 0; u < kWarps; ++u) {
229240 uint32_t any_u = 0u;
230241 #pragma unroll
231242 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {
232243 any_u |= warp_contrib[w_idx][u];
233244 }
234245 chunk_any[u] = any_u;
235246 chunk_prefix[u] = tile_compact + batch_active;
236247 batch_active += __popc(any_u);
237248 }
238249 if ((chunk_any[warp] >> lane) & 1u) {
239250 uint32_t cmask = 0u;
240251 #pragma unroll
241252 for (int w_idx = 0; w_idx < kWarps; ++w_idx) {
242253 cmask |= ((warp_contrib[w_idx][warp] >> lane) & 1u) << w_idx;
243254 }
244255 const int dst =
245256 chunk_prefix[warp] + __popc(chunk_any[warp] & ((1u << lane) - 1u));
246257 point_list[range.x + dst] =
247258 static_cast<int32_t>((cmask << 24) | static_cast<uint32_t>(id));
248259 }
249260 #pragma unroll
250261 for (int k = 0; k < kRasterRows; ++k) {
251262 if (last[k] < 0) {
252263 const int j = -1 - last[k];
253264 const int u = j >> 5;
254265 const int b_idx = j & 31;
255266 last[k] =
256267 chunk_prefix[u] + __popc(chunk_any[u] & ((1u << b_idx) - 1u)) + 1;
257268 }
258269 }
259270 tile_compact += batch_active;
260271 if (block_done) break;
261272 }
262273 const int pixels = frame.width * frame.height;
263274 #pragma unroll
264275 for (int k = 0; k < kRasterRows; ++k) {
265276 const int y = px.y0 + k;
266277 if (px.x >= frame.width || y >= frame.height) continue;
267278 const int p = y * frame.width + px.x;
268279 image[0 * pixels + p] = r[k];
269280 image[1 * pixels + p] = g[k];
270281 image[2 * pixels + p] = b[k];
271− final_t[p] = t[k];
282+ final_t[p] = fabsf(t[k]);
272283 n_contrib[p] = last[k];
273284 }
274285 }
275286
↕ Expand 24 unchanged lines (lines 287–310) · in RasterForwardKernel
276287 template __global__ void PreprocessKernel<Culling::kRect, float>(
277288 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
278289 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
279290 template __global__ void PreprocessKernel<Culling::kRect, ShBf16>(
280291 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
281292 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
282293 template __global__ void PreprocessKernel<Culling::kEllipse, float>(
283294 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
284295 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
285296 template __global__ void PreprocessKernel<Culling::kEllipse, ShBf16>(
286297 GaussianParams, Frame, Splat* __restrict__, int32_t* __restrict__,
287298 uint32_t* __restrict__, int32_t* __restrict__, int32_t* __restrict__);
288299
289300 template __global__ void RasterForwardKernel<1>(
290301 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
291302 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
292303 template __global__ void RasterForwardKernel<2>(
293304 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
294305 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
295306 template __global__ void RasterForwardKernel<4>(
296307 const int2* __restrict__, int32_t* __restrict__, const Splat* __restrict__,
297308 Frame, float* __restrict__, float* __restrict__, int32_t* __restrict__);
298309
299310 } // namespace gs
csrc/backward.cu.cc +83 −67
s05_branchless_predication/csrc/backward.cu.cc
s06_multirow_and_pipe_math/csrc/backward.cu.cc
↕ Expand 197 unchanged lines (lines 1–197) · in ProjectVjp
1/*1/*
2 * Copyright (C) 2023, Inria2 * Copyright (C) 2023, Inria
3 * GRAPHDECO research group, https://team.inria.fr/graphdeco3 * GRAPHDECO research group, https://team.inria.fr/graphdeco
4 * All rights reserved.4 * All rights reserved.
5 *5 *
6 * This software is free for non-commercial, research and evaluation use6 * This software is free for non-commercial, research and evaluation use
7 * under the terms of the LICENSE.md file.7 * under the terms of the LICENSE.md file.
8 *8 *
9 * For inquiries contact george.drettakis@inria.fr9 * For inquiries contact george.drettakis@inria.fr
10 */10 */
1111
12#include "backward.h"12#include "backward.h"
1313
14#include "auxiliary.h"14#include "auxiliary.h"
1515
16namespace gs {16namespace gs {
1717
18namespace sh {18namespace sh {
19// Evaluates SH color VJP 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, 6)
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;
215 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;214 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
216 const int tid = threadIdx.x;215 const int tid = threadIdx.x;
217 const int warp = tid / 32;216 const int warp = tid / 32;
218 const int lane = tid % 32;217 const int lane = tid % 32;
↕ Expand 30 unchanged lines (lines 218–247) · in PixelState
219 const RasterPixels px = RasterPixels::For<kRows>(tid);218 const RasterPixels px = RasterPixels::For<kRows>(tid);
220 const float fx = static_cast<float>(px.x);219 const float fx = static_cast<float>(px.x);
221 const int2 range = ranges[tile];220 const int2 range = ranges[tile];
222221
223 struct PixelState {222 struct PixelState {
224 float t;223 float t;
225 float3 dpix;224 float3 dpix;
226 float accum; // dot(color accumulated behind, dpix)225 float accum; // dot(color accumulated behind, dpix)
227 int last;226 int last;
228 };227 };
229 PixelState st[kRasterRows];228 PixelState st[kRasterRows];
230 const int pixels = frame.width * frame.height;229 const int pixels = frame.width * frame.height;
231 int thread_last = 0;230 int thread_last = 0;
232#pragma unroll231#pragma unroll
233 for (int k = 0; k < kRasterRows; ++k) {232 for (int k = 0; k < kRasterRows; ++k) {
234 const int y = px.y0 + k;233 const int y = px.y0 + k;
235 const bool inside = px.x < frame.width && y < frame.height;234 const bool inside = px.x < frame.width && y < frame.height;
236 const int p = y * frame.width + px.x;235 const int p = y * frame.width + px.x;
237 st[k].t = inside ? final_t[p] : 0.0f;236 st[k].t = inside ? final_t[p] : 0.0f;
238 st[k].dpix =237 st[k].dpix =
239 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],238 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],
240 dl_dimage[2 * pixels + p]}239 dl_dimage[2 * pixels + p]}
241 : float3{0.0f, 0.0f, 0.0f};240 : float3{0.0f, 0.0f, 0.0f};
242 st[k].accum = 0.0f;241 st[k].accum = 0.0f;
243 st[k].last = inside ? n_contrib[p] : 0;242 st[k].last = inside ? n_contrib[p] : 0;
244 thread_last = max(thread_last, st[k].last);243 thread_last = max(thread_last, st[k].last);
245 }244 }
246245
247 int warp_max_last = thread_last;246 int warp_max_last = thread_last;
248#pragma unroll247#pragma unroll
249 for (int offset = 16; offset > 0; offset /= 2) {248 for (int offset = 16; offset > 0; offset /= 2) {
250 warp_max_last = max(249 warp_max_last = max(
251 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));250 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));
252 }251 }
253 __shared__ int shared_max_last[kWarps];
254 if (lane == 0) shared_max_last[warp] = warp_max_last;
255 __syncthreads();
256 int block_max_last = 0;
257#pragma unroll
258 for (int w = 0; w < kWarps; ++w) {
259 block_max_last = max(block_max_last, shared_max_last[w]);
260 }
261 if (block_max_last <= 0) {
262 return;
263 }
264252
253 // Each warp stages its own splats in its 32-entry slice of these arrays,
254 // so warps never wait for each other (no block barriers).
265 __shared__ float4 batch_geom0[kRasterThreads];255 __shared__ float4 batch_geom0[kRasterThreads];
266 __shared__ float batch_geom1[kRasterThreads];256 __shared__ float batch_geom1[kRasterThreads];
267 __shared__ float4 batch_rgb_o[kRasterThreads];257 __shared__ float4 batch_rgb_o[kRasterThreads];
268 __shared__ uint32_t batch_id[kRasterThreads];258 __shared__ uint32_t batch_id[kRasterThreads];
269 __shared__ uint8_t batch_mask[kRasterThreads];
270 const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);259 const uint32_t warp_bit = 1u << warp;
260 const int warp_base = warp * 32;
271 int scatter = -1;261 int scatter = -1;
272 int scatter2 = -1;262 int scatter2 = -1;
273 if constexpr (L::kWarpWidth == 8) {263 if constexpr (L::kWarpWidth == 8) {
274 const int sub_lane = lane & 15;264 const int sub_lane = lane & 15;
↕ Expand 25 unchanged lines (lines 265–289) · in PixelState
275 const int row = sub_lane >> 3, col = sub_lane & 7;265 const int row = sub_lane >> 3, col = sub_lane & 7;
276 if (col == 0) {266 if (col == 0) {
277 scatter2 = row;267 scatter2 = row;
278 } else if (col == 2) {268 } else if (col == 2) {
279 scatter2 = 2 + row;269 scatter2 = 2 + row;
280 } else if (col == 4) {270 } else if (col == 4) {
281 scatter2 = row == 0 ? 4 : 7;271 scatter2 = row == 0 ? 4 : 7;
282 } else if (col == 6) {272 } else if (col == 6) {
283 scatter2 = row == 0 ? 6 : 8;273 scatter2 = row == 0 ? 6 : 8;
284 } else if (col == 1 && row == 0) {274 } else if (col == 1 && row == 0) {
285 scatter2 = 5;275 scatter2 = 5;
286 }276 }
287 const int full_row = lane >> 3, full_col = lane & 7;277 const int full_row = lane >> 3, full_col = lane & 7;
288 if (full_col == 0) {278 if (full_col == 0) {
289 scatter = full_row;279 scatter = full_row;
290 } else if (full_col == 2) {280 } else if (full_col == 2) {
291 scatter = full_row == 0 ? 4 : 5 + full_row;281 scatter = full_row == 0 ? 4 : 5 + full_row;
292 } else if (full_col == 4 && full_row == 0) {282 } else if (full_col == 4 && full_row == 0) {
293 scatter = 5;283 scatter = 5;
294 }284 }
295 } else {285 } else {
296 scatter = warp::ScatterIndex(kSplatGradFloats, lane);286 scatter = warp::ScatterIndex(kSplatGradFloats, lane);
297 }287 }
298288
299 // Evaluates one splat at this thread's pixels and accumulates the per-thread289 // 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 on290 // 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.291 // 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 accum292 // 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.293 // exactly unchanged (t * rcp(1) == t) and contributes zero gradient.
294 //
295 // Per pixel, ga = alpha_raw T diff = dL/dpower (log2 units, up to the
296 // constant), opacity included: the opacity gradient divides it back out.
297 // Returns ga_sum = sum ga, ga_dy = sum ga dy, ga_dy2 = sum ga dy^2. Since the
298 // rows are dy_k = dy_0 - k, the last two are formed from the moments
299 // m1 = sum k ga_k and m2 = sum k^2 ga_k: sum ga dy = dy_0 ga_sum - m1 and
300 // sum ga dy^2 = dy_0 (dy_0 ga_sum - 2 m1) + m2.
304 auto eval_splat = [&](float4 g0, float c2, float4 ro, int entry, float& dx,301 auto eval_splat = [&](float4 g0, float c2, float4 ro, int entry, float& dx,
305 float& ga_sum, float& ga_dy, float& ga_dy2,302 float& ga_sum, float& ga_dy, float& ga_dy2,
306 float3& drgb) {303 float3& drgb) {
307 dx = __fsub_rn(g0.x, fx);304 dx = __fsub_rn(g0.x, fx);
308 const Power2Column col = Power2Column::At(g0.z, g0.w, dx);305 const Power2Column col = Power2Column::At(g0.z, g0.w, ro.w, dx);
309 ga_sum = 0.0f;
306 float ga[kRasterRows];
310 ga_dy = 0.0f;307 float dy0 = 0.0f;
311 ga_dy2 = 0.0f;
312 drgb = {0.0f, 0.0f, 0.0f};308 drgb = {0.0f, 0.0f, 0.0f};
313#pragma unroll309#pragma unroll
314 for (int k = 0; k < kRasterRows; ++k) {310 for (int k = 0; k < kRasterRows; ++k) {
315 PixelState& ps = st[k];311 PixelState& ps = st[k];
316 const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));312 const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));
313 if (k == 0) dy0 = dy;
317 const float power = col.Row(c2, dy);314 const float alpha_raw = Exp2Approx(col.Row(c2, dy));
318 const float g_val = Exp2Approx(power);
319 const float alpha_k = SplatAlpha(ro.w, g_val);
320 const bool hit = entry < ps.last && power <= 0.0f && alpha_k >= kMinAlpha;315 const bool hit = entry < ps.last && alpha_raw >= kMinAlpha;
321 const float alpha = hit ? alpha_k : 0.0f;316 const float alpha = hit ? fminf(kMaxAlpha, alpha_raw) : 0.0f;
322 ps.t = ps.t * RcpApprox(1.0f - alpha);317 ps.t = ps.t * RcpApprox(1.0f - alpha);
323 const float w = alpha * ps.t;318 const float w = alpha * ps.t;
324 const float color_dot =319 const float color_dot =
325 ro.x * ps.dpix.x + ro.y * ps.dpix.y + ro.z * ps.dpix.z;320 ro.x * ps.dpix.x + ro.y * ps.dpix.y + ro.z * ps.dpix.z;
326 const float diff = color_dot - ps.accum;321 const float diff = color_dot - ps.accum;
327 ps.accum += alpha * diff;322 ps.accum += alpha * diff;
328 const float ga = hit ? g_val * (ps.t * diff) : 0.0f;323 ga[k] = hit ? alpha_raw * (ps.t * diff) : 0.0f;
329 ga_sum += ga;
330 const float ga_y = ga * dy;
331 ga_dy += ga_y;
332 ga_dy2 += ga_y * dy;
333 drgb += w * ps.dpix;324 drgb += w * ps.dpix;
325 }
326 ga_sum = ga[0];
327#pragma unroll
328 for (int k = 1; k < kRasterRows; ++k) ga_sum += ga[k];
329 if constexpr (kRasterRows == 1) {
330 ga_dy = ga_sum * dy0;
331 ga_dy2 = ga_dy * dy0;
332 } else {
333 float m1 = ga[1], m2 = ga[1];
334#pragma unroll
335 for (int k = 2; k < kRasterRows; ++k) {
336 m1 = fmaf(static_cast<float>(k), ga[k], m1);
337 m2 = fmaf(static_cast<float>(k * k), ga[k], m2);
338 }
339 ga_dy = fmaf(dy0, ga_sum, -m1);
340 ga_dy2 = fmaf(dy0, ga_dy - m1, m2);
334 }341 }
335 };342 };
336 // Conic (a, b, c) from the staged log2-scaled one.343 // Conic (a, b, c) from the staged log2-scaled one.
337 auto raw_conic = [](float4 g0, float c2) {344 auto raw_conic = [](float4 g0, float c2) {
338 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,345 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,
339 kInvPower2Scale * c2};346 kInvPower2Scale * c2};
340 };347 };
341348
342 for (int end = block_max_last; end > 0; end -= kRasterThreads) {349 for (int end = warp_max_last; end > 0; end -= 32) {
350 // Lane l stages entry end - 1 - l if this warp contributed to it.
343 const int e = end - 1 - tid;351 const int e = end - 1 - lane;
352 bool mine = false;
344 if (e >= 0) {353 if (e >= 0) {
345 const uint32_t packed = static_cast<uint32_t>(point_list[range.x + e]);354 const uint32_t packed = static_cast<uint32_t>(point_list[range.x + e]);
355 mine = ((packed >> 24) & warp_bit) != 0u;
356 if (mine) {
346 const uint32_t id = packed & 0x00ffffffu;357 const uint32_t id = packed & 0x00ffffffu;
347 splat_touched[id] = 1u;358 splat_touched[id] = 1u;
348 const float4 p0 = __ldg(&splats[id].p0);359 const float4 p0 = __ldg(&splats[id].p0);
349 const float4 p1 = __ldg(&splats[id].p1);360 const float4 p1 = __ldg(&splats[id].p1);
350 const float4 p2 = __ldg(&splats[id].p2);361 const float4 p2 = __ldg(&splats[id].p2);
351 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);362 const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
352 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};363 batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
353 batch_geom1[tid] = c2.z;364 batch_geom1[tid] = c2.z;
354 batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};365 batch_rgb_o[tid] = p2;
355 batch_id[tid] = id;366 batch_id[tid] = id;
356 batch_mask[tid] = static_cast<uint8_t>(packed >> 24);
357 } else {
358 batch_mask[tid] = 0u;
359 }367 }
360 __syncthreads();
361 const int count = min(kRasterThreads, end);
362#pragma unroll
363 for (int c = 0; c < kRasterThreads; c += 32) {
364 if (c < count && end - 32 - c < warp_max_last) {
368 }
365 uint32_t mask = __ballot_sync(369 uint32_t mask = __ballot_sync(warp::kFullMask, mine);
366 warp::kFullMask,
367 c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
370 __syncwarp();
371 {
372 {
368 if constexpr (L::kWarpWidth == 8) {373 if constexpr (L::kWarpWidth == 8) {
369 while ((mask & (mask - 1u)) != 0u) {374 while ((mask & (mask - 1u)) != 0u) {
370 const int bit0 = __ffs(mask) - 1;375 const int bit0 = __ffs(mask) - 1;
371 mask &= mask - 1u;376 mask &= mask - 1u;
372 const int bit1 = __ffs(mask) - 1;377 const int bit1 = __ffs(mask) - 1;
373 mask &= mask - 1u;378 mask &= mask - 1u;
374 const int j0 = c + bit0, j1 = c + bit1;379 const int j0 = warp_base + bit0, j1 = warp_base + bit1;
375 const float4 g0_0 = batch_geom0[j0];380 const float4 g0_0 = batch_geom0[j0];
376 const float c2_0 = batch_geom1[j0];381 const float c2_0 = batch_geom1[j0];
377 const float4 ro_0 = batch_rgb_o[j0];382 const float4 ro_0 = batch_rgb_o[j0];
378 const float4 g0_1 = batch_geom0[j1];383 const float4 g0_1 = batch_geom0[j1];
379 const float c2_1 = batch_geom1[j1];384 const float c2_1 = batch_geom1[j1];
380 const float4 ro_1 = batch_rgb_o[j1];385 const float4 ro_1 = batch_rgb_o[j1];
381386
382 float dx0, ga_sum0, ga_dy0, ga_dy2_0;387 float dx0, ga_sum0, ga_dy0, ga_dy2_0;
383 float3 drgb0;388 float3 drgb0;
384 eval_splat(g0_0, c2_0, ro_0, end - 1 - j0, dx0, ga_sum0, ga_dy0,389 eval_splat(g0_0, c2_0, ro_0, end - 1 - bit0, dx0, ga_sum0, ga_dy0,
385 ga_dy2_0, drgb0);390 ga_dy2_0, drgb0);
386391
387 float dx1, ga_sum1, ga_dy1, ga_dy2_1;392 float dx1, ga_sum1, ga_dy1, ga_dy2_1;
388 float3 drgb1;393 float3 drgb1;
389 eval_splat(g0_1, c2_1, ro_1, end - 1 - j1, dx1, ga_sum1, ga_dy1,394 eval_splat(g0_1, c2_1, ro_1, end - 1 - bit1, dx1, ga_sum1, ga_dy1,
390 ga_dy2_1, drgb1);395 ga_dy2_1, drgb1);
391396
392 const bool hi16 = (lane & 16) != 0;397 const bool hi16 = (lane & 16) != 0;
398 const bool row1 = (lane & 8) != 0;
393 const float opacity = hi16 ? ro_1.w : ro_0.w;399 const float log2_opacity = hi16 ? ro_1.w : ro_0.w;
394 const float dx = hi16 ? dx1 : dx0;400 const float dx = hi16 ? dx1 : dx0;
401 // This lane's row of the raw conic [[a, b], [b, c]] times -1:
402 // lanes 0-7 hold (a, b), lanes 8-15 (b, c), from the staged
403 // (A, B, C) = kPower2Scale (a, 2b, c). Selecting before scaling
404 // leaves two multiplies.
405 const float4 g0_sel = hi16 ? g0_1 : g0_0;
406 const float c2_sel = hi16 ? c2_1 : c2_0;
395 const float3 cn =407 const float cn_x =
396 hi16 ? raw_conic(g0_1, c2_1) : raw_conic(g0_0, c2_0);
408 (row1 ? g0_sel.w : g0_sel.z) *
409 (row1 ? -0.5f * kInvPower2Scale : -kInvPower2Scale);
410 const float cn_y =
411 (row1 ? c2_sel : g0_sel.w) *
412 (row1 ? -kInvPower2Scale : -0.5f * kInvPower2Scale);
397413
398 auto fold16 = [&](float v0, float v1) {414 auto fold16 = [&](float v0, float v1) {
399 const float send = hi16 ? v0 : v1;415 const float send = hi16 ? v0 : v1;
400 const float recv = __shfl_xor_sync(warp::kFullMask, send, 16);416 const float recv = __shfl_xor_sync(warp::kFullMask, send, 16);
401 return (hi16 ? v1 : v0) + recv;417 return (hi16 ? v1 : v0) + recv;
402 };418 };
403 float ga_sum = fold16(ga_sum0, ga_sum1);419 float ga_sum = fold16(ga_sum0, ga_sum1);
404 float ga_dy = fold16(ga_dy0, ga_dy1);420 float ga_dy = fold16(ga_dy0, ga_dy1);
405 float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1),421 float b4[4] = {-0.5f * fold16(ga_dy2_0, ga_dy2_1),
406 fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y),422 fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y),
407 fold16(drgb0.z, drgb1.z)};423 fold16(drgb0.z, drgb1.z)};
408 warp::internal::HalvingStep<4, 8>(b4, lane);424 warp::internal::HalvingStep<4, 8>(b4, lane);
409 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);425 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
410 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);426 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
411427
412 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;428 const float p_sum = ga_sum, p_dy = ga_dy;
413 const float p_dx = dx * p_sum;429 const float p_dx = dx * p_sum;
414 const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)430 const float v_dx0 = fmaf(cn_x, p_dx, cn_y * p_dy);
415 : -(cn.y * p_dx + cn.z * p_dy);
416 const float v_dx1 =431 const float v_dx1 = dx * (row1 ? -p_dy : -0.5f * p_dx);
417 (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
418 float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]};432 float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]};
419 warp::internal::HalvingStep<4, 4>(v4, lane);433 warp::internal::HalvingStep<4, 4>(v4, lane);
420 warp::internal::HalvingStep<2, 2>(v4, lane);434 warp::internal::HalvingStep<2, 2>(v4, lane);
421 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4);435 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4);
422 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2);436 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2);
423 const float v_sum =437 const float v_sum =
424 v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1);438 v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1);
425 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1);439 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1);
440 const float out_grad =
426 const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum;441 ((lane & 7) == 1) ? ga_sum * Exp2Approx(-log2_opacity) : v_sum;
427 if (scatter2 >= 0 && out_grad != 0.0f) {442 if (scatter2 >= 0 && out_grad != 0.0f) {
428 const uint32_t target_id = batch_id[hi16 ? j1 : j0];443 const uint32_t target_id = batch_id[hi16 ? j1 : j0];
429 atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2,444 atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2,
430 out_grad);445 out_grad);
431 }446 }
432 }447 }
433 }448 }
434 while (mask != 0u) {449 while (mask != 0u) {
435 const int bit = __ffs(mask) - 1;450 const int bit = __ffs(mask) - 1;
436 mask &= mask - 1u;451 mask &= mask - 1u;
437 const int j = c + bit;452 const int j = warp_base + bit;
438 const float4 g0 = batch_geom0[j];453 const float4 g0 = batch_geom0[j];
439 const float c2 = batch_geom1[j];454 const float c2 = batch_geom1[j];
440 const float4 ro = batch_rgb_o[j];455 const float4 ro = batch_rgb_o[j];
441 float dx, ga_sum, ga_dy, ga_dy2;456 float dx, ga_sum, ga_dy, ga_dy2;
442 float3 drgb;457 float3 drgb;
443 eval_splat(g0, c2, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2, drgb);458 eval_splat(g0, c2, ro, end - 1 - bit, dx, ga_sum, ga_dy, ga_dy2,
459 drgb);
444 const float opacity = ro.w;460 const float inv_opacity = Exp2Approx(-ro.w);
445 const float3 cn = raw_conic(g0, c2);461 const float3 cn = raw_conic(g0, c2);
446 float out_grad = 0.0f;462 float out_grad = 0.0f;
447 if constexpr (L::kWarpWidth == 8) {463 if constexpr (L::kWarpWidth == 8) {
448 float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z};464 float b4[4] = {-0.5f * ga_dy2, drgb.x, drgb.y, drgb.z};
449 warp::internal::HalvingStep<4, 16>(b4, lane);465 warp::internal::HalvingStep<4, 16>(b4, lane);
450 warp::internal::HalvingStep<2, 8>(b4, lane);466 warp::internal::HalvingStep<2, 8>(b4, lane);
451 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16);467 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16);
452 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16);468 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16);
453 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);469 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
454 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);470 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
455 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;471 const float p_sum = ga_sum, p_dy = ga_dy;
456 const float p_dx = dx * p_sum;472 const float p_dx = dx * p_sum;
457 float v_dx;473 float v_dx;
458 if ((lane & 16) == 0) {474 if ((lane & 16) == 0) {
459 v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)475 v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
460 : -(cn.y * p_dx + cn.z * p_dy);476 : -(cn.y * p_dx + cn.z * p_dy);
461 } else {477 } else {
462 v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;478 v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
463 }479 }
464 float v[3] = {v_dx, b4[0], ga_sum};480 float v[3] = {v_dx, b4[0], ga_sum * inv_opacity};
465 warp::internal::HalvingStep<3, 4>(v, lane);481 warp::internal::HalvingStep<3, 4>(v, lane);
466 warp::internal::HalvingStep<2, 2>(v, lane);482 warp::internal::HalvingStep<2, 2>(v, lane);
467 out_grad = v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1);483 out_grad = v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1);
468 } else {484 } else {
469 const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;485 const float p_sum = ga_sum, p_dy = ga_dy;
470 const float p_dx = dx * p_sum;486 const float p_dx = dx * p_sum;
471 float gr[kSplatGradFloats] = {-(cn.x * p_dx + cn.y * p_dy), // xy487 float gr[kSplatGradFloats] = {-(cn.x * p_dx + cn.y * p_dy), // xy
472 -(cn.y * p_dx + cn.z * p_dy),488 -(cn.y * p_dx + cn.z * p_dy),
473 -0.5f * dx * p_dx, // conic489 -0.5f * dx * p_dx, // conic
474 -dx * p_dy,490 -dx * p_dy,
475 -0.5f * opacity * ga_dy2,491 -0.5f * ga_dy2,
476 ga_sum, // opacity492 ga_sum * inv_opacity, // opacity
477 drgb.x,493 drgb.x,
478 drgb.y,494 drgb.y,
479 drgb.z}; // rgb495 drgb.z}; // rgb
480 warp::ReduceScatter(gr, lane);496 warp::ReduceScatter(gr, lane);
481 out_grad = gr[0];497 out_grad = gr[0];
482 }498 }
483 if (scatter >= 0 && out_grad != 0.0f) {499 if (scatter >= 0 && out_grad != 0.0f) {
484 atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter,500 atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter,
485 out_grad);501 out_grad);
486 }502 }
487 }503 }
488 }504 }
489 }505 }
490 __syncthreads();
506 __syncwarp(); // The slice is restaged next iteration.
491 }507 }
492}508}
493509
494// ---------------------------------------------------------------------------510// ---------------------------------------------------------------------------
495// Preprocess backward: splat gradients -> parameter gradients (dense).511// Preprocess backward: splat gradients -> parameter gradients (dense).
496// Uses the 1-byte `splat_touched` flag written by `RasterBackwardKernel` to512// Uses the 1-byte `splat_touched` flag written by `RasterBackwardKernel` to
497// skip reading `splat_grads` and `params.sh` for untouched Gaussians, and513// skip reading `splat_grads` and `params.sh` for untouched Gaussians, and
498// zeros untouched SH rows with warp-coalesced float4 stores (0 shared memory).514// zeros untouched SH rows with warp-coalesced 16-byte stores (0 shared memory).
499515
500template <typename ShT>516template <typename ShT>
501__global__ void __launch_bounds__(kGaussianThreads)517__global__ void __launch_bounds__(kGaussianThreads)
502 PreprocessBackwardKernel(GaussianParams params, Frame frame,518 PreprocessBackwardKernel(GaussianParams params, Frame frame,
↕ Expand 144 unchanged lines (lines 519–662) · in PreprocessBackwardKernel
503 const float* __restrict__ splat_grads,519 const float* __restrict__ splat_grads,
504 const uint8_t* __restrict__ splat_touched,520 const uint8_t* __restrict__ splat_touched,
505 GaussianGrads grads) {521 GaussianGrads grads) {
506 constexpr int kShVec16 = ShIo<ShT>::kRowVec16; // 16-byte stores per row.522 constexpr int kShVec16 = ShIo<ShT>::kRowVec16; // 16-byte stores per row.
507 const int i = blockIdx.x * blockDim.x + threadIdx.x;523 const int i = blockIdx.x * blockDim.x + threadIdx.x;
508 const int lane = threadIdx.x % 32;524 const int lane = threadIdx.x % 32;
509 bool touched = (i < params.n && splat_touched[i] != 0u);525 bool touched = (i < params.n && splat_touched[i] != 0u);
510 const uint32_t init_mask = __ballot_sync(warp::kFullMask, touched);526 const uint32_t init_mask = __ballot_sync(warp::kFullMask, touched);
511 if (init_mask == 0u) {527 if (init_mask == 0u) {
512 if (i < params.n) {528 if (i < params.n) {
513 grads.means[3 * i] = 0.0f;529 grads.means[3 * i] = 0.0f;
514 grads.means[3 * i + 1] = 0.0f;530 grads.means[3 * i + 1] = 0.0f;
515 grads.means[3 * i + 2] = 0.0f;531 grads.means[3 * i + 2] = 0.0f;
516 grads.scales[3 * i] = 0.0f;532 grads.scales[3 * i] = 0.0f;
517 grads.scales[3 * i + 1] = 0.0f;533 grads.scales[3 * i + 1] = 0.0f;
518 grads.scales[3 * i + 2] = 0.0f;534 grads.scales[3 * i + 2] = 0.0f;
519 reinterpret_cast<float4*>(grads.quats)[i] = {0.0f, 0.0f, 0.0f, 0.0f};535 reinterpret_cast<float4*>(grads.quats)[i] = {0.0f, 0.0f, 0.0f, 0.0f};
520 grads.opacities[i] = 0.0f;536 grads.opacities[i] = 0.0f;
521 }537 }
522 const int warp_i0 =538 const int warp_i0 =
523 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;539 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;
524 const int warp_n = max(0, min(32, params.n - warp_i0));540 const int warp_n = max(0, min(32, params.n - warp_i0));
525 uint4* warp_sh_dst =541 uint4* warp_sh_dst =
526 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));542 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));
527 const uint4 zero = {0u, 0u, 0u, 0u};543 const uint4 zero = {0u, 0u, 0u, 0u};
528 if (warp_n == 32) {544 if (warp_n == 32) {
529#pragma unroll545#pragma unroll
530 for (int s = 0; s < kShVec16; ++s) {546 for (int s = 0; s < kShVec16; ++s) {
531 warp_sh_dst[s * 32 + lane] = zero;547 warp_sh_dst[s * 32 + lane] = zero;
532 }548 }
533 } else {549 } else {
534 for (int k = lane; k < warp_n * kShVec16; k += 32) {550 for (int k = lane; k < warp_n * kShVec16; k += 32) {
535 warp_sh_dst[k] = zero;551 warp_sh_dst[k] = zero;
536 }552 }
537 }553 }
538 return;554 return;
539 }555 }
540556
541 float3 dmean = {0.0f, 0.0f, 0.0f};557 float3 dmean = {0.0f, 0.0f, 0.0f};
542 float3 dscale = {0.0f, 0.0f, 0.0f};558 float3 dscale = {0.0f, 0.0f, 0.0f};
543 float4 dquat = {0.0f, 0.0f, 0.0f, 0.0f};559 float4 dquat = {0.0f, 0.0f, 0.0f, 0.0f};
544 float dopacity = 0.0f;560 float dopacity = 0.0f;
545 if (touched) {561 if (touched) {
546 float g[kSplatGradFloats];562 float g[kSplatGradFloats];
547 bool nonzero = false;563 bool nonzero = false;
548#pragma unroll564#pragma unroll
549 for (int q = 0; q < kSplatGradFloats; ++q) {565 for (int q = 0; q < kSplatGradFloats; ++q) {
550 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);566 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);
551 nonzero = nonzero || g[q] != 0.0f;567 nonzero = nonzero || g[q] != 0.0f;
552 }568 }
553 touched = nonzero;569 touched = nonzero;
554 if (touched) {570 if (touched) {
555 ProjectionGrad pg;571 ProjectionGrad pg;
556 pg.xy = {g[0], g[1]};572 pg.xy = {g[0], g[1]};
557 pg.conic = {g[2], g[3], g[4]};573 pg.conic = {g[2], g[3], g[4]};
558 pg.opacity = g[5];574 pg.opacity = g[5];
559 pg.rgb = {g[6], g[7], g[8]};575 pg.rgb = {g[6], g[7], g[8]};
560 dopacity = g[5];576 dopacity = g[5];
561 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),577 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),
562 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);578 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);
563 }579 }
564 }580 }
565 if (i < params.n) {581 if (i < params.n) {
566 grads.means[3 * i] = dmean.x;582 grads.means[3 * i] = dmean.x;
567 grads.means[3 * i + 1] = dmean.y;583 grads.means[3 * i + 1] = dmean.y;
568 grads.means[3 * i + 2] = dmean.z;584 grads.means[3 * i + 2] = dmean.z;
569 grads.scales[3 * i] = dscale.x;585 grads.scales[3 * i] = dscale.x;
570 grads.scales[3 * i + 1] = dscale.y;586 grads.scales[3 * i + 1] = dscale.y;
571 grads.scales[3 * i + 2] = dscale.z;587 grads.scales[3 * i + 2] = dscale.z;
572 reinterpret_cast<float4*>(grads.quats)[i] = dquat;588 reinterpret_cast<float4*>(grads.quats)[i] = dquat;
573 grads.opacities[i] = dopacity;589 grads.opacities[i] = dopacity;
574 }590 }
575 const uint32_t mask = __ballot_sync(warp::kFullMask, touched);591 const uint32_t mask = __ballot_sync(warp::kFullMask, touched);
576 if (mask != warp::kFullMask) {592 if (mask != warp::kFullMask) {
577 const int warp_i0 =593 const int warp_i0 =
578 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;594 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;
579 const int warp_n = max(0, min(32, params.n - warp_i0));595 const int warp_n = max(0, min(32, params.n - warp_i0));
580 uint4* warp_sh_dst =596 uint4* warp_sh_dst =
581 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));597 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));
582 const uint4 zero = {0u, 0u, 0u, 0u};598 const uint4 zero = {0u, 0u, 0u, 0u};
583 if (warp_n == 32) {599 if (warp_n == 32) {
584#pragma unroll600#pragma unroll
585 for (int s = 0; s < kShVec16; ++s) {601 for (int s = 0; s < kShVec16; ++s) {
586 const int k = s * 32 + lane;602 const int k = s * 32 + lane;
587 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;603 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;
588 if (((mask >> row_in_warp) & 1u) == 0u) {604 if (((mask >> row_in_warp) & 1u) == 0u) {
589 warp_sh_dst[k] = zero;605 warp_sh_dst[k] = zero;
590 }606 }
591 }607 }
592 } else {608 } else {
593 for (int k = lane; k < warp_n * kShVec16; k += 32) {609 for (int k = lane; k < warp_n * kShVec16; k += 32) {
594 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;610 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;
595 if (((mask >> row_in_warp) & 1u) == 0u) {611 if (((mask >> row_in_warp) & 1u) == 0u) {
596 warp_sh_dst[k] = zero;612 warp_sh_dst[k] = zero;
597 }613 }
598 }614 }
599 }615 }
600 }616 }
601}617}
602618
603template <typename ShT>619template <typename ShT>
604cudaError_t LaunchPreprocessBackward(const GaussianParams& params,620cudaError_t LaunchPreprocessBackward(const GaussianParams& params,
605 const Frame& frame,621 const Frame& frame,
606 const float* splat_grads,622 const float* splat_grads,
607 const uint8_t* splat_touched,623 const uint8_t* splat_touched,
608 const GaussianGrads& grads,624 const GaussianGrads& grads,
609 cudaStream_t stream) {625 cudaStream_t stream) {
610 PreprocessBackwardKernel<ShT>626 PreprocessBackwardKernel<ShT>
611 <<<GaussianBlocks(params.n), kGaussianThreads, 0, stream>>>(627 <<<GaussianBlocks(params.n), kGaussianThreads, 0, stream>>>(
612 params, frame, splat_grads, splat_touched, grads);628 params, frame, splat_grads, splat_touched, grads);
613 return cudaSuccess;629 return cudaSuccess;
614}630}
615631
616template __global__ void RasterBackwardKernel<1>(632template __global__ void RasterBackwardKernel<1>(
617 const int2* __restrict__, const int32_t* __restrict__,633 const int2* __restrict__, const int32_t* __restrict__,
618 const Splat* __restrict__, Frame, const float* __restrict__,634 const Splat* __restrict__, Frame, const float* __restrict__,
619 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,635 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
620 uint8_t* __restrict__);636 uint8_t* __restrict__);
621template __global__ void RasterBackwardKernel<2>(637template __global__ void RasterBackwardKernel<2>(
622 const int2* __restrict__, const int32_t* __restrict__,638 const int2* __restrict__, const int32_t* __restrict__,
623 const Splat* __restrict__, Frame, const float* __restrict__,639 const Splat* __restrict__, Frame, const float* __restrict__,
624 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,640 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
625 uint8_t* __restrict__);641 uint8_t* __restrict__);
626template __global__ void RasterBackwardKernel<4>(642template __global__ void RasterBackwardKernel<4>(
627 const int2* __restrict__, const int32_t* __restrict__,643 const int2* __restrict__, const int32_t* __restrict__,
628 const Splat* __restrict__, Frame, const float* __restrict__,644 const Splat* __restrict__, Frame, const float* __restrict__,
629 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,645 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
630 uint8_t* __restrict__);646 uint8_t* __restrict__);
631template __global__ void PreprocessBackwardKernel<float>(647template __global__ void PreprocessBackwardKernel<float>(
632 GaussianParams, Frame, const float* __restrict__,648 GaussianParams, Frame, const float* __restrict__,
633 const uint8_t* __restrict__, GaussianGrads);649 const uint8_t* __restrict__, GaussianGrads);
634template __global__ void PreprocessBackwardKernel<ShBf16>(650template __global__ void PreprocessBackwardKernel<ShBf16>(
635 GaussianParams, Frame, const float* __restrict__,651 GaussianParams, Frame, const float* __restrict__,
636 const uint8_t* __restrict__, GaussianGrads);652 const uint8_t* __restrict__, GaussianGrads);
637template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,653template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,
638 const Frame&, const float*,654 const Frame&, const float*,
639 const uint8_t*,655 const uint8_t*,
640 const GaussianGrads&,656 const GaussianGrads&,
641 cudaStream_t);657 cudaStream_t);
642template cudaError_t LaunchPreprocessBackward<ShBf16>(658template cudaError_t LaunchPreprocessBackward<ShBf16>(
643 const GaussianParams&, const Frame&, const float*, const uint8_t*,659 const GaussianParams&, const Frame&, const float*, const uint8_t*,
644 const GaussianGrads&, cudaStream_t);660 const GaussianGrads&, cudaStream_t);
645661
646} // namespace gs662} // namespace gs
↕ Expand 197 unchanged lines (lines 1–197) · in ProjectVjp
11 /*
22 * Copyright (C) 2023, Inria
33 * GRAPHDECO research group, https://team.inria.fr/graphdeco
44 * All rights reserved.
55 *
66 * This software is free for non-commercial, research and evaluation use
77 * under the terms of the LICENSE.md file.
88 *
99 * For inquiries contact george.drettakis@inria.fr
1010 */
1111
1212 #include "backward.h"
1313
1414 #include "auxiliary.h"
1515
1616 namespace gs {
1717
1818 namespace sh {
1919 // Evaluates SH color VJP 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>
202−__global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 4)
202+__global__ void __launch_bounds__(RasterLayout<kRows>::kThreads, 6)
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;
214− constexpr int kWarps = L::kWarps;
215214 const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x;
216215 const int tid = threadIdx.x;
217216 const int warp = tid / 32;
218217 const int lane = tid % 32;
↕ Expand 30 unchanged lines (lines 218–247) · in PixelState
219218 const RasterPixels px = RasterPixels::For<kRows>(tid);
220219 const float fx = static_cast<float>(px.x);
221220 const int2 range = ranges[tile];
222221
223222 struct PixelState {
224223 float t;
225224 float3 dpix;
226225 float accum; // dot(color accumulated behind, dpix)
227226 int last;
228227 };
229228 PixelState st[kRasterRows];
230229 const int pixels = frame.width * frame.height;
231230 int thread_last = 0;
232231 #pragma unroll
233232 for (int k = 0; k < kRasterRows; ++k) {
234233 const int y = px.y0 + k;
235234 const bool inside = px.x < frame.width && y < frame.height;
236235 const int p = y * frame.width + px.x;
237236 st[k].t = inside ? final_t[p] : 0.0f;
238237 st[k].dpix =
239238 inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p],
240239 dl_dimage[2 * pixels + p]}
241240 : float3{0.0f, 0.0f, 0.0f};
242241 st[k].accum = 0.0f;
243242 st[k].last = inside ? n_contrib[p] : 0;
244243 thread_last = max(thread_last, st[k].last);
245244 }
246245
247246 int warp_max_last = thread_last;
248247 #pragma unroll
249248 for (int offset = 16; offset > 0; offset /= 2) {
250249 warp_max_last = max(
251250 warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset));
252251 }
253− __shared__ int shared_max_last[kWarps];
254− if (lane == 0) shared_max_last[warp] = warp_max_last;
255− __syncthreads();
256− int block_max_last = 0;
257−#pragma unroll
258− for (int w = 0; w < kWarps; ++w) {
259− block_max_last = max(block_max_last, shared_max_last[w]);
260− }
261− if (block_max_last <= 0) {
262− return;
263− }
264252
253+ // Each warp stages its own splats in its 32-entry slice of these arrays,
254+ // so warps never wait for each other (no block barriers).
265255 __shared__ float4 batch_geom0[kRasterThreads];
266256 __shared__ float batch_geom1[kRasterThreads];
267257 __shared__ float4 batch_rgb_o[kRasterThreads];
268258 __shared__ uint32_t batch_id[kRasterThreads];
269− __shared__ uint8_t batch_mask[kRasterThreads];
270− const uint8_t warp_bit = static_cast<uint8_t>(1u << warp);
259+ const uint32_t warp_bit = 1u << warp;
260+ const int warp_base = warp * 32;
271261 int scatter = -1;
272262 int scatter2 = -1;
273263 if constexpr (L::kWarpWidth == 8) {
274264 const int sub_lane = lane & 15;
↕ Expand 25 unchanged lines (lines 265–289) · in PixelState
275265 const int row = sub_lane >> 3, col = sub_lane & 7;
276266 if (col == 0) {
277267 scatter2 = row;
278268 } else if (col == 2) {
279269 scatter2 = 2 + row;
280270 } else if (col == 4) {
281271 scatter2 = row == 0 ? 4 : 7;
282272 } else if (col == 6) {
283273 scatter2 = row == 0 ? 6 : 8;
284274 } else if (col == 1 && row == 0) {
285275 scatter2 = 5;
286276 }
287277 const int full_row = lane >> 3, full_col = lane & 7;
288278 if (full_col == 0) {
289279 scatter = full_row;
290280 } else if (full_col == 2) {
291281 scatter = full_row == 0 ? 4 : 5 + full_row;
292282 } else if (full_col == 4 && full_row == 0) {
293283 scatter = 5;
294284 }
295285 } else {
296286 scatter = warp::ScatterIndex(kSplatGradFloats, lane);
297287 }
298288
299289 // Evaluates one splat at this thread's pixels and accumulates the per-thread
300290 // partial gradient sums. Branch-free: lanes of a warp almost never agree on
301291 // which pixels a splat reaches, so predication is cheaper than branching.
302292 // Pixels the splat does not reach get alpha = 0, which leaves t and accum
303293 // exactly unchanged (t * rcp(1) == t) and contributes zero gradient.
294+ //
295+ // Per pixel, ga = alpha_raw T diff = dL/dpower (log2 units, up to the
296+ // constant), opacity included: the opacity gradient divides it back out.
297+ // Returns ga_sum = sum ga, ga_dy = sum ga dy, ga_dy2 = sum ga dy^2. Since the
298+ // rows are dy_k = dy_0 - k, the last two are formed from the moments
299+ // m1 = sum k ga_k and m2 = sum k^2 ga_k: sum ga dy = dy_0 ga_sum - m1 and
300+ // sum ga dy^2 = dy_0 (dy_0 ga_sum - 2 m1) + m2.
304301 auto eval_splat = [&](float4 g0, float c2, float4 ro, int entry, float& dx,
305302 float& ga_sum, float& ga_dy, float& ga_dy2,
306303 float3& drgb) {
307304 dx = __fsub_rn(g0.x, fx);
308− const Power2Column col = Power2Column::At(g0.z, g0.w, dx);
305+ const Power2Column col = Power2Column::At(g0.z, g0.w, ro.w, dx);
309− ga_sum = 0.0f;
306+ float ga[kRasterRows];
310− ga_dy = 0.0f;
307+ float dy0 = 0.0f;
311− ga_dy2 = 0.0f;
312308 drgb = {0.0f, 0.0f, 0.0f};
313309 #pragma unroll
314310 for (int k = 0; k < kRasterRows; ++k) {
315311 PixelState& ps = st[k];
316312 const float dy = __fsub_rn(g0.y, static_cast<float>(px.y0 + k));
313+ if (k == 0) dy0 = dy;
317− const float power = col.Row(c2, dy);
314+ const float alpha_raw = Exp2Approx(col.Row(c2, dy));
318− const float g_val = Exp2Approx(power);
319− const float alpha_k = SplatAlpha(ro.w, g_val);
320− const bool hit = entry < ps.last && power <= 0.0f && alpha_k >= kMinAlpha;
315+ const bool hit = entry < ps.last && alpha_raw >= kMinAlpha;
321− const float alpha = hit ? alpha_k : 0.0f;
316+ const float alpha = hit ? fminf(kMaxAlpha, alpha_raw) : 0.0f;
322317 ps.t = ps.t * RcpApprox(1.0f - alpha);
323318 const float w = alpha * ps.t;
324319 const float color_dot =
325320 ro.x * ps.dpix.x + ro.y * ps.dpix.y + ro.z * ps.dpix.z;
326321 const float diff = color_dot - ps.accum;
327322 ps.accum += alpha * diff;
328− const float ga = hit ? g_val * (ps.t * diff) : 0.0f;
323+ ga[k] = hit ? alpha_raw * (ps.t * diff) : 0.0f;
329− ga_sum += ga;
330− const float ga_y = ga * dy;
331− ga_dy += ga_y;
332− ga_dy2 += ga_y * dy;
333324 drgb += w * ps.dpix;
325+ }
326+ ga_sum = ga[0];
327+#pragma unroll
328+ for (int k = 1; k < kRasterRows; ++k) ga_sum += ga[k];
329+ if constexpr (kRasterRows == 1) {
330+ ga_dy = ga_sum * dy0;
331+ ga_dy2 = ga_dy * dy0;
332+ } else {
333+ float m1 = ga[1], m2 = ga[1];
334+#pragma unroll
335+ for (int k = 2; k < kRasterRows; ++k) {
336+ m1 = fmaf(static_cast<float>(k), ga[k], m1);
337+ m2 = fmaf(static_cast<float>(k * k), ga[k], m2);
338+ }
339+ ga_dy = fmaf(dy0, ga_sum, -m1);
340+ ga_dy2 = fmaf(dy0, ga_dy - m1, m2);
334341 }
335342 };
336343 // Conic (a, b, c) from the staged log2-scaled one.
337344 auto raw_conic = [](float4 g0, float c2) {
338345 return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w,
339346 kInvPower2Scale * c2};
340347 };
341348
342− for (int end = block_max_last; end > 0; end -= kRasterThreads) {
349+ for (int end = warp_max_last; end > 0; end -= 32) {
350+ // Lane l stages entry end - 1 - l if this warp contributed to it.
343− const int e = end - 1 - tid;
351+ const int e = end - 1 - lane;
352+ bool mine = false;
344353 if (e >= 0) {
345354 const uint32_t packed = static_cast<uint32_t>(point_list[range.x + e]);
355+ mine = ((packed >> 24) & warp_bit) != 0u;
356+ if (mine) {
346357⇥ const uint32_t id = packed & 0x00ffffffu;
347358⇥ splat_touched[id] = 1u;
348359⇥ const float4 p0 = __ldg(&splats[id].p0);
349360⇥ const float4 p1 = __ldg(&splats[id].p1);
350361⇥ const float4 p2 = __ldg(&splats[id].p2);
351362⇥ const float3 c2 = Power2Conic(p0.z, p0.w, p1.x);
352363⇥ batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y};
353364⇥ batch_geom1[tid] = c2.z;
354− batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y};
365+ batch_rgb_o[tid] = p2;
355366⇥ batch_id[tid] = id;
356− batch_mask[tid] = static_cast<uint8_t>(packed >> 24);
357− } else {
358− batch_mask[tid] = 0u;
359367⇥ }
360− __syncthreads();
361− const int count = min(kRasterThreads, end);
362−#pragma unroll
363− for (int c = 0; c < kRasterThreads; c += 32) {
364− if (c < count && end - 32 - c < warp_max_last) {
368+ }
365− uint32_t mask = __ballot_sync(
369+ uint32_t mask = __ballot_sync(warp::kFullMask, mine);
366− warp::kFullMask,
367− c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u);
370+ __syncwarp();
371+ {
372+ {
368373 if constexpr (L::kWarpWidth == 8) {
369374 while ((mask & (mask - 1u)) != 0u) {
370375 const int bit0 = __ffs(mask) - 1;
371376 mask &= mask - 1u;
372377 const int bit1 = __ffs(mask) - 1;
373378 mask &= mask - 1u;
374− const int j0 = c + bit0, j1 = c + bit1;
379+ const int j0 = warp_base + bit0, j1 = warp_base + bit1;
375380 const float4 g0_0 = batch_geom0[j0];
376381 const float c2_0 = batch_geom1[j0];
377382 const float4 ro_0 = batch_rgb_o[j0];
378383 const float4 g0_1 = batch_geom0[j1];
379384 const float c2_1 = batch_geom1[j1];
380385 const float4 ro_1 = batch_rgb_o[j1];
381386
382387 float dx0, ga_sum0, ga_dy0, ga_dy2_0;
383388 float3 drgb0;
384− eval_splat(g0_0, c2_0, ro_0, end - 1 - j0, dx0, ga_sum0, ga_dy0,
389+ eval_splat(g0_0, c2_0, ro_0, end - 1 - bit0, dx0, ga_sum0, ga_dy0,
385390 ga_dy2_0, drgb0);
386391
387392 float dx1, ga_sum1, ga_dy1, ga_dy2_1;
388393 float3 drgb1;
389− eval_splat(g0_1, c2_1, ro_1, end - 1 - j1, dx1, ga_sum1, ga_dy1,
394+ eval_splat(g0_1, c2_1, ro_1, end - 1 - bit1, dx1, ga_sum1, ga_dy1,
390395 ga_dy2_1, drgb1);
391396
392397 const bool hi16 = (lane & 16) != 0;
398+ const bool row1 = (lane & 8) != 0;
393− const float opacity = hi16 ? ro_1.w : ro_0.w;
399+ const float log2_opacity = hi16 ? ro_1.w : ro_0.w;
394400 const float dx = hi16 ? dx1 : dx0;
401+ // This lane's row of the raw conic [[a, b], [b, c]] times -1:
402+ // lanes 0-7 hold (a, b), lanes 8-15 (b, c), from the staged
403+ // (A, B, C) = kPower2Scale (a, 2b, c). Selecting before scaling
404+ // leaves two multiplies.
405+ const float4 g0_sel = hi16 ? g0_1 : g0_0;
406+ const float c2_sel = hi16 ? c2_1 : c2_0;
395− const float3 cn =
407+ const float cn_x =
396− hi16 ? raw_conic(g0_1, c2_1) : raw_conic(g0_0, c2_0);
408+ (row1 ? g0_sel.w : g0_sel.z) *
409+ (row1 ? -0.5f * kInvPower2Scale : -kInvPower2Scale);
410+ const float cn_y =
411+ (row1 ? c2_sel : g0_sel.w) *
412+ (row1 ? -kInvPower2Scale : -0.5f * kInvPower2Scale);
397413
398414 auto fold16 = [&](float v0, float v1) {
399415 const float send = hi16 ? v0 : v1;
400416 const float recv = __shfl_xor_sync(warp::kFullMask, send, 16);
401417 return (hi16 ? v1 : v0) + recv;
402418 };
403419 float ga_sum = fold16(ga_sum0, ga_sum1);
404420 float ga_dy = fold16(ga_dy0, ga_dy1);
405− float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1),
421+ float b4[4] = {-0.5f * fold16(ga_dy2_0, ga_dy2_1),
406422 fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y),
407423 fold16(drgb0.z, drgb1.z)};
408424 warp::internal::HalvingStep<4, 8>(b4, lane);
409425 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
410426 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
411427
412− const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
428+ const float p_sum = ga_sum, p_dy = ga_dy;
413429 const float p_dx = dx * p_sum;
414− const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
430+ const float v_dx0 = fmaf(cn_x, p_dx, cn_y * p_dy);
415− : -(cn.y * p_dx + cn.z * p_dy);
416− const float v_dx1 =
431+ const float v_dx1 = dx * (row1 ? -p_dy : -0.5f * p_dx);
417− (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
418432 float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]};
419433 warp::internal::HalvingStep<4, 4>(v4, lane);
420434 warp::internal::HalvingStep<2, 2>(v4, lane);
421435 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4);
422436 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2);
423437 const float v_sum =
424438 v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1);
425439 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1);
440+ const float out_grad =
426− const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum;
441+ ((lane & 7) == 1) ? ga_sum * Exp2Approx(-log2_opacity) : v_sum;
427442 if (scatter2 >= 0 && out_grad != 0.0f) {
428443 const uint32_t target_id = batch_id[hi16 ? j1 : j0];
429444 atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2,
430445 out_grad);
431446 }
432447 }
433448 }
434449 while (mask != 0u) {
435450 const int bit = __ffs(mask) - 1;
436451 mask &= mask - 1u;
437− const int j = c + bit;
452+ const int j = warp_base + bit;
438453 const float4 g0 = batch_geom0[j];
439454 const float c2 = batch_geom1[j];
440455 const float4 ro = batch_rgb_o[j];
441456 float dx, ga_sum, ga_dy, ga_dy2;
442457 float3 drgb;
443− eval_splat(g0, c2, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2, drgb);
458+ eval_splat(g0, c2, ro, end - 1 - bit, dx, ga_sum, ga_dy, ga_dy2,
459+ drgb);
444− const float opacity = ro.w;
460+ const float inv_opacity = Exp2Approx(-ro.w);
445461 const float3 cn = raw_conic(g0, c2);
446462 float out_grad = 0.0f;
447463 if constexpr (L::kWarpWidth == 8) {
448− float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z};
464+ float b4[4] = {-0.5f * ga_dy2, drgb.x, drgb.y, drgb.z};
449465 warp::internal::HalvingStep<4, 16>(b4, lane);
450466 warp::internal::HalvingStep<2, 8>(b4, lane);
451467 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16);
452468 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16);
453469 ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8);
454470 ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8);
455− const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
471+ const float p_sum = ga_sum, p_dy = ga_dy;
456472 const float p_dx = dx * p_sum;
457473 float v_dx;
458474 if ((lane & 16) == 0) {
459475 v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy)
460476 : -(cn.y * p_dx + cn.z * p_dy);
461477 } else {
462478 v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy;
463479 }
464− float v[3] = {v_dx, b4[0], ga_sum};
480+ float v[3] = {v_dx, b4[0], ga_sum * inv_opacity};
465481 warp::internal::HalvingStep<3, 4>(v, lane);
466482 warp::internal::HalvingStep<2, 2>(v, lane);
467483 out_grad = v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1);
468484 } else {
469− const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy;
485+ const float p_sum = ga_sum, p_dy = ga_dy;
470486 const float p_dx = dx * p_sum;
471487 float gr[kSplatGradFloats] = {-(cn.x * p_dx + cn.y * p_dy), // xy
472488 -(cn.y * p_dx + cn.z * p_dy),
473489 -0.5f * dx * p_dx, // conic
474490 -dx * p_dy,
475− -0.5f * opacity * ga_dy2,
491+ -0.5f * ga_dy2,
476− ga_sum, // opacity
492+ ga_sum * inv_opacity, // opacity
477493 drgb.x,
478494 drgb.y,
479495 drgb.z}; // rgb
480496 warp::ReduceScatter(gr, lane);
481497 out_grad = gr[0];
482498 }
483499 if (scatter >= 0 && out_grad != 0.0f) {
484500 atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter,
485501 out_grad);
486502 }
487503 }
488504 }
489505 }
490− __syncthreads();
506+ __syncwarp(); // The slice is restaged next iteration.
491507 }
492508 }
493509
494510 // ---------------------------------------------------------------------------
495511 // Preprocess backward: splat gradients -> parameter gradients (dense).
496512 // Uses the 1-byte `splat_touched` flag written by `RasterBackwardKernel` to
497513 // skip reading `splat_grads` and `params.sh` for untouched Gaussians, and
498−// zeros untouched SH rows with warp-coalesced float4 stores (0 shared memory).
514+// zeros untouched SH rows with warp-coalesced 16-byte stores (0 shared memory).
499515
500516 template <typename ShT>
501517 __global__ void __launch_bounds__(kGaussianThreads)
502518 PreprocessBackwardKernel(GaussianParams params, Frame frame,
↕ Expand 144 unchanged lines (lines 519–662) · in PreprocessBackwardKernel
503519 const float* __restrict__ splat_grads,
504520 const uint8_t* __restrict__ splat_touched,
505521 GaussianGrads grads) {
506522 constexpr int kShVec16 = ShIo<ShT>::kRowVec16; // 16-byte stores per row.
507523 const int i = blockIdx.x * blockDim.x + threadIdx.x;
508524 const int lane = threadIdx.x % 32;
509525 bool touched = (i < params.n && splat_touched[i] != 0u);
510526 const uint32_t init_mask = __ballot_sync(warp::kFullMask, touched);
511527 if (init_mask == 0u) {
512528 if (i < params.n) {
513529 grads.means[3 * i] = 0.0f;
514530 grads.means[3 * i + 1] = 0.0f;
515531 grads.means[3 * i + 2] = 0.0f;
516532 grads.scales[3 * i] = 0.0f;
517533 grads.scales[3 * i + 1] = 0.0f;
518534 grads.scales[3 * i + 2] = 0.0f;
519535 reinterpret_cast<float4*>(grads.quats)[i] = {0.0f, 0.0f, 0.0f, 0.0f};
520536 grads.opacities[i] = 0.0f;
521537 }
522538 const int warp_i0 =
523539 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;
524540 const int warp_n = max(0, min(32, params.n - warp_i0));
525541 uint4* warp_sh_dst =
526542 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));
527543 const uint4 zero = {0u, 0u, 0u, 0u};
528544 if (warp_n == 32) {
529545 #pragma unroll
530546 for (int s = 0; s < kShVec16; ++s) {
531547 warp_sh_dst[s * 32 + lane] = zero;
532548 }
533549 } else {
534550 for (int k = lane; k < warp_n * kShVec16; k += 32) {
535551 warp_sh_dst[k] = zero;
536552 }
537553 }
538554 return;
539555 }
540556
541557 float3 dmean = {0.0f, 0.0f, 0.0f};
542558 float3 dscale = {0.0f, 0.0f, 0.0f};
543559 float4 dquat = {0.0f, 0.0f, 0.0f, 0.0f};
544560 float dopacity = 0.0f;
545561 if (touched) {
546562 float g[kSplatGradFloats];
547563 bool nonzero = false;
548564 #pragma unroll
549565 for (int q = 0; q < kSplatGradFloats; ++q) {
550566 g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]);
551567 nonzero = nonzero || g[q] != 0.0f;
552568 }
553569 touched = nonzero;
554570 if (touched) {
555571 ProjectionGrad pg;
556572 pg.xy = {g[0], g[1]};
557573 pg.conic = {g[2], g[3], g[4]};
558574 pg.opacity = g[5];
559575 pg.rgb = {g[6], g[7], g[8]};
560576 dopacity = g[5];
561577 ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i),
562578 ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat);
563579 }
564580 }
565581 if (i < params.n) {
566582 grads.means[3 * i] = dmean.x;
567583 grads.means[3 * i + 1] = dmean.y;
568584 grads.means[3 * i + 2] = dmean.z;
569585 grads.scales[3 * i] = dscale.x;
570586 grads.scales[3 * i + 1] = dscale.y;
571587 grads.scales[3 * i + 2] = dscale.z;
572588 reinterpret_cast<float4*>(grads.quats)[i] = dquat;
573589 grads.opacities[i] = dopacity;
574590 }
575591 const uint32_t mask = __ballot_sync(warp::kFullMask, touched);
576592 if (mask != warp::kFullMask) {
577593 const int warp_i0 =
578594 static_cast<int>(blockIdx.x * blockDim.x) + (threadIdx.x / 32) * 32;
579595 const int warp_n = max(0, min(32, params.n - warp_i0));
580596 uint4* warp_sh_dst =
581597 reinterpret_cast<uint4*>(ShRow<ShT>(grads.sh, warp_i0));
582598 const uint4 zero = {0u, 0u, 0u, 0u};
583599 if (warp_n == 32) {
584600 #pragma unroll
585601 for (int s = 0; s < kShVec16; ++s) {
586602 const int k = s * 32 + lane;
587603 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;
588604 if (((mask >> row_in_warp) & 1u) == 0u) {
589605 warp_sh_dst[k] = zero;
590606 }
591607 }
592608 } else {
593609 for (int k = lane; k < warp_n * kShVec16; k += 32) {
594610 const unsigned row_in_warp = static_cast<unsigned>(k) / kShVec16;
595611 if (((mask >> row_in_warp) & 1u) == 0u) {
596612 warp_sh_dst[k] = zero;
597613 }
598614 }
599615 }
600616 }
601617 }
602618
603619 template <typename ShT>
604620 cudaError_t LaunchPreprocessBackward(const GaussianParams& params,
605621 const Frame& frame,
606622 const float* splat_grads,
607623 const uint8_t* splat_touched,
608624 const GaussianGrads& grads,
609625 cudaStream_t stream) {
610626 PreprocessBackwardKernel<ShT>
611627 <<<GaussianBlocks(params.n), kGaussianThreads, 0, stream>>>(
612628 params, frame, splat_grads, splat_touched, grads);
613629 return cudaSuccess;
614630 }
615631
616632 template __global__ void RasterBackwardKernel<1>(
617633 const int2* __restrict__, const int32_t* __restrict__,
618634 const Splat* __restrict__, Frame, const float* __restrict__,
619635 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
620636 uint8_t* __restrict__);
621637 template __global__ void RasterBackwardKernel<2>(
622638 const int2* __restrict__, const int32_t* __restrict__,
623639 const Splat* __restrict__, Frame, const float* __restrict__,
624640 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
625641 uint8_t* __restrict__);
626642 template __global__ void RasterBackwardKernel<4>(
627643 const int2* __restrict__, const int32_t* __restrict__,
628644 const Splat* __restrict__, Frame, const float* __restrict__,
629645 const int32_t* __restrict__, const float* __restrict__, float* __restrict__,
630646 uint8_t* __restrict__);
631647 template __global__ void PreprocessBackwardKernel<float>(
632648 GaussianParams, Frame, const float* __restrict__,
633649 const uint8_t* __restrict__, GaussianGrads);
634650 template __global__ void PreprocessBackwardKernel<ShBf16>(
635651 GaussianParams, Frame, const float* __restrict__,
636652 const uint8_t* __restrict__, GaussianGrads);
637653 template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&,
638654 const Frame&, const float*,
639655 const uint8_t*,
640656 const GaussianGrads&,
641657 cudaStream_t);
642658 template cudaError_t LaunchPreprocessBackward<ShBf16>(
643659 const GaussianParams&, const Frame&, const float*, const uint8_t*,
644660 const GaussianGrads&, cudaStream_t);
645661
646662 } // namespace gs
csrc/rasterizer_impl.h +2 −2
s05_branchless_predication/csrc/rasterizer_impl.h
s06_multirow_and_pipe_math/csrc/rasterizer_impl.h
↕ Expand 17 unchanged lines (lines 1–17)
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#pragma once12#pragma once
1313
14#include <cstddef>14#include <cstddef>
15#include <cstdint>15#include <cstdint>
16#include <iterator>16#include <iterator>
1717
18#include "auxiliary.h"18#include "auxiliary.h"
1919
20namespace gs {20namespace gs {
2121
22inline constexpr int kDefaultForwardRows = 1;22inline constexpr int kDefaultForwardRows = 2;
23inline constexpr int kDefaultBackwardRows = 1;23inline constexpr int kDefaultBackwardRows = 2;
2424
25// Number of bits needed to represent tile ids in [0, num_tiles).25// Number of bits needed to represent tile ids in [0, num_tiles).
26inline int TileBits(int num_tiles) {26inline int TileBits(int num_tiles) {
27 int bits = 1;27 int bits = 1;
↕ Expand 41 unchanged lines (lines 28–68) · in TileRangesKernel
28 while ((1 << bits) < num_tiles) ++bits;28 while ((1 << bits) < num_tiles) ++bits;
29 return bits;29 return bits;
30}30}
3131
32struct GatherCountsIter {32struct GatherCountsIter {
33 const int32_t* counts;33 const int32_t* counts;
34 const int32_t* order;34 const int32_t* order;
35 __host__ __device__ __forceinline__ int32_t operator[](ptrdiff_t idx) const {35 __host__ __device__ __forceinline__ int32_t operator[](ptrdiff_t idx) const {
36 return counts[order[idx]];36 return counts[order[idx]];
37 }37 }
38 __host__ __device__ __forceinline__ GatherCountsIter38 __host__ __device__ __forceinline__ GatherCountsIter
39 operator+(ptrdiff_t offset) const {39 operator+(ptrdiff_t offset) const {
40 return {counts, order + offset};40 return {counts, order + offset};
41 }41 }
42 __host__ __device__ __forceinline__ int32_t operator*() const {42 __host__ __device__ __forceinline__ int32_t operator*() const {
43 return counts[*order];43 return counts[*order];
44 }44 }
45};45};
4646
47template <Culling kCulling>47template <Culling kCulling>
48__global__ void EmitPairsKernel(const Splat* __restrict__ splats,48__global__ void EmitPairsKernel(const Splat* __restrict__ splats,
49 const int32_t* __restrict__ sorted_ids,49 const int32_t* __restrict__ sorted_ids,
50 const int32_t* __restrict__ offsets, int n_vis,50 const int32_t* __restrict__ offsets, int n_vis,
51 Frame frame, uint16_t* __restrict__ keys,51 Frame frame, uint16_t* __restrict__ keys,
52 int32_t* __restrict__ values);52 int32_t* __restrict__ values);
5353
54__global__ void TileRangesKernel(const uint16_t* __restrict__ sorted_tiles,54__global__ void TileRangesKernel(const uint16_t* __restrict__ sorted_tiles,
55 int num_pairs, int2* __restrict__ ranges);55 int num_pairs, int2* __restrict__ ranges);
5656
57} // namespace gs57} // namespace gs
5858
59namespace std {59namespace std {
60template <>60template <>
61struct iterator_traits<gs::GatherCountsIter> {61struct iterator_traits<gs::GatherCountsIter> {
62 using difference_type = ptrdiff_t;62 using difference_type = ptrdiff_t;
63 using value_type = int32_t;63 using value_type = int32_t;
64 using pointer = const int32_t*;64 using pointer = const int32_t*;
65 using reference = int32_t;65 using reference = int32_t;
66 using iterator_category = random_access_iterator_tag;66 using iterator_category = random_access_iterator_tag;
67};67};
68} // namespace std68} // namespace std
↕ Expand 17 unchanged lines (lines 1–17)
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 #pragma once
1313
1414 #include <cstddef>
1515 #include <cstdint>
1616 #include <iterator>
1717
1818 #include "auxiliary.h"
1919
2020 namespace gs {
2121
22−inline constexpr int kDefaultForwardRows = 1;
22+inline constexpr int kDefaultForwardRows = 2;
23−inline constexpr int kDefaultBackwardRows = 1;
23+inline constexpr int kDefaultBackwardRows = 2;
2424
2525 // Number of bits needed to represent tile ids in [0, num_tiles).
2626 inline int TileBits(int num_tiles) {
2727 int bits = 1;
↕ Expand 41 unchanged lines (lines 28–68) · in TileRangesKernel
2828 while ((1 << bits) < num_tiles) ++bits;
2929 return bits;
3030 }
3131
3232 struct GatherCountsIter {
3333 const int32_t* counts;
3434 const int32_t* order;
3535 __host__ __device__ __forceinline__ int32_t operator[](ptrdiff_t idx) const {
3636 return counts[order[idx]];
3737 }
3838 __host__ __device__ __forceinline__ GatherCountsIter
3939 operator+(ptrdiff_t offset) const {
4040 return {counts, order + offset};
4141 }
4242 __host__ __device__ __forceinline__ int32_t operator*() const {
4343 return counts[*order];
4444 }
4545 };
4646
4747 template <Culling kCulling>
4848 __global__ void EmitPairsKernel(const Splat* __restrict__ splats,
4949 const int32_t* __restrict__ sorted_ids,
5050 const int32_t* __restrict__ offsets, int n_vis,
5151 Frame frame, uint16_t* __restrict__ keys,
5252 int32_t* __restrict__ values);
5353
5454 __global__ void TileRangesKernel(const uint16_t* __restrict__ sorted_tiles,
5555 int num_pairs, int2* __restrict__ ranges);
5656
5757 } // namespace gs
5858
5959 namespace std {
6060 template <>
6161 struct iterator_traits<gs::GatherCountsIter> {
6262 using difference_type = ptrdiff_t;
6363 using value_type = int32_t;
6464 using pointer = const int32_t*;
6565 using reference = int32_t;
6666 using iterator_category = random_access_iterator_tag;
6767 };
6868 } // namespace std
Unchanged files in s06_multirow_and_pipe_math (identical to s05_branchless_predication): csrc/config.h, csrc/forward.h, csrc/backward.h, csrc/rasterizer_impl.cu.cc