s01_literature → s02_reduce_scatterStep 2: Warp Reduce-Scatter & Two-Splat Half-Warp Folding · +234 / −55 lines across 2 changed files
csrc/auxiliary.h): Introduces warp::internal::HalvingStep so paired lanes split the active gradient array in half at each step, finishing with distinct threads holding distinct gradient sums.csrc/backward.cu.cc): In RasterBackwardKernel, pairs consecutive active splats via while ((mask & (mask - 1u)) != 0u), folds Splat 0 into the lower half-warp (T0..T15) and Splat 1 into the upper half-warp (T16..T31) via fold16 (10 shuffles/splat), and issues all 18 global atomicAdds simultaneously from 18 distinct threads (with a 12-shuffle halving path for any odd remainder splat).csrc/backward.cu.cc): RasterBackwardKernel is now compiled with __launch_bounds__(256, 4) (was 6), i.e. 4 resident blocks of 256 threads per SM, which lets the compiler use up to 64 registers per thread without spilling; and a pair whose splats no pixel of the warp blends skips the folding, reduction and atomics entirely via if (!__any_sync(warp::kFullMask, hit0 || hit1)) continue; (the odd remainder splat keeps the same __any_sync skip).s01_literature/csrc/auxiliary.hs02_reduce_scatter/csrc/auxiliary.h Expand 297 unchanged lines (lines 1–297) · in ForEachTileRow | |||
| 1 | /* | 1 | /* |
| 2 | * Copyright (C) 2023, Inria | 2 | * Copyright (C) 2023, Inria |
| 3 | * GRAPHDECO research group, https://team.inria.fr/graphdeco | 3 | * 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 use | 6 | * 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.fr | 9 | * For inquiries contact george.drettakis@inria.fr |
| 10 | */ | 10 | */ |
| 11 | 11 | ||
| 12 | #ifndef CUDA_RASTERIZER_AUXILIARY_H_INCLUDED | 12 | #ifndef CUDA_RASTERIZER_AUXILIARY_H_INCLUDED |
| 13 | #define CUDA_RASTERIZER_AUXILIARY_H_INCLUDED | 13 | #define CUDA_RASTERIZER_AUXILIARY_H_INCLUDED |
| 14 | 14 | ||
| 15 | #include <cuda_runtime.h> | 15 | #include <cuda_runtime.h> |
| 16 | 16 | ||
| 17 | #include <cmath> | 17 | #include <cmath> |
| 18 | #include <cstdint> | 18 | #include <cstdint> |
| 19 | 19 | ||
| 20 | #include "config.h" | 20 | #include "config.h" |
| 21 | #include "stdio.h" | 21 | #include "stdio.h" |
| 22 | 22 | ||
| 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) |
| 25 | 25 | ||
| 26 | // Spherical harmonics coefficients | 26 | // 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}; |
| 36 | 36 | ||
| 37 | namespace gs { | 37 | namespace gs { |
| 38 | 38 | ||
| 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__ |
| 41 | 41 | ||
| 42 | GS_HD float3 operator+(float3 a, float3 b) { | 42 | GS_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 | } |
| 45 | GS_HD float3 operator-(float3 a, float3 b) { | 45 | GS_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 | } |
| 48 | GS_HD float3 operator*(float s, float3 a) { | 48 | GS_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 | } |
| 51 | GS_HD float3& operator+=(float3& a, float3 b) { | 51 | GS_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 | } |
| 57 | GS_HD float dot(float3 a, float3 b) { | 57 | GS_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 | } |
| 60 | 60 | ||
| 61 | // Row-major 3x3 matrix. | 61 | // Row-major 3x3 matrix. |
| 62 | struct Mat3 { | 62 | struct 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 | }; |
| 67 | 67 | ||
| 68 | GS_HD float3 Mul(const Mat3& a, float3 v) { | 68 | GS_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. |
| 72 | GS_HD float3 MulT(const Mat3& a, float3 v) { | 72 | GS_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 | } |
| 75 | 75 | ||
| 76 | // Reinterprets bits; used to pack small integers into float records. | 76 | // Reinterprets bits; used to pack small integers into float records. |
| 77 | GS_HD float AsFloat(uint32_t u) { | 77 | GS_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 | } |
| 84 | GS_HD uint32_t AsUint(float f) { | 84 | GS_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 | } |
| 91 | 91 | ||
| 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]. |
| 94 | inline constexpr int kCameraFloats = 20; | 94 | inline constexpr int kCameraFloats = 20; |
| 95 | 95 | ||
| 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. |
| 98 | struct Camera { | 98 | struct 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; |
| 104 | 104 | ||
| 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 | } |
| 119 | 119 | ||
| 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 | }; |
| 125 | 125 | ||
| 126 | // Screen tiling. Tiles are `kTileSize` x `kTileSize` pixels; one thread block | 126 | // Screen tiling. Tiles are `kTileSize` x `kTileSize` pixels; one thread block |
| 127 | // rasterizes one tile. | 127 | // rasterizes one tile. |
| 128 | inline constexpr int kTileSize = 16; | 128 | inline constexpr int kTileSize = 16; |
| 129 | inline constexpr int kTilePixels = kTileSize * kTileSize; | 129 | inline constexpr int kTilePixels = kTileSize * kTileSize; |
| 130 | 130 | ||
| 131 | struct TileGrid { | 131 | struct 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 | }; |
| 139 | 139 | ||
| 140 | // Smallest alpha that contributes to a pixel; smaller ones are skipped. | 140 | // Smallest alpha that contributes to a pixel; smaller ones are skipped. |
| 141 | inline constexpr float kMinAlpha = 1.0f / 255.0f; | 141 | inline constexpr float kMinAlpha = 1.0f / 255.0f; |
| 142 | inline constexpr float kMaxAlpha = 0.99f; | 142 | inline constexpr float kMaxAlpha = 0.99f; |
| 143 | // Transmittance below which a pixel stops accumulating. | 143 | // Transmittance below which a pixel stops accumulating. |
| 144 | inline constexpr float kMinTransmittance = 1e-4f; | 144 | inline constexpr float kMinTransmittance = 1e-4f; |
| 145 | 145 | ||
| 146 | // One splat as stored in memory: three float4 so the rasterizer issues three | 146 | // 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 => culled | 149 | // p1 = (conic.c, opacity, limit, radius) radius 0 => culled |
| 150 | // p2 = (r, g, b, min_power) | 150 | // p2 = (r, g, b, min_power) |
| 151 | inline constexpr int kSplatFloats = 12; | 151 | inline constexpr int kSplatFloats = 12; |
| 152 | struct alignas(16) Splat { | 152 | struct alignas(16) Splat { |
| 153 | float4 p0, p1, p2; | 153 | float4 p0, p1, p2; |
| 154 | GS_HD float2 xy() const { return {p0.x, p0.y}; } | 154 | GS_HD float2 xy() const { return {p0.x, p0.y}; } |
| 155 | GS_HD float3 conic() const { return {p0.z, p0.w, p1.x}; } | 155 | GS_HD float3 conic() const { return {p0.z, p0.w, p1.x}; } |
| 156 | GS_HD float opacity() const { return p1.y; } | 156 | GS_HD float opacity() const { return p1.y; } |
| 157 | GS_HD float limit() const { return p1.z; } | 157 | GS_HD float limit() const { return p1.z; } |
| 158 | GS_HD int radius() const { return static_cast<int>(p1.w); } | 158 | GS_HD int radius() const { return static_cast<int>(p1.w); } |
| 159 | GS_HD float3 rgb() const { return {p2.x, p2.y, p2.z}; } | 159 | GS_HD float3 rgb() const { return {p2.x, p2.y, p2.z}; } |
| 160 | }; | 160 | }; |
| 161 | 161 | ||
| 162 | // Gradient w.r.t. a splat's rasterized quantities, accumulated by the raster | 162 | // 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). |
| 164 | inline constexpr int kSplatGradFloats = 9; | 164 | inline constexpr int kSplatGradFloats = 9; |
| 165 | 165 | ||
| 166 | // Half-open tile rectangle [x0, x1) x [y0, y1). | 166 | // Half-open tile rectangle [x0, x1) x [y0, y1). |
| 167 | struct TileRect { | 167 | struct 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 | }; |
| 171 | 171 | ||
| 172 | // Tile rectangle of a splat of integer bounding `radius`, identical to the | 172 | // Tile rectangle of a splat of integer bounding `radius`, identical to the |
| 173 | // reference rasterizer. | 173 | // reference rasterizer. |
| 174 | GS_HD TileRect RectFor(float2 xy, int radius, const TileGrid& grid) { | 174 | GS_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 | } |
| 185 | 185 | ||
| 186 | // How splats are assigned to tiles. | 186 | // How splats are assigned to tiles. |
| 187 | enum class Culling : int { | 187 | enum 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. Gives | 190 | // 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 | }; |
| 194 | 194 | ||
| 195 | // alpha = o exp(-q/2) >= 1/255 <=> q <= 2 ln(255 o). Writes the bound on q | 195 | // 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 the | 196 | // 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. |
| 198 | GS_HD bool AlphaLimit(float opacity, float& limit) { | 198 | GS_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 | } |
| 204 | 204 | ||
| 205 | // Pad (pixels) on ellipse extents, against rounding. | 205 | // Pad (pixels) on ellipse extents, against rounding. |
| 206 | inline constexpr float kSpanPadPx = 0.01f; | 206 | inline constexpr float kSpanPadPx = 0.01f; |
| 207 | 207 | ||
| 208 | // Precomputed splat-level ellipse constants for fast band intersection tests | 208 | // 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. |
| 210 | struct EllipseBounds { | 210 | struct 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; |
| 217 | 217 | ||
| 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 | } |
| 234 | 234 | ||
| 235 | // Span [x0_px, x1_px] of integer pixel columns hit across integer pixel rows | 235 | // 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 right | 246 | // 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 | }; |
| 261 | 261 | ||
| 262 | // Visits every tile row `ty` with the half-open span [tx0, tx1) of tiles that | 262 | // 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). |
| 264 | template <Culling kCulling, typename Fn> | 264 | template <Culling kCulling, typename Fn> |
| 265 | GS_D void ForEachTileRow(const Splat& s, const TileGrid& grid, int width, | 265 | GS_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 | } |
| 297 | 297 | ||
| 298 | namespace warp { | 298 | namespace warp { |
| 299 | 299 | ||
| 300 | inline constexpr unsigned kFullMask = 0xffffffffu; | 300 | inline constexpr unsigned kFullMask = 0xffffffffu; |
| 301 | 301 | ||
| 302 | // Standard warp butterfly reduction of an array of `N` per-lane values: | 302 | // Reduce-scatter of an array of `N` <= 32 per-lane values over the warp. |
| 303 | // 5 steps of `__shfl_xor_sync` for each element (`5 * N` shuffles total), | ||
| 304 | // after which lane 0 (and every other lane) holds the warp sum of `v[0..N)`. | ||
| 303 | // | ||
| 304 | // A butterfly where each step halves the payload: at offset `o`, lanes with | ||
| 305 | // bit `o` clear keep the low half of the array and send the high half, the | ||
| 306 | // others do the opposite. After log2(32) steps every lane holds a single | ||
| 307 | // value, v[0], which is the warp total of element `ScatterIndex(N, lane)` | ||
| 308 | // (or junk if that is -1). For N = 9 this takes 12 shuffles instead of the | ||
| 309 | // 45 of nine independent butterflies, and the totals end in distinct lanes, | ||
| 310 | // so writing them out is one instruction. | ||
| 311 | |||
| 312 | namespace internal { | ||
| 305 | template <int N> | 313 | template <int N, int kOffset> |
| 306 | GS_D void ButterflyReduce(float (&v)[N]) { | 314 | GS_D void HalvingStep(float* v, int lane) { |
| 315 | constexpr int kHalf = (N + 1) / 2; | ||
| 316 | const bool upper = lane & kOffset; | ||
| 307 | #pragma unroll | 317 | #pragma unroll |
| 308 | for (int offset = 16; offset >= 1; offset >>= 1) { | ||
| 309 | #pragma unroll | ||
| 310 | for (int i = 0; i < N; ++i) { | 318 | for (int i = 0; i < kHalf; ++i) { |
| 319 | const float lo = v[i]; | ||
| 320 | const float hi = i + kHalf < N ? v[i + kHalf] : 0.0f; | ||
| 321 | const float send = upper ? lo : hi; | ||
| 322 | const float keep = upper ? hi : lo; | ||
| 311 | v[i] += __shfl_xor_sync(kFullMask, v[i], offset); | 323 | v[i] = keep + __shfl_xor_sync(kFullMask, send, kOffset); |
| 312 | } | 324 | } |
| 313 | } | 325 | } |
| 326 | |||
| 327 | template <int N, int kOffset> | ||
| 328 | GS_D void ReduceScatterImpl(float* v, int lane) { | ||
| 329 | if constexpr (kOffset > 0) { | ||
| 330 | HalvingStep<N, kOffset>(v, lane); | ||
| 331 | ReduceScatterImpl<(N + 1) / 2, kOffset / 2>(v, lane); | ||
| 332 | } | ||
| 333 | } | ||
| 334 | } // namespace internal | ||
| 335 | |||
| 336 | template <int N> | ||
| 337 | GS_D void ReduceScatter(float (&v)[N], int lane) { | ||
| 338 | static_assert(N >= 1 && N <= 32); | ||
| 339 | internal::ReduceScatterImpl<N, 16>(v, lane); | ||
| 340 | } | ||
| 341 | |||
| 342 | template <int N> | ||
| 343 | GS_D void ReduceScatter16(float (&v)[N], int lane) { | ||
| 344 | static_assert(N >= 1 && N <= 16); | ||
| 345 | internal::ReduceScatterImpl<N, 8>(v, lane); | ||
| 346 | } | ||
| 347 | |||
| 348 | // Element whose warp total lane `lane` holds after `ReduceScatter<N>`, or -1. | ||
| 349 | GS_HD constexpr int ScatterIndex(int n, int lane) { | ||
| 350 | // Payload sizes and halves per step, offset 16 first. | ||
| 351 | int sizes[5] = {}; | ||
| 352 | int size = n; | ||
| 353 | for (int s = 0; s < 5; ++s) { | ||
| 354 | sizes[s] = size; | ||
| 355 | size = (size + 1) / 2; | ||
| 356 | } | ||
| 357 | int index = 0; // Slot in the final (size 1) array. | ||
| 358 | for (int s = 4; s >= 0; --s) { | ||
| 359 | const int half = (sizes[s] + 1) / 2; | ||
| 360 | if (lane & (16 >> s)) index += half; | ||
| 361 | if (index >= sizes[s]) return -1; | ||
| 362 | } | ||
| 363 | return index; | ||
| 364 | } | ||
| 365 | |||
| 366 | // Element whose 16-lane half-warp total lane `lane` holds after | ||
| 367 | // `ReduceScatter16<N>`, or -1. | ||
| 368 | GS_HD constexpr int ScatterIndex16(int n, int lane) { | ||
| 369 | const int half_lane = lane & 15; | ||
| 370 | int sizes[4] = {}; | ||
| 371 | int size = n; | ||
| 372 | for (int s = 0; s < 4; ++s) { | ||
| 373 | sizes[s] = size; | ||
| 374 | size = (size + 1) / 2; | ||
| 375 | } | ||
| 376 | int index = 0; | ||
| 377 | for (int s = 3; s >= 0; --s) { | ||
| 378 | const int half = (sizes[s] + 1) / 2; | ||
| 379 | if (half_lane & (8 >> s)) index += half; | ||
| 380 | if (index >= sizes[s]) return -1; | ||
| 381 | } | ||
| 382 | return index; | ||
| 314 | } | 383 | } |
| 315 | 384 | ||
| 316 | } // namespace warp | 385 | } // namespace warp |
| 317 | 386 | ||
Expand 467 unchanged lines (lines 387–853) · in WarpMask | |||
| 318 | inline constexpr int kShCoeffs = 16; // Degree 3. | 387 | inline constexpr int kShCoeffs = 16; // Degree 3. |
| 319 | inline constexpr float kNearPlane = 0.2f; | 388 | inline constexpr float kNearPlane = 0.2f; |
| 320 | inline constexpr float kLowPass = 0.3f; | 389 | inline constexpr float kLowPass = 0.3f; |
| 321 | inline constexpr float kFovClamp = 1.3f; | 390 | inline constexpr float kFovClamp = 1.3f; |
| 322 | 391 | ||
| 323 | // Pointers to the trainable parameters, all activated, row-major. | 392 | // Pointers to the trainable parameters, all activated, row-major. |
| 324 | struct GaussianParams { | 393 | struct GaussianParams { |
| 325 | const float* means; // (N, 3) | 394 | const float* means; // (N, 3) |
| 326 | const float* scales; // (N, 3) | 395 | const float* scales; // (N, 3) |
| 327 | const float* quats; // (N, 4) as (w, x, y, z), not normalized. | 396 | const float* quats; // (N, 4) as (w, x, y, z), not normalized. |
| 328 | const float* opacities; // (N,) | 397 | const float* opacities; // (N,) |
| 329 | const void* sh; // (N, 16, 3) of `float` or `ShBf16` (see `ShIo`). | 398 | const void* sh; // (N, 16, 3) of `float` or `ShBf16` (see `ShIo`). |
| 330 | int n; | 399 | int n; |
| 331 | }; | 400 | }; |
| 332 | 401 | ||
| 333 | // Gradients w.r.t. `GaussianParams`, same layout and SH storage type. | 402 | // Gradients w.r.t. `GaussianParams`, same layout and SH storage type. |
| 334 | struct GaussianGrads { | 403 | struct GaussianGrads { |
| 335 | float* means; | 404 | float* means; |
| 336 | float* scales; | 405 | float* scales; |
| 337 | float* quats; | 406 | float* quats; |
| 338 | float* opacities; | 407 | float* opacities; |
| 339 | void* sh; | 408 | void* sh; |
| 340 | }; | 409 | }; |
| 341 | 410 | ||
| 342 | // Screen-space quantities needed by rasterization. `conic` is the inverse 2D | 411 | // Screen-space quantities needed by rasterization. `conic` is the inverse 2D |
| 343 | // covariance (a, b, c) with power = -0.5 (a dx^2 + c dy^2) - b dx dy. | 412 | // covariance (a, b, c) with power = -0.5 (a dx^2 + c dy^2) - b dx dy. |
| 344 | struct Projection { | 413 | struct Projection { |
| 345 | float2 xy; | 414 | float2 xy; |
| 346 | float depth; | 415 | float depth; |
| 347 | float3 conic; | 416 | float3 conic; |
| 348 | float opacity; | 417 | float opacity; |
| 349 | float limit; | 418 | float limit; |
| 350 | float3 rgb; | 419 | float3 rgb; |
| 351 | int radius; // 0 means culled. | 420 | int radius; // 0 means culled. |
| 352 | }; | 421 | }; |
| 353 | 422 | ||
| 354 | // ---------------------------------------------------------------------------- | 423 | // ---------------------------------------------------------------------------- |
| 355 | // SH coefficient storage. A row holds the 16 x 3 coefficients of one Gaussian | 424 | // SH coefficient storage. A row holds the 16 x 3 coefficients of one Gaussian |
| 356 | // and may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32. | 425 | // and may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32. |
| 357 | 426 | ||
| 358 | using ShBf16 = uint16_t; // bfloat16 bits. | 427 | using ShBf16 = uint16_t; // bfloat16 bits. |
| 359 | 428 | ||
| 360 | template <typename T> | 429 | template <typename T> |
| 361 | struct ShIo; | 430 | struct ShIo; |
| 362 | 431 | ||
| 363 | template <> | 432 | template <> |
| 364 | struct ShIo<float> { | 433 | struct ShIo<float> { |
| 365 | GS_D static void LoadRow(const float* row, float3 coeffs[kShCoeffs]) { | 434 | GS_D static void LoadRow(const float* row, float3 coeffs[kShCoeffs]) { |
| 366 | const float4* p = reinterpret_cast<const float4*>(row); | 435 | const float4* p = reinterpret_cast<const float4*>(row); |
| 367 | float raw[3 * kShCoeffs]; | 436 | float raw[3 * kShCoeffs]; |
| 368 | #pragma unroll | 437 | #pragma unroll |
| 369 | for (int k = 0; k < 12; ++k) { | 438 | for (int k = 0; k < 12; ++k) { |
| 370 | const float4 q = __ldg(p + k); | 439 | const float4 q = __ldg(p + k); |
| 371 | raw[4 * k] = q.x; | 440 | raw[4 * k] = q.x; |
| 372 | raw[4 * k + 1] = q.y; | 441 | raw[4 * k + 1] = q.y; |
| 373 | raw[4 * k + 2] = q.z; | 442 | raw[4 * k + 2] = q.z; |
| 374 | raw[4 * k + 3] = q.w; | 443 | raw[4 * k + 3] = q.w; |
| 375 | } | 444 | } |
| 376 | #pragma unroll | 445 | #pragma unroll |
| 377 | for (int c = 0; c < kShCoeffs; ++c) { | 446 | for (int c = 0; c < kShCoeffs; ++c) { |
| 378 | coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]}; | 447 | coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]}; |
| 379 | } | 448 | } |
| 380 | } | 449 | } |
| 381 | GS_D static void StoreRow(float* row, const float3 dsh[kShCoeffs]) { | 450 | GS_D static void StoreRow(float* row, const float3 dsh[kShCoeffs]) { |
| 382 | float raw[3 * kShCoeffs]; | 451 | float raw[3 * kShCoeffs]; |
| 383 | #pragma unroll | 452 | #pragma unroll |
| 384 | for (int c = 0; c < kShCoeffs; ++c) { | 453 | for (int c = 0; c < kShCoeffs; ++c) { |
| 385 | raw[3 * c] = dsh[c].x; | 454 | raw[3 * c] = dsh[c].x; |
| 386 | raw[3 * c + 1] = dsh[c].y; | 455 | raw[3 * c + 1] = dsh[c].y; |
| 387 | raw[3 * c + 2] = dsh[c].z; | 456 | raw[3 * c + 2] = dsh[c].z; |
| 388 | } | 457 | } |
| 389 | float4* p = reinterpret_cast<float4*>(row); | 458 | float4* p = reinterpret_cast<float4*>(row); |
| 390 | #pragma unroll | 459 | #pragma unroll |
| 391 | for (int k = 0; k < 12; ++k) { | 460 | for (int k = 0; k < 12; ++k) { |
| 392 | p[k] = {raw[4 * k], raw[4 * k + 1], raw[4 * k + 2], raw[4 * k + 3]}; | 461 | p[k] = {raw[4 * k], raw[4 * k + 1], raw[4 * k + 2], raw[4 * k + 3]}; |
| 393 | } | 462 | } |
| 394 | } | 463 | } |
| 395 | }; | 464 | }; |
| 396 | 465 | ||
| 397 | template <> | 466 | template <> |
| 398 | struct ShIo<ShBf16> { | 467 | struct ShIo<ShBf16> { |
| 399 | GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); } | 468 | GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); } |
| 400 | GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); } | 469 | GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); } |
| 401 | // Round to nearest even (inputs are finite). | 470 | // Round to nearest even (inputs are finite). |
| 402 | GS_D static uint32_t Round(float f) { | 471 | GS_D static uint32_t Round(float f) { |
| 403 | const uint32_t u = __float_as_uint(f); | 472 | const uint32_t u = __float_as_uint(f); |
| 404 | return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16; | 473 | return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16; |
| 405 | } | 474 | } |
| 406 | GS_D static uint32_t Pack(float lo, float hi) { | 475 | GS_D static uint32_t Pack(float lo, float hi) { |
| 407 | return Round(lo) | (Round(hi) << 16); | 476 | return Round(lo) | (Round(hi) << 16); |
| 408 | } | 477 | } |
| 409 | GS_D static void LoadRow(const ShBf16* row, float3 coeffs[kShCoeffs]) { | 478 | GS_D static void LoadRow(const ShBf16* row, float3 coeffs[kShCoeffs]) { |
| 410 | const uint2* p = reinterpret_cast<const uint2*>(row); | 479 | const uint2* p = reinterpret_cast<const uint2*>(row); |
| 411 | float raw[3 * kShCoeffs]; | 480 | float raw[3 * kShCoeffs]; |
| 412 | #pragma unroll | 481 | #pragma unroll |
| 413 | for (int k = 0; k < 12; ++k) { | 482 | for (int k = 0; k < 12; ++k) { |
| 414 | const uint2 w = __ldg(p + k); | 483 | const uint2 w = __ldg(p + k); |
| 415 | raw[4 * k] = Lo(w.x); | 484 | raw[4 * k] = Lo(w.x); |
| 416 | raw[4 * k + 1] = Hi(w.x); | 485 | raw[4 * k + 1] = Hi(w.x); |
| 417 | raw[4 * k + 2] = Lo(w.y); | 486 | raw[4 * k + 2] = Lo(w.y); |
| 418 | raw[4 * k + 3] = Hi(w.y); | 487 | raw[4 * k + 3] = Hi(w.y); |
| 419 | } | 488 | } |
| 420 | #pragma unroll | 489 | #pragma unroll |
| 421 | for (int c = 0; c < kShCoeffs; ++c) { | 490 | for (int c = 0; c < kShCoeffs; ++c) { |
| 422 | coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]}; | 491 | coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]}; |
| 423 | } | 492 | } |
| 424 | } | 493 | } |
| 425 | GS_D static void StoreRow(ShBf16* row, const float3 dsh[kShCoeffs]) { | 494 | GS_D static void StoreRow(ShBf16* row, const float3 dsh[kShCoeffs]) { |
| 426 | float raw[3 * kShCoeffs]; | 495 | float raw[3 * kShCoeffs]; |
| 427 | #pragma unroll | 496 | #pragma unroll |
| 428 | for (int c = 0; c < kShCoeffs; ++c) { | 497 | for (int c = 0; c < kShCoeffs; ++c) { |
| 429 | raw[3 * c] = dsh[c].x; | 498 | raw[3 * c] = dsh[c].x; |
| 430 | raw[3 * c + 1] = dsh[c].y; | 499 | raw[3 * c + 1] = dsh[c].y; |
| 431 | raw[3 * c + 2] = dsh[c].z; | 500 | raw[3 * c + 2] = dsh[c].z; |
| 432 | } | 501 | } |
| 433 | uint2* p = reinterpret_cast<uint2*>(row); | 502 | uint2* p = reinterpret_cast<uint2*>(row); |
| 434 | #pragma unroll | 503 | #pragma unroll |
| 435 | for (int k = 0; k < 12; ++k) { | 504 | for (int k = 0; k < 12; ++k) { |
| 436 | p[k] = {Pack(raw[4 * k], raw[4 * k + 1]), | 505 | p[k] = {Pack(raw[4 * k], raw[4 * k + 1]), |
| 437 | Pack(raw[4 * k + 2], raw[4 * k + 3])}; | 506 | Pack(raw[4 * k + 2], raw[4 * k + 3])}; |
| 438 | } | 507 | } |
| 439 | } | 508 | } |
| 440 | }; | 509 | }; |
| 441 | 510 | ||
| 442 | template <typename ShT> | 511 | template <typename ShT> |
| 443 | GS_D const ShT* ShRow(const void* sh, int i) { | 512 | GS_D const ShT* ShRow(const void* sh, int i) { |
| 444 | return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs; | 513 | return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs; |
| 445 | } | 514 | } |
| 446 | template <typename ShT> | 515 | template <typename ShT> |
| 447 | GS_D ShT* ShRow(void* sh, int i) { | 516 | GS_D ShT* ShRow(void* sh, int i) { |
| 448 | return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs; | 517 | return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs; |
| 449 | } | 518 | } |
| 450 | 519 | ||
| 451 | // ---------------------------------------------------------------------------- | 520 | // ---------------------------------------------------------------------------- |
| 452 | // Spherical harmonics (degree 3, Inria sign convention). | 521 | // Spherical harmonics (degree 3, Inria sign convention). |
| 453 | 522 | ||
| 454 | namespace sh { | 523 | namespace sh { |
| 455 | inline constexpr float kC0 = 0.28209479177387814f; | 524 | inline constexpr float kC0 = 0.28209479177387814f; |
| 456 | inline constexpr float kC1 = 0.4886025119029199f; | 525 | inline constexpr float kC1 = 0.4886025119029199f; |
| 457 | inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f, | 526 | inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f, |
| 458 | 0.31539156525252005f, -1.0925484305920792f, | 527 | 0.31539156525252005f, -1.0925484305920792f, |
| 459 | 0.5462742152960396f}; | 528 | 0.5462742152960396f}; |
| 460 | inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f, | 529 | inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f, |
| 461 | -0.4570457994644658f, 0.3731763325901154f, | 530 | -0.4570457994644658f, 0.3731763325901154f, |
| 462 | -0.4570457994644658f, 1.445305721320277f, | 531 | -0.4570457994644658f, 1.445305721320277f, |
| 463 | -0.5900435899266435f}; | 532 | -0.5900435899266435f}; |
| 464 | 533 | ||
| 465 | // Basis values for a unit direction. | 534 | // Basis values for a unit direction. |
| 466 | GS_D void Basis(float3 d, float b[kShCoeffs]) { | 535 | GS_D void Basis(float3 d, float b[kShCoeffs]) { |
| 467 | const float x = d.x, y = d.y, z = d.z; | 536 | const float x = d.x, y = d.y, z = d.z; |
| 468 | const float xx = x * x, yy = y * y, zz = z * z; | 537 | const float xx = x * x, yy = y * y, zz = z * z; |
| 469 | b[0] = kC0; | 538 | b[0] = kC0; |
| 470 | b[1] = -kC1 * y; | 539 | b[1] = -kC1 * y; |
| 471 | b[2] = kC1 * z; | 540 | b[2] = kC1 * z; |
| 472 | b[3] = -kC1 * x; | 541 | b[3] = -kC1 * x; |
| 473 | b[4] = kC2[0] * x * y; | 542 | b[4] = kC2[0] * x * y; |
| 474 | b[5] = kC2[1] * y * z; | 543 | b[5] = kC2[1] * y * z; |
| 475 | b[6] = kC2[2] * (2.0f * zz - xx - yy); | 544 | b[6] = kC2[2] * (2.0f * zz - xx - yy); |
| 476 | b[7] = kC2[3] * x * z; | 545 | b[7] = kC2[3] * x * z; |
| 477 | b[8] = kC2[4] * (xx - yy); | 546 | b[8] = kC2[4] * (xx - yy); |
| 478 | b[9] = kC3[0] * y * (3.0f * xx - yy); | 547 | b[9] = kC3[0] * y * (3.0f * xx - yy); |
| 479 | b[10] = kC3[1] * x * y * z; | 548 | b[10] = kC3[1] * x * y * z; |
| 480 | b[11] = kC3[2] * y * (4.0f * zz - xx - yy); | 549 | b[11] = kC3[2] * y * (4.0f * zz - xx - yy); |
| 481 | b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy); | 550 | b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy); |
| 482 | b[13] = kC3[4] * x * (4.0f * zz - xx - yy); | 551 | b[13] = kC3[4] * x * (4.0f * zz - xx - yy); |
| 483 | b[14] = kC3[5] * z * (xx - yy); | 552 | b[14] = kC3[5] * z * (xx - yy); |
| 484 | b[15] = kC3[6] * x * (xx - 3.0f * yy); | 553 | b[15] = kC3[6] * x * (xx - 3.0f * yy); |
| 485 | } | 554 | } |
| 486 | 555 | ||
| 487 | // Partial derivatives of every basis function w.r.t. (x, y, z) of the unit | 556 | // Partial derivatives of every basis function w.r.t. (x, y, z) of the unit |
| 488 | // direction (treated as free coordinates). | 557 | // direction (treated as free coordinates). |
| 489 | GS_D void BasisGrad(float3 d, float3 g[kShCoeffs]) { | 558 | GS_D void BasisGrad(float3 d, float3 g[kShCoeffs]) { |
| 490 | const float x = d.x, y = d.y, z = d.z; | 559 | const float x = d.x, y = d.y, z = d.z; |
| 491 | const float xx = x * x, yy = y * y, zz = z * z; | 560 | const float xx = x * x, yy = y * y, zz = z * z; |
| 492 | g[0] = {0.0f, 0.0f, 0.0f}; | 561 | g[0] = {0.0f, 0.0f, 0.0f}; |
| 493 | g[1] = {0.0f, -kC1, 0.0f}; | 562 | g[1] = {0.0f, -kC1, 0.0f}; |
| 494 | g[2] = {0.0f, 0.0f, kC1}; | 563 | g[2] = {0.0f, 0.0f, kC1}; |
| 495 | g[3] = {-kC1, 0.0f, 0.0f}; | 564 | g[3] = {-kC1, 0.0f, 0.0f}; |
| 496 | g[4] = kC2[0] * float3{y, x, 0.0f}; | 565 | g[4] = kC2[0] * float3{y, x, 0.0f}; |
| 497 | g[5] = kC2[1] * float3{0.0f, z, y}; | 566 | g[5] = kC2[1] * float3{0.0f, z, y}; |
| 498 | g[6] = kC2[2] * float3{-2.0f * x, -2.0f * y, 4.0f * z}; | 567 | g[6] = kC2[2] * float3{-2.0f * x, -2.0f * y, 4.0f * z}; |
| 499 | g[7] = kC2[3] * float3{z, 0.0f, x}; | 568 | g[7] = kC2[3] * float3{z, 0.0f, x}; |
| 500 | g[8] = kC2[4] * float3{2.0f * x, -2.0f * y, 0.0f}; | 569 | g[8] = kC2[4] * float3{2.0f * x, -2.0f * y, 0.0f}; |
| 501 | g[9] = kC3[0] * float3{6.0f * x * y, 3.0f * (xx - yy), 0.0f}; | 570 | g[9] = kC3[0] * float3{6.0f * x * y, 3.0f * (xx - yy), 0.0f}; |
| 502 | g[10] = kC3[1] * float3{y * z, x * z, x * y}; | 571 | g[10] = kC3[1] * float3{y * z, x * z, x * y}; |
| 503 | g[11] = | 572 | g[11] = |
| 504 | kC3[2] * float3{-2.0f * x * y, 4.0f * zz - xx - 3.0f * yy, 8.0f * y * z}; | 573 | kC3[2] * float3{-2.0f * x * y, 4.0f * zz - xx - 3.0f * yy, 8.0f * y * z}; |
| 505 | g[12] = kC3[3] * float3{-6.0f * x * z, -6.0f * y * z, | 574 | g[12] = kC3[3] * float3{-6.0f * x * z, -6.0f * y * z, |
| 506 | 6.0f * zz - 3.0f * xx - 3.0f * yy}; | 575 | 6.0f * zz - 3.0f * xx - 3.0f * yy}; |
| 507 | g[13] = | 576 | g[13] = |
| 508 | kC3[4] * float3{4.0f * zz - 3.0f * xx - yy, -2.0f * x * y, 8.0f * x * z}; | 577 | kC3[4] * float3{4.0f * zz - 3.0f * xx - yy, -2.0f * x * y, 8.0f * x * z}; |
| 509 | g[14] = kC3[5] * float3{2.0f * x * z, -2.0f * y * z, xx - yy}; | 578 | g[14] = kC3[5] * float3{2.0f * x * z, -2.0f * y * z, xx - yy}; |
| 510 | g[15] = kC3[6] * float3{3.0f * (xx - yy), -6.0f * x * y, 0.0f}; | 579 | g[15] = kC3[6] * float3{3.0f * (xx - yy), -6.0f * x * y, 0.0f}; |
| 511 | } | 580 | } |
| 512 | 581 | ||
| 513 | // Pre-clamp color 0.5 + sum_k b_k sh_k, loading the full 16-basis SH row. | 582 | // Pre-clamp color 0.5 + sum_k b_k sh_k, loading the full 16-basis SH row. |
| 514 | template <typename ShT> | 583 | template <typename ShT> |
| 515 | GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) { | 584 | GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) { |
| 516 | float3 coeffs[kShCoeffs]; | 585 | float3 coeffs[kShCoeffs]; |
| 517 | ShIo<ShT>::LoadRow(sh_row, coeffs); | 586 | ShIo<ShT>::LoadRow(sh_row, coeffs); |
| 518 | float3 c = {0.5f, 0.5f, 0.5f}; | 587 | float3 c = {0.5f, 0.5f, 0.5f}; |
| 519 | #pragma unroll | 588 | #pragma unroll |
| 520 | for (int k = 0; k < kShCoeffs; ++k) { | 589 | for (int k = 0; k < kShCoeffs; ++k) { |
| 521 | c += b[k] * coeffs[k]; | 590 | c += b[k] * coeffs[k]; |
| 522 | } | 591 | } |
| 523 | return c; | 592 | return c; |
| 524 | } | 593 | } |
| 525 | } // namespace sh | 594 | } // namespace sh |
| 526 | 595 | ||
| 527 | // ---------------------------------------------------------------------------- | 596 | // ---------------------------------------------------------------------------- |
| 528 | // Covariance helpers. | 597 | // Covariance helpers. |
| 529 | 598 | ||
| 530 | // Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z). | 599 | // Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z). |
| 531 | GS_D Mat3 QuatToRotation(float4 q) { | 600 | GS_D Mat3 QuatToRotation(float4 q) { |
| 532 | const float w = q.x, x = q.y, y = q.z, z = q.w; | 601 | const float w = q.x, x = q.y, y = q.z, z = q.w; |
| 533 | Mat3 r; | 602 | Mat3 r; |
| 534 | r.m[0][0] = 1.0f - 2.0f * (y * y + z * z); | 603 | r.m[0][0] = 1.0f - 2.0f * (y * y + z * z); |
| 535 | r.m[0][1] = 2.0f * (x * y - w * z); | 604 | r.m[0][1] = 2.0f * (x * y - w * z); |
| 536 | r.m[0][2] = 2.0f * (x * z + w * y); | 605 | r.m[0][2] = 2.0f * (x * z + w * y); |
| 537 | r.m[1][0] = 2.0f * (x * y + w * z); | 606 | r.m[1][0] = 2.0f * (x * y + w * z); |
| 538 | r.m[1][1] = 1.0f - 2.0f * (x * x + z * z); | 607 | r.m[1][1] = 1.0f - 2.0f * (x * x + z * z); |
| 539 | r.m[1][2] = 2.0f * (y * z - w * x); | 608 | r.m[1][2] = 2.0f * (y * z - w * x); |
| 540 | r.m[2][0] = 2.0f * (x * z - w * y); | 609 | r.m[2][0] = 2.0f * (x * z - w * y); |
| 541 | r.m[2][1] = 2.0f * (y * z + w * x); | 610 | r.m[2][1] = 2.0f * (y * z + w * x); |
| 542 | r.m[2][2] = 1.0f - 2.0f * (x * x + y * y); | 611 | r.m[2][2] = 1.0f - 2.0f * (x * x + y * y); |
| 543 | return r; | 612 | return r; |
| 544 | } | 613 | } |
| 545 | 614 | ||
| 546 | // Symmetric 3x3 matrix. | 615 | // Symmetric 3x3 matrix. |
| 547 | struct Sym3 { | 616 | struct Sym3 { |
| 548 | float xx, xy, xz, yy, yz, zz; | 617 | float xx, xy, xz, yy, yz, zz; |
| 549 | GS_D float3 Mul(float3 v) const { | 618 | GS_D float3 Mul(float3 v) const { |
| 550 | return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z, | 619 | return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z, |
| 551 | xz * v.x + yz * v.y + zz * v.z}; | 620 | xz * v.x + yz * v.y + zz * v.z}; |
| 552 | } | 621 | } |
| 553 | }; | 622 | }; |
| 554 | 623 | ||
| 555 | // Sigma = A A^T with A = R diag(s). | 624 | // Sigma = A A^T with A = R diag(s). |
| 556 | GS_D Sym3 Covariance3D(const Mat3& r, float3 s) { | 625 | GS_D Sym3 Covariance3D(const Mat3& r, float3 s) { |
| 557 | Mat3 a; | 626 | Mat3 a; |
| 558 | const float sv[3] = {s.x, s.y, s.z}; | 627 | const float sv[3] = {s.x, s.y, s.z}; |
| 559 | #pragma unroll | 628 | #pragma unroll |
| 560 | for (int i = 0; i < 3; ++i) | 629 | for (int i = 0; i < 3; ++i) |
| 561 | #pragma unroll | 630 | #pragma unroll |
| 562 | for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j]; | 631 | for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j]; |
| 563 | return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)), | 632 | return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)), |
| 564 | dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)), | 633 | dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)), |
| 565 | dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))}; | 634 | dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))}; |
| 566 | } | 635 | } |
| 567 | 636 | ||
| 568 | // Intermediate values of the projection shared by forward and VJP. | 637 | // Intermediate values of the projection shared by forward and VJP. |
| 569 | struct ProjectionState { | 638 | struct ProjectionState { |
| 570 | float3 t; // View-space mean. | 639 | float3 t; // View-space mean. |
| 571 | float tx_c, ty_c; // Clamped x, y used by the Jacobian. | 640 | float tx_c, ty_c; // Clamped x, y used by the Jacobian. |
| 572 | bool x_clamped, y_clamped; | 641 | bool x_clamped, y_clamped; |
| 573 | float3 m0, m1; // Rows of M = J W (2x3). | 642 | float3 m0, m1; // Rows of M = J W (2x3). |
| 574 | Sym3 sigma; // World-space 3D covariance. | 643 | Sym3 sigma; // World-space 3D covariance. |
| 575 | float a, b, c; // Dilated 2D covariance. | 644 | float a, b, c; // Dilated 2D covariance. |
| 576 | }; | 645 | }; |
| 577 | 646 | ||
| 578 | // Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane). | 647 | // Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane). |
| 579 | GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale, | 648 | GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale, |
| 580 | float4 quat, ProjectionState& st) { | 649 | float4 quat, ProjectionState& st) { |
| 581 | st.t = t; | 650 | st.t = t; |
| 582 | const float tz = t.z; | 651 | const float tz = t.z; |
| 583 | const float limx = kFovClamp * cam.TanHalfFovX(); | 652 | const float limx = kFovClamp * cam.TanHalfFovX(); |
| 584 | const float limy = kFovClamp * cam.TanHalfFovY(); | 653 | const float limy = kFovClamp * cam.TanHalfFovY(); |
| 585 | const float txtz = t.x / tz, tytz = t.y / tz; | 654 | const float txtz = t.x / tz, tytz = t.y / tz; |
| 586 | st.x_clamped = txtz < -limx || txtz > limx; | 655 | st.x_clamped = txtz < -limx || txtz > limx; |
| 587 | st.y_clamped = tytz < -limy || tytz > limy; | 656 | st.y_clamped = tytz < -limy || tytz > limy; |
| 588 | st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz; | 657 | st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz; |
| 589 | st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz; | 658 | st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz; |
| 590 | const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz); | 659 | const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz); |
| 591 | const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz); | 660 | const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz); |
| 592 | const Mat3& w = cam.rotation; | 661 | const Mat3& w = cam.rotation; |
| 593 | st.m0 = j00 * w.row(0) + j02 * w.row(2); | 662 | st.m0 = j00 * w.row(0) + j02 * w.row(2); |
| 594 | st.m1 = j11 * w.row(1) + j12 * w.row(2); | 663 | st.m1 = j11 * w.row(1) + j12 * w.row(2); |
| 595 | st.sigma = Covariance3D(QuatToRotation(quat), scale); | 664 | st.sigma = Covariance3D(QuatToRotation(quat), scale); |
| 596 | const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1); | 665 | const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1); |
| 597 | st.a = dot(st.m0, s0) + kLowPass; | 666 | st.a = dot(st.m0, s0) + kLowPass; |
| 598 | st.b = dot(st.m0, s1); | 667 | st.b = dot(st.m0, s1); |
| 599 | st.c = dot(st.m1, s1) + kLowPass; | 668 | st.c = dot(st.m1, s1) + kLowPass; |
| 600 | } | 669 | } |
| 601 | 670 | ||
| 602 | // Computes everything up to the 2D covariance. Returns false if culled by the | 671 | // Computes everything up to the 2D covariance. Returns false if culled by the |
| 603 | // near plane. | 672 | // near plane. |
| 604 | GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale, | 673 | GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale, |
| 605 | float4 quat, ProjectionState& st) { | 674 | float4 quat, ProjectionState& st) { |
| 606 | const float3 t = cam.ToView(mean); | 675 | const float3 t = cam.ToView(mean); |
| 607 | if (t.z <= kNearPlane) return false; | 676 | if (t.z <= kNearPlane) return false; |
| 608 | ProjectCovarianceFromView(cam, t, scale, quat, st); | 677 | ProjectCovarianceFromView(cam, t, scale, quat, st); |
| 609 | return true; | 678 | return true; |
| 610 | } | 679 | } |
| 611 | 680 | ||
| 612 | // Integer bounding radius (pixels) of the 2D covariance, as in the reference. | 681 | // Integer bounding radius (pixels) of the 2D covariance, as in the reference. |
| 613 | GS_D int BoundingRadius(float a, float b, float c) { | 682 | GS_D int BoundingRadius(float a, float b, float c) { |
| 614 | const float det = a * c - b * b; | 683 | const float det = a * c - b * b; |
| 615 | const float mid = 0.5f * (a + c); | 684 | const float mid = 0.5f * (a + c); |
| 616 | const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det)); | 685 | const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det)); |
| 617 | return static_cast<int>(ceilf(3.0f * sqrtf(lambda))); | 686 | return static_cast<int>(ceilf(3.0f * sqrtf(lambda))); |
| 618 | } | 687 | } |
| 619 | 688 | ||
| 620 | GS_D float2 ProjectMean(const Camera& cam, float3 t) { | 689 | GS_D float2 ProjectMean(const Camera& cam, float3 t) { |
| 621 | return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy}; | 690 | return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy}; |
| 622 | } | 691 | } |
| 623 | 692 | ||
| 624 | GS_D float3 LoadMean(const GaussianParams& p, int i) { | 693 | GS_D float3 LoadMean(const GaussianParams& p, int i) { |
| 625 | return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]}; | 694 | return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]}; |
| 626 | } | 695 | } |
| 627 | 696 | ||
| 628 | // Unit direction from the camera center to `mean`, and 1 / distance. | 697 | // Unit direction from the camera center to `mean`, and 1 / distance. |
| 629 | GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) { | 698 | GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) { |
| 630 | const float3 v = mean - cam.position; | 699 | const float3 v = mean - cam.position; |
| 631 | inv_len = rsqrtf(dot(v, v)); | 700 | inv_len = rsqrtf(dot(v, v)); |
| 632 | return inv_len * v; | 701 | return inv_len * v; |
| 633 | } | 702 | } |
| 634 | 703 | ||
| 635 | // Gradient of the loss w.r.t. one splat's screen-space quantities. | 704 | // Gradient of the loss w.r.t. one splat's screen-space quantities. |
| 636 | struct ProjectionGrad { | 705 | struct ProjectionGrad { |
| 637 | float2 xy; | 706 | float2 xy; |
| 638 | float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`. | 707 | float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`. |
| 639 | float opacity; | 708 | float opacity; |
| 640 | float3 rgb; | 709 | float3 rgb; |
| 641 | }; | 710 | }; |
| 642 | 711 | ||
| 643 | // Image geometry passed by value to every kernel. | 712 | // Image geometry passed by value to every kernel. |
| 644 | struct Frame { | 713 | struct Frame { |
| 645 | const float* camera; // Device pointer to the packed camera. | 714 | const float* camera; // Device pointer to the packed camera. |
| 646 | int width, height; | 715 | int width, height; |
| 647 | TileGrid grid; | 716 | TileGrid grid; |
| 648 | GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); } | 717 | GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); } |
| 649 | }; | 718 | }; |
| 650 | 719 | ||
| 651 | // --------------------------------------------------------------------------- | 720 | // --------------------------------------------------------------------------- |
| 652 | // Per-Gaussian kernels run one thread per Gaussian. | 721 | // Per-Gaussian kernels run one thread per Gaussian. |
| 653 | 722 | ||
| 654 | inline constexpr int kGaussianThreads = 128; | 723 | inline constexpr int kGaussianThreads = 128; |
| 655 | 724 | ||
| 656 | inline int GaussianBlocks(int64_t n) { | 725 | inline int GaussianBlocks(int64_t n) { |
| 657 | return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads); | 726 | return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads); |
| 658 | } | 727 | } |
| 659 | 728 | ||
| 660 | // Base-2 exponent scale: power2 = kPower2Scale * q = (-log2(e)/2) * q. | 729 | // Base-2 exponent scale: power2 = kPower2Scale * q = (-log2(e)/2) * q. |
| 661 | inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2 | 730 | inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2 |
| 662 | inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale; | 731 | inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale; |
| 663 | 732 | ||
| 664 | GS_HD float3 Power2Conic(float a, float b, float c) { | 733 | GS_HD float3 Power2Conic(float a, float b, float c) { |
| 665 | return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c}; | 734 | return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c}; |
| 666 | } | 735 | } |
| 667 | 736 | ||
| 668 | GS_D float Exp2Approx(float x) { | 737 | GS_D float Exp2Approx(float x) { |
| 669 | float y; | 738 | float y; |
| 670 | asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x)); | 739 | asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x)); |
| 671 | return y; | 740 | return y; |
| 672 | } | 741 | } |
| 673 | 742 | ||
| 674 | // --------------------------------------------------------------------------- | 743 | // --------------------------------------------------------------------------- |
| 675 | // Rasterization. One block per 16x16 tile, one pixel per thread, arranged into | 744 | // Rasterization. One block per 16x16 tile, one pixel per thread, arranged into |
| 676 | // eight 8x4 sub-tile warps (`RasterLayout<1>`). Splats are staged through | 745 | // eight 8x4 sub-tile warps (`RasterLayout<1>`). Splats are staged through |
| 677 | // shared memory in batches of 256 with an 8-bit per-warp sub-tile mask. | 746 | // shared memory in batches of 256 with an 8-bit per-warp sub-tile mask. |
| 678 | 747 | ||
| 679 | template <int kRows> | 748 | template <int kRows> |
| 680 | struct RasterLayout { | 749 | struct RasterLayout { |
| 681 | static_assert(kRows == 1 || kRows == 2 || kRows == 4); | 750 | static_assert(kRows == 1 || kRows == 2 || kRows == 4); |
| 682 | static constexpr int kThreads = kTilePixels / kRows; | 751 | static constexpr int kThreads = kTilePixels / kRows; |
| 683 | static constexpr int kWarps = kThreads / 32; | 752 | static constexpr int kWarps = kThreads / 32; |
| 684 | static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8; | 753 | static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8; |
| 685 | static constexpr int kWarpRows = (32 / kWarpWidth) * kRows; | 754 | static constexpr int kWarpRows = (32 / kWarpWidth) * kRows; |
| 686 | static constexpr int kWarpsX = kTileSize / kWarpWidth; | 755 | static constexpr int kWarpsX = kTileSize / kWarpWidth; |
| 687 | static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps); | 756 | static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps); |
| 688 | 757 | ||
| 689 | GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; } | 758 | GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; } |
| 690 | GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; } | 759 | GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; } |
| 691 | }; | 760 | }; |
| 692 | 761 | ||
| 693 | struct RasterPixels { | 762 | struct RasterPixels { |
| 694 | int x, y0; | 763 | int x, y0; |
| 695 | template <int kRows> | 764 | template <int kRows> |
| 696 | GS_D static RasterPixels For(int tid) { | 765 | GS_D static RasterPixels For(int tid) { |
| 697 | using L = RasterLayout<kRows>; | 766 | using L = RasterLayout<kRows>; |
| 698 | const int warp = tid / 32, lane = tid % 32; | 767 | const int warp = tid / 32, lane = tid % 32; |
| 699 | return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) + | 768 | return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) + |
| 700 | lane % L::kWarpWidth, | 769 | lane % L::kWarpWidth, |
| 701 | static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) + | 770 | static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) + |
| 702 | (lane / L::kWarpWidth) * kRows}; | 771 | (lane / L::kWarpWidth) * kRows}; |
| 703 | } | 772 | } |
| 704 | }; | 773 | }; |
| 705 | 774 | ||
| 706 | // Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the | 775 | // Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the |
| 707 | // pixel region of warp w of this block's tile. | 776 | // pixel region of warp w of this block's tile. |
| 708 | template <int kRows> | 777 | template <int kRows> |
| 709 | GS_D uint32_t WarpMask(float4 p0, float4 p1) { | 778 | GS_D uint32_t WarpMask(float4 p0, float4 p1) { |
| 710 | using L = RasterLayout<kRows>; | 779 | using L = RasterLayout<kRows>; |
| 711 | constexpr int kWarpsY = L::kWarps / L::kWarpsX; | 780 | constexpr int kWarpsY = L::kWarps / L::kWarpsX; |
| 712 | float limit = p1.z; | 781 | float limit = p1.z; |
| 713 | if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0; | 782 | if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0; |
| 714 | const float a = p0.z, b = p0.w, c = p1.x; | 783 | const float a = p0.z, b = p0.w, c = p1.x; |
| 715 | const float det = a * c - b * b; | 784 | const float det = a * c - b * b; |
| 716 | if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0; | 785 | if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0; |
| 717 | const float l_over_det = limit / det; | 786 | const float l_over_det = limit / det; |
| 718 | const float rx = sqrtf(c * l_over_det); | 787 | const float rx = sqrtf(c * l_over_det); |
| 719 | const float ry = sqrtf(a * l_over_det); | 788 | const float ry = sqrtf(a * l_over_det); |
| 720 | const float2 xy = {p0.x, p0.y}; | 789 | const float2 xy = {p0.x, p0.y}; |
| 721 | const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx)); | 790 | const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx)); |
| 722 | const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx)); | 791 | const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx)); |
| 723 | const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx)); | 792 | const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx)); |
| 724 | const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx)); | 793 | const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx)); |
| 725 | if (x0 > x1 || y0 > y1) return 0; | 794 | if (x0 > x1 || y0 > y1) return 0; |
| 726 | const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize; | 795 | const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize; |
| 727 | uint32_t mx = 0u; | 796 | uint32_t mx = 0u; |
| 728 | #pragma unroll | 797 | #pragma unroll |
| 729 | for (int wx = 0; wx < L::kWarpsX; ++wx) { | 798 | for (int wx = 0; wx < L::kWarpsX; ++wx) { |
| 730 | const int wx0 = tx + wx * L::kWarpWidth; | 799 | const int wx0 = tx + wx * L::kWarpWidth; |
| 731 | if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx); | 800 | if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx); |
| 732 | } | 801 | } |
| 733 | if (mx == 0u) return 0; | 802 | if (mx == 0u) return 0; |
| 734 | uint32_t my = 0u; | 803 | uint32_t my = 0u; |
| 735 | #pragma unroll | 804 | #pragma unroll |
| 736 | for (int wy = 0; wy < kWarpsY; ++wy) { | 805 | for (int wy = 0; wy < kWarpsY; ++wy) { |
| 737 | const int wy0 = ty + wy * L::kWarpRows; | 806 | const int wy0 = ty + wy * L::kWarpRows; |
| 738 | if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy); | 807 | if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy); |
| 739 | } | 808 | } |
| 740 | if (my == 0u) return 0; | 809 | if (my == 0u) return 0; |
| 741 | if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) { | 810 | if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) { |
| 742 | uint32_t mask = 0u; | 811 | uint32_t mask = 0u; |
| 743 | #pragma unroll | 812 | #pragma unroll |
| 744 | for (int wy = 0; wy < kWarpsY; ++wy) { | 813 | for (int wy = 0; wy < kWarpsY; ++wy) { |
| 745 | if (!((my >> wy) & 1u)) continue; | 814 | if (!((my >> wy) & 1u)) continue; |
| 746 | #pragma unroll | 815 | #pragma unroll |
| 747 | for (int wx = 0; wx < L::kWarpsX; ++wx) { | 816 | for (int wx = 0; wx < L::kWarpsX; ++wx) { |
| 748 | if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx)); | 817 | if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx)); |
| 749 | } | 818 | } |
| 750 | } | 819 | } |
| 751 | return mask; | 820 | return mask; |
| 752 | } | 821 | } |
| 753 | EllipseBounds eb; | 822 | EllipseBounds eb; |
| 754 | eb.a = a; | 823 | eb.a = a; |
| 755 | eb.b = b; | 824 | eb.b = b; |
| 756 | eb.c = c; | 825 | eb.c = c; |
| 757 | eb.det = det; | 826 | eb.det = det; |
| 758 | eb.inv_a = 1.0f / a; | 827 | eb.inv_a = 1.0f / a; |
| 759 | eb.a_limit = a * limit; | 828 | eb.a_limit = a * limit; |
| 760 | eb.y_ext = ry; | 829 | eb.y_ext = ry; |
| 761 | eb.dy_peak = b * sqrtf(l_over_det / c); | 830 | eb.dy_peak = b * sqrtf(l_over_det / c); |
| 762 | uint32_t mask = 0u; | 831 | uint32_t mask = 0u; |
| 763 | #pragma unroll | 832 | #pragma unroll |
| 764 | for (int wy = 0; wy < kWarpsY; ++wy) { | 833 | for (int wy = 0; wy < kWarpsY; ++wy) { |
| 765 | if (!((my >> wy) & 1u)) continue; | 834 | if (!((my >> wy) & 1u)) continue; |
| 766 | const int wy0 = ty + wy * L::kWarpRows; | 835 | const int wy0 = ty + wy * L::kWarpRows; |
| 767 | int x0_px, x1_px; | 836 | int x0_px, x1_px; |
| 768 | if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) { | 837 | if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) { |
| 769 | continue; | 838 | continue; |
| 770 | } | 839 | } |
| 771 | #pragma unroll | 840 | #pragma unroll |
| 772 | for (int wx = 0; wx < L::kWarpsX; ++wx) { | 841 | for (int wx = 0; wx < L::kWarpsX; ++wx) { |
| 773 | const int wx0 = tx + wx * L::kWarpWidth; | 842 | const int wx0 = tx + wx * L::kWarpWidth; |
| 774 | if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) { | 843 | if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) { |
| 775 | mask |= 1u << (wy * L::kWarpsX + wx); | 844 | mask |= 1u << (wy * L::kWarpsX + wx); |
| 776 | } | 845 | } |
| 777 | } | 846 | } |
| 778 | } | 847 | } |
| 779 | return mask; | 848 | return mask; |
| 780 | } | 849 | } |
| 781 | 850 | ||
| 782 | } // namespace gs | 851 | } // namespace gs |
| 783 | 852 | ||
| 784 | #endif | 853 | #endif |
Expand 297 unchanged lines (lines 1–297) · in ForEachTileRow | |||
| 1 | 1 | /* | |
| 2 | 2 | * Copyright (C) 2023, Inria | |
| 3 | 3 | * GRAPHDECO research group, https://team.inria.fr/graphdeco | |
| 4 | 4 | * All rights reserved. | |
| 5 | 5 | * | |
| 6 | 6 | * This software is free for non-commercial, research and evaluation use | |
| 7 | 7 | * under the terms of the LICENSE.md file. | |
| 8 | 8 | * | |
| 9 | 9 | * For inquiries contact george.drettakis@inria.fr | |
| 10 | 10 | */ | |
| 11 | 11 | ||
| 12 | 12 | #ifndef CUDA_RASTERIZER_AUXILIARY_H_INCLUDED | |
| 13 | 13 | #define CUDA_RASTERIZER_AUXILIARY_H_INCLUDED | |
| 14 | 14 | ||
| 15 | 15 | #include <cuda_runtime.h> | |
| 16 | 16 | ||
| 17 | 17 | #include <cmath> | |
| 18 | 18 | #include <cstdint> | |
| 19 | 19 | ||
| 20 | 20 | #include "config.h" | |
| 21 | 21 | #include "stdio.h" | |
| 22 | 22 | ||
| 23 | 23 | #define BLOCK_SIZE (BLOCK_X * BLOCK_Y) | |
| 24 | 24 | #define NUM_WARPS (BLOCK_SIZE / 32) | |
| 25 | 25 | ||
| 26 | 26 | // Spherical harmonics coefficients | |
| 27 | 27 | __device__ const float SH_C0 = 0.28209479177387814f; | |
| 28 | 28 | __device__ const float SH_C1 = 0.4886025119029199f; | |
| 29 | 29 | __device__ const float SH_C2[] = {1.0925484305920792f, -1.0925484305920792f, | |
| 30 | 30 | 0.31539156525252005f, -1.0925484305920792f, | |
| 31 | 31 | 0.5462742152960396f}; | |
| 32 | 32 | __device__ const float SH_C3[] = {-0.5900435899266435f, 2.890611442640554f, | |
| 33 | 33 | -0.4570457994644658f, 0.3731763325901154f, | |
| 34 | 34 | -0.4570457994644658f, 1.445305721320277f, | |
| 35 | 35 | -0.5900435899266435f}; | |
| 36 | 36 | ||
| 37 | 37 | namespace gs { | |
| 38 | 38 | ||
| 39 | 39 | #define GS_HD __host__ __device__ __forceinline__ | |
| 40 | 40 | #define GS_D __device__ __forceinline__ | |
| 41 | 41 | ||
| 42 | 42 | GS_HD float3 operator+(float3 a, float3 b) { | |
| 43 | 43 | return {a.x + b.x, a.y + b.y, a.z + b.z}; | |
| 44 | 44 | } | |
| 45 | 45 | GS_HD float3 operator-(float3 a, float3 b) { | |
| 46 | 46 | return {a.x - b.x, a.y - b.y, a.z - b.z}; | |
| 47 | 47 | } | |
| 48 | 48 | GS_HD float3 operator*(float s, float3 a) { | |
| 49 | 49 | return {s * a.x, s * a.y, s * a.z}; | |
| 50 | 50 | } | |
| 51 | 51 | GS_HD float3& operator+=(float3& a, float3 b) { | |
| 52 | 52 | a.x += b.x; | |
| 53 | 53 | a.y += b.y; | |
| 54 | 54 | a.z += b.z; | |
| 55 | 55 | return a; | |
| 56 | 56 | } | |
| 57 | 57 | GS_HD float dot(float3 a, float3 b) { | |
| 58 | 58 | return a.x * b.x + a.y * b.y + a.z * b.z; | |
| 59 | 59 | } | |
| 60 | 60 | ||
| 61 | 61 | // Row-major 3x3 matrix. | |
| 62 | 62 | struct Mat3 { | |
| 63 | 63 | float m[3][3]; | |
| 64 | 64 | GS_HD float3 row(int i) const { return {m[i][0], m[i][1], m[i][2]}; } | |
| 65 | 65 | GS_HD float3 col(int j) const { return {m[0][j], m[1][j], m[2][j]}; } | |
| 66 | 66 | }; | |
| 67 | 67 | ||
| 68 | 68 | GS_HD float3 Mul(const Mat3& a, float3 v) { | |
| 69 | 69 | return {dot(a.row(0), v), dot(a.row(1), v), dot(a.row(2), v)}; | |
| 70 | 70 | } | |
| 71 | 71 | // a^T v. | |
| 72 | 72 | GS_HD float3 MulT(const Mat3& a, float3 v) { | |
| 73 | 73 | return {dot(a.col(0), v), dot(a.col(1), v), dot(a.col(2), v)}; | |
| 74 | 74 | } | |
| 75 | 75 | ||
| 76 | 76 | // Reinterprets bits; used to pack small integers into float records. | |
| 77 | 77 | GS_HD float AsFloat(uint32_t u) { | |
| 78 | 78 | union { | |
| 79 | 79 | uint32_t u; | |
| 80 | 80 | float f; | |
| 81 | 81 | } c{u}; | |
| 82 | 82 | return c.f; | |
| 83 | 83 | } | |
| 84 | 84 | GS_HD uint32_t AsUint(float f) { | |
| 85 | 85 | union { | |
| 86 | 86 | float f; | |
| 87 | 87 | uint32_t u; | |
| 88 | 88 | } c{f}; | |
| 89 | 89 | return c.u; | |
| 90 | 90 | } | |
| 91 | 91 | ||
| 92 | 92 | // Number of floats in the packed camera buffer built by `camera.py`: | |
| 93 | 93 | // [R (9, row-major world-to-camera), t (3), fx, fy, cx, cy, position (3), 0]. | |
| 94 | 94 | inline constexpr int kCameraFloats = 20; | |
| 95 | 95 | ||
| 96 | 96 | // Pinhole camera with OpenCV axes (+x right, +y down, +z forward). Pixel (i, j) | |
| 97 | 97 | // is sampled at coordinate (j, i), as in the Inria rasterizer. | |
| 98 | 98 | struct Camera { | |
| 99 | 99 | Mat3 rotation; // World-to-camera rotation. | |
| 100 | 100 | float3 translation; | |
| 101 | 101 | float fx, fy, cx, cy; | |
| 102 | 102 | float3 position; // Camera center in world space. | |
| 103 | 103 | int width, height; | |
| 104 | 104 | ||
| 105 | 105 | GS_HD static Camera Load(const float* p, int width, int height) { | |
| 106 | 106 | Camera c; | |
| 107 | 107 | for (int i = 0; i < 3; ++i) | |
| 108 | 108 | for (int j = 0; j < 3; ++j) c.rotation.m[i][j] = p[3 * i + j]; | |
| 109 | 109 | c.translation = {p[9], p[10], p[11]}; | |
| 110 | 110 | c.fx = p[12]; | |
| 111 | 111 | c.fy = p[13]; | |
| 112 | 112 | c.cx = p[14]; | |
| 113 | 113 | c.cy = p[15]; | |
| 114 | 114 | c.position = {p[16], p[17], p[18]}; | |
| 115 | 115 | c.width = width; | |
| 116 | 116 | c.height = height; | |
| 117 | 117 | return c; | |
| 118 | 118 | } | |
| 119 | 119 | ||
| 120 | 120 | GS_HD float3 ToView(float3 p) const { return Mul(rotation, p) + translation; } | |
| 121 | 121 | // Tangent of the half field of view, as the Inria rasterizer defines it. | |
| 122 | 122 | GS_HD float TanHalfFovX() const { return width / (2.0f * fx); } | |
| 123 | 123 | GS_HD float TanHalfFovY() const { return height / (2.0f * fy); } | |
| 124 | 124 | }; | |
| 125 | 125 | ||
| 126 | 126 | // Screen tiling. Tiles are `kTileSize` x `kTileSize` pixels; one thread block | |
| 127 | 127 | // rasterizes one tile. | |
| 128 | 128 | inline constexpr int kTileSize = 16; | |
| 129 | 129 | inline constexpr int kTilePixels = kTileSize * kTileSize; | |
| 130 | 130 | ||
| 131 | 131 | struct TileGrid { | |
| 132 | 132 | int tiles_x, tiles_y; | |
| 133 | 133 | GS_HD static TileGrid For(int width, int height) { | |
| 134 | 134 | return {(width + kTileSize - 1) / kTileSize, | |
| 135 | 135 | (height + kTileSize - 1) / kTileSize}; | |
| 136 | 136 | } | |
| 137 | 137 | GS_HD int num_tiles() const { return tiles_x * tiles_y; } | |
| 138 | 138 | }; | |
| 139 | 139 | ||
| 140 | 140 | // Smallest alpha that contributes to a pixel; smaller ones are skipped. | |
| 141 | 141 | inline constexpr float kMinAlpha = 1.0f / 255.0f; | |
| 142 | 142 | inline constexpr float kMaxAlpha = 0.99f; | |
| 143 | 143 | // Transmittance below which a pixel stops accumulating. | |
| 144 | 144 | inline constexpr float kMinTransmittance = 1e-4f; | |
| 145 | 145 | ||
| 146 | 146 | // One splat as stored in memory: three float4 so the rasterizer issues three | |
| 147 | 147 | // 16-byte loads. Layout: | |
| 148 | 148 | // p0 = (x, y, conic.a, conic.b) | |
| 149 | 149 | // p1 = (conic.c, opacity, limit, radius) radius 0 => culled | |
| 150 | 150 | // p2 = (r, g, b, min_power) | |
| 151 | 151 | inline constexpr int kSplatFloats = 12; | |
| 152 | 152 | struct alignas(16) Splat { | |
| 153 | 153 | float4 p0, p1, p2; | |
| 154 | 154 | GS_HD float2 xy() const { return {p0.x, p0.y}; } | |
| 155 | 155 | GS_HD float3 conic() const { return {p0.z, p0.w, p1.x}; } | |
| 156 | 156 | GS_HD float opacity() const { return p1.y; } | |
| 157 | 157 | GS_HD float limit() const { return p1.z; } | |
| 158 | 158 | GS_HD int radius() const { return static_cast<int>(p1.w); } | |
| 159 | 159 | GS_HD float3 rgb() const { return {p2.x, p2.y, p2.z}; } | |
| 160 | 160 | }; | |
| 161 | 161 | ||
| 162 | 162 | // Gradient w.r.t. a splat's rasterized quantities, accumulated by the raster | |
| 163 | 163 | // backward pass: (dx, dy, da, db, dc, dopacity, dr, dg, db). | |
| 164 | 164 | inline constexpr int kSplatGradFloats = 9; | |
| 165 | 165 | ||
| 166 | 166 | // Half-open tile rectangle [x0, x1) x [y0, y1). | |
| 167 | 167 | struct TileRect { | |
| 168 | 168 | int x0, y0, x1, y1; | |
| 169 | 169 | GS_HD int area() const { return (x1 - x0) * (y1 - y0); } | |
| 170 | 170 | }; | |
| 171 | 171 | ||
| 172 | 172 | // Tile rectangle of a splat of integer bounding `radius`, identical to the | |
| 173 | 173 | // reference rasterizer. | |
| 174 | 174 | GS_HD TileRect RectFor(float2 xy, int radius, const TileGrid& grid) { | |
| 175 | 175 | const float r = static_cast<float>(radius); | |
| 176 | 176 | auto lo = [](float v, int n) { | |
| 177 | 177 | return min(n, max(0, static_cast<int>(v / kTileSize))); | |
| 178 | 178 | }; | |
| 179 | 179 | auto hi = [](float v, int n) { | |
| 180 | 180 | return min(n, max(0, static_cast<int>((v + kTileSize - 1) / kTileSize))); | |
| 181 | 181 | }; | |
| 182 | 182 | return {lo(xy.x - r, grid.tiles_x), lo(xy.y - r, grid.tiles_y), | |
| 183 | 183 | hi(xy.x + r, grid.tiles_x), hi(xy.y + r, grid.tiles_y)}; | |
| 184 | 184 | } | |
| 185 | 185 | ||
| 186 | 186 | // How splats are assigned to tiles. | |
| 187 | 187 | enum class Culling : int { | |
| 188 | 188 | // Every tile of the bounding rectangle (reference behaviour). | |
| 189 | 189 | kRect = 0, | |
| 190 | 190 | // Tiles of the rectangle where some pixel can reach alpha >= 1/255. Gives | |
| 191 | 191 | // the same image: skipped (splat, tile) pairs never contribute. | |
| 192 | 192 | kEllipse = 1, | |
| 193 | 193 | }; | |
| 194 | 194 | ||
| 195 | 195 | // alpha = o exp(-q/2) >= 1/255 <=> q <= 2 ln(255 o). Writes the bound on q | |
| 196 | 196 | // with margins that keep tests conservative under float rounding of the | |
| 197 | 197 | // rasterizer's power. Returns false if no pixel can reach 1/255. | |
| 198 | 198 | GS_HD bool AlphaLimit(float opacity, float& limit) { | |
| 199 | 199 | const float o255 = opacity * 255.0f; | |
| 200 | 200 | if (!(o255 >= 0.999f)) return false; | |
| 201 | 201 | limit = 2.0f * logf(fmaxf(o255, 1.0f)) * 1.001f + 0.02f; | |
| 202 | 202 | return true; | |
| 203 | 203 | } | |
| 204 | 204 | ||
| 205 | 205 | // Pad (pixels) on ellipse extents, against rounding. | |
| 206 | 206 | inline constexpr float kSpanPadPx = 0.01f; | |
| 207 | 207 | ||
| 208 | 208 | // Precomputed splat-level ellipse constants for fast band intersection tests | |
| 209 | 209 | // of {q(d) <= limit}, where q(d) = a dx^2 + 2 b dx dy + c dy^2. | |
| 210 | 210 | struct EllipseBounds { | |
| 211 | 211 | float a, b, c; | |
| 212 | 212 | float det; | |
| 213 | 213 | float inv_a; | |
| 214 | 214 | float a_limit; | |
| 215 | 215 | float y_ext; | |
| 216 | 216 | float dy_peak; | |
| 217 | 217 | ||
| 218 | 218 | GS_D static bool Init(float3 conic, float opacity, float cached_limit, | |
| 219 | 219 | EllipseBounds& out) { | |
| 220 | 220 | float limit = cached_limit; | |
| 221 | 221 | if (limit <= 0.0f && !AlphaLimit(opacity, limit)) return false; | |
| 222 | 222 | out.a = conic.x; | |
| 223 | 223 | out.b = conic.y; | |
| 224 | 224 | out.c = conic.z; | |
| 225 | 225 | out.det = out.a * out.c - out.b * out.b; | |
| 226 | 226 | if (!(out.det > 0.0f && out.a > 0.0f && out.c > 0.0f)) return false; | |
| 227 | 227 | out.inv_a = 1.0f / out.a; | |
| 228 | 228 | out.a_limit = out.a * limit; | |
| 229 | 229 | const float limit_over_det = limit / out.det; | |
| 230 | 230 | out.y_ext = sqrtf(out.a * limit_over_det); | |
| 231 | 231 | out.dy_peak = out.b * sqrtf(limit_over_det / out.c); | |
| 232 | 232 | return true; | |
| 233 | 233 | } | |
| 234 | 234 | ||
| 235 | 235 | // Span [x0_px, x1_px] of integer pixel columns hit across integer pixel rows | |
| 236 | 236 | // Y in [y0_px, y1_px] (inclusive). Returns false if no integer pixel hits. | |
| 237 | 237 | GS_D bool SpanForPixelRows(float2 xy, int y0_px, int y1_px, int& x0_px, | |
| 238 | 238 | int& x1_px) const { | |
| 239 | 239 | const int ylo_i = | |
| 240 | 240 | max(y0_px, static_cast<int>(ceilf(xy.y - y_ext - kSpanPadPx))); | |
| 241 | 241 | const int yhi_i = | |
| 242 | 242 | min(y1_px, static_cast<int>(floorf(xy.y + y_ext + kSpanPadPx))); | |
| 243 | 243 | if (ylo_i > yhi_i) return false; | |
| 244 | 244 | const float ylo = static_cast<float>(ylo_i) - xy.y; | |
| 245 | 245 | const float yhi = static_cast<float>(yhi_i) - xy.y; | |
| 246 | 246 | // Boundary x(dy) = (-b dy +- sqrt(a limit - det dy^2)) / a; the right | |
| 247 | 247 | // (left) branch is concave (convex) and peaks at dy = -+dy_peak. | |
| 248 | 248 | const float dyr = fminf(yhi, fmaxf(ylo, -dy_peak)); | |
| 249 | 249 | const float dyl = fminf(yhi, fmaxf(ylo, dy_peak)); | |
| 250 | 250 | const float dyr2 = dyr * dyr, dyl2 = dyl * dyl; | |
| 251 | 251 | const float rr = sqrtf(fmaxf(0.0f, a_limit - det * dyr2)); | |
| 252 | 252 | const float rl = | |
| 253 | 253 | (dyr2 == dyl2) ? rr : sqrtf(fmaxf(0.0f, a_limit - det * dyl2)); | |
| 254 | 254 | const float hi = (-b * dyr + rr) * inv_a; | |
| 255 | 255 | const float lo = (-b * dyl - rl) * inv_a; | |
| 256 | 256 | x0_px = static_cast<int>(ceilf(xy.x + lo - kSpanPadPx)); | |
| 257 | 257 | x1_px = static_cast<int>(floorf(xy.x + hi + kSpanPadPx)); | |
| 258 | 258 | return x0_px <= x1_px; | |
| 259 | 259 | } | |
| 260 | 260 | }; | |
| 261 | 261 | ||
| 262 | 262 | // Visits every tile row `ty` with the half-open span [tx0, tx1) of tiles that | |
| 263 | 263 | // splat `s` (not culled) is binned to. Rows may be empty (tx0 >= tx1). | |
| 264 | 264 | template <Culling kCulling, typename Fn> | |
| 265 | 265 | GS_D void ForEachTileRow(const Splat& s, const TileGrid& grid, int width, | |
| 266 | 266 | int height, Fn&& fn) { | |
| 267 | 267 | const float2 xy = s.xy(); | |
| 268 | 268 | const TileRect rect = RectFor(xy, s.radius(), grid); | |
| 269 | 269 | if constexpr (kCulling == Culling::kRect) { | |
| 270 | 270 | for (int ty = rect.y0; ty < rect.y1; ++ty) fn(ty, rect.x0, rect.x1); | |
| 271 | 271 | } else { | |
| 272 | 272 | EllipseBounds eb; | |
| 273 | 273 | if (!EllipseBounds::Init(s.conic(), s.opacity(), s.limit(), eb)) return; | |
| 274 | 274 | const int min_y = | |
| 275 | 275 | max(0, static_cast<int>(ceilf(xy.y - eb.y_ext - kSpanPadPx))); | |
| 276 | 276 | const int max_y = | |
| 277 | 277 | min(height - 1, static_cast<int>(floorf(xy.y + eb.y_ext + kSpanPadPx))); | |
| 278 | 278 | if (min_y > max_y) return; | |
| 279 | 279 | const int ty0 = max(rect.y0, min_y / kTileSize); | |
| 280 | 280 | const int ty1 = min(rect.y1, (max_y / kTileSize) + 1); | |
| 281 | 281 | for (int ty = ty0; ty < ty1; ++ty) { | |
| 282 | 282 | int x0_px, x1_px; | |
| 283 | 283 | if (!eb.SpanForPixelRows(xy, ty * kTileSize, | |
| 284 | 284 | min((ty + 1) * kTileSize, height) - 1, x0_px, | |
| 285 | 285 | x1_px)) { | |
| 286 | 286 | continue; | |
| 287 | 287 | } | |
| 288 | 288 | x0_px = max(0, x0_px); | |
| 289 | 289 | x1_px = min(width - 1, x1_px); | |
| 290 | 290 | if (x0_px > x1_px) continue; | |
| 291 | 291 | const int tx0 = max(rect.x0, x0_px / kTileSize); | |
| 292 | 292 | const int tx1 = min(rect.x1, (x1_px / kTileSize) + 1); | |
| 293 | 293 | if (tx0 < tx1) fn(ty, tx0, tx1); | |
| 294 | 294 | } | |
| 295 | 295 | } | |
| 296 | 296 | } | |
| 297 | 297 | ||
| 298 | 298 | namespace warp { | |
| 299 | 299 | ||
| 300 | 300 | inline constexpr unsigned kFullMask = 0xffffffffu; | |
| 301 | 301 | ||
| 302 | − | // Standard warp butterfly reduction of an array of `N` per-lane values: | |
| 302 | + | // Reduce-scatter of an array of `N` <= 32 per-lane values over the warp. | |
| 303 | − | // 5 steps of `__shfl_xor_sync` for each element (`5 * N` shuffles total), | |
| 304 | − | // after which lane 0 (and every other lane) holds the warp sum of `v[0..N)`. | |
| 303 | + | // | |
| 304 | + | // A butterfly where each step halves the payload: at offset `o`, lanes with | |
| 305 | + | // bit `o` clear keep the low half of the array and send the high half, the | |
| 306 | + | // others do the opposite. After log2(32) steps every lane holds a single | |
| 307 | + | // value, v[0], which is the warp total of element `ScatterIndex(N, lane)` | |
| 308 | + | // (or junk if that is -1). For N = 9 this takes 12 shuffles instead of the | |
| 309 | + | // 45 of nine independent butterflies, and the totals end in distinct lanes, | |
| 310 | + | // so writing them out is one instruction. | |
| 311 | + | ||
| 312 | + | namespace internal { | |
| 305 | − | template <int N> | |
| 313 | + | template <int N, int kOffset> | |
| 306 | − | GS_D void ButterflyReduce(float (&v)[N]) { | |
| 314 | + | GS_D void HalvingStep(float* v, int lane) { | |
| 315 | + | constexpr int kHalf = (N + 1) / 2; | |
| 316 | + | const bool upper = lane & kOffset; | |
| 307 | 317 | #pragma unroll | |
| 308 | − | for (int offset = 16; offset >= 1; offset >>= 1) { | |
| 309 | − | #pragma unroll | |
| 310 | − | for (int i = 0; i < N; ++i) { | |
| 318 | + | for (int i = 0; i < kHalf; ++i) { | |
| 319 | + | const float lo = v[i]; | |
| 320 | + | const float hi = i + kHalf < N ? v[i + kHalf] : 0.0f; | |
| 321 | + | const float send = upper ? lo : hi; | |
| 322 | + | const float keep = upper ? hi : lo; | |
| 311 | − | v[i] += __shfl_xor_sync(kFullMask, v[i], offset); | |
| 323 | + | v[i] = keep + __shfl_xor_sync(kFullMask, send, kOffset); | |
| 312 | 324 | ⇥ | } |
| 313 | 325 | ⇥ | } |
| 326 | + | ||
| 327 | + | template <int N, int kOffset> | |
| 328 | + | GS_D void ReduceScatterImpl(float* v, int lane) { | |
| 329 | + | if constexpr (kOffset > 0) { | |
| 330 | + | HalvingStep<N, kOffset>(v, lane); | |
| 331 | + | ReduceScatterImpl<(N + 1) / 2, kOffset / 2>(v, lane); | |
| 332 | + | } | |
| 333 | + | } | |
| 334 | + | } // namespace internal | |
| 335 | + | ||
| 336 | + | template <int N> | |
| 337 | + | GS_D void ReduceScatter(float (&v)[N], int lane) { | |
| 338 | + | static_assert(N >= 1 && N <= 32); | |
| 339 | + | internal::ReduceScatterImpl<N, 16>(v, lane); | |
| 340 | + | } | |
| 341 | + | ||
| 342 | + | template <int N> | |
| 343 | + | GS_D void ReduceScatter16(float (&v)[N], int lane) { | |
| 344 | + | static_assert(N >= 1 && N <= 16); | |
| 345 | + | internal::ReduceScatterImpl<N, 8>(v, lane); | |
| 346 | + | } | |
| 347 | + | ||
| 348 | + | // Element whose warp total lane `lane` holds after `ReduceScatter<N>`, or -1. | |
| 349 | + | GS_HD constexpr int ScatterIndex(int n, int lane) { | |
| 350 | + | // Payload sizes and halves per step, offset 16 first. | |
| 351 | + | int sizes[5] = {}; | |
| 352 | + | int size = n; | |
| 353 | + | for (int s = 0; s < 5; ++s) { | |
| 354 | + | sizes[s] = size; | |
| 355 | + | size = (size + 1) / 2; | |
| 356 | + | } | |
| 357 | + | int index = 0; // Slot in the final (size 1) array. | |
| 358 | + | for (int s = 4; s >= 0; --s) { | |
| 359 | + | const int half = (sizes[s] + 1) / 2; | |
| 360 | + | if (lane & (16 >> s)) index += half; | |
| 361 | + | if (index >= sizes[s]) return -1; | |
| 362 | + | } | |
| 363 | + | return index; | |
| 364 | + | } | |
| 365 | + | ||
| 366 | + | // Element whose 16-lane half-warp total lane `lane` holds after | |
| 367 | + | // `ReduceScatter16<N>`, or -1. | |
| 368 | + | GS_HD constexpr int ScatterIndex16(int n, int lane) { | |
| 369 | + | const int half_lane = lane & 15; | |
| 370 | + | int sizes[4] = {}; | |
| 371 | + | int size = n; | |
| 372 | + | for (int s = 0; s < 4; ++s) { | |
| 373 | + | sizes[s] = size; | |
| 374 | + | size = (size + 1) / 2; | |
| 375 | + | } | |
| 376 | + | int index = 0; | |
| 377 | + | for (int s = 3; s >= 0; --s) { | |
| 378 | + | const int half = (sizes[s] + 1) / 2; | |
| 379 | + | if (half_lane & (8 >> s)) index += half; | |
| 380 | + | if (index >= sizes[s]) return -1; | |
| 381 | + | } | |
| 382 | + | return index; | |
| 314 | 383 | } | |
| 315 | 384 | ||
| 316 | 385 | } // namespace warp | |
| 317 | 386 | ||
Expand 467 unchanged lines (lines 387–853) · in WarpMask | |||
| 318 | 387 | inline constexpr int kShCoeffs = 16; // Degree 3. | |
| 319 | 388 | inline constexpr float kNearPlane = 0.2f; | |
| 320 | 389 | inline constexpr float kLowPass = 0.3f; | |
| 321 | 390 | inline constexpr float kFovClamp = 1.3f; | |
| 322 | 391 | ||
| 323 | 392 | // Pointers to the trainable parameters, all activated, row-major. | |
| 324 | 393 | struct GaussianParams { | |
| 325 | 394 | const float* means; // (N, 3) | |
| 326 | 395 | const float* scales; // (N, 3) | |
| 327 | 396 | const float* quats; // (N, 4) as (w, x, y, z), not normalized. | |
| 328 | 397 | const float* opacities; // (N,) | |
| 329 | 398 | const void* sh; // (N, 16, 3) of `float` or `ShBf16` (see `ShIo`). | |
| 330 | 399 | int n; | |
| 331 | 400 | }; | |
| 332 | 401 | ||
| 333 | 402 | // Gradients w.r.t. `GaussianParams`, same layout and SH storage type. | |
| 334 | 403 | struct GaussianGrads { | |
| 335 | 404 | float* means; | |
| 336 | 405 | float* scales; | |
| 337 | 406 | float* quats; | |
| 338 | 407 | float* opacities; | |
| 339 | 408 | void* sh; | |
| 340 | 409 | }; | |
| 341 | 410 | ||
| 342 | 411 | // Screen-space quantities needed by rasterization. `conic` is the inverse 2D | |
| 343 | 412 | // covariance (a, b, c) with power = -0.5 (a dx^2 + c dy^2) - b dx dy. | |
| 344 | 413 | struct Projection { | |
| 345 | 414 | float2 xy; | |
| 346 | 415 | float depth; | |
| 347 | 416 | float3 conic; | |
| 348 | 417 | float opacity; | |
| 349 | 418 | float limit; | |
| 350 | 419 | float3 rgb; | |
| 351 | 420 | int radius; // 0 means culled. | |
| 352 | 421 | }; | |
| 353 | 422 | ||
| 354 | 423 | // ---------------------------------------------------------------------------- | |
| 355 | 424 | // SH coefficient storage. A row holds the 16 x 3 coefficients of one Gaussian | |
| 356 | 425 | // and may be stored as f32 or as bf16 (raw bits, `ShBf16`); math is always f32. | |
| 357 | 426 | ||
| 358 | 427 | using ShBf16 = uint16_t; // bfloat16 bits. | |
| 359 | 428 | ||
| 360 | 429 | template <typename T> | |
| 361 | 430 | struct ShIo; | |
| 362 | 431 | ||
| 363 | 432 | template <> | |
| 364 | 433 | struct ShIo<float> { | |
| 365 | 434 | GS_D static void LoadRow(const float* row, float3 coeffs[kShCoeffs]) { | |
| 366 | 435 | const float4* p = reinterpret_cast<const float4*>(row); | |
| 367 | 436 | float raw[3 * kShCoeffs]; | |
| 368 | 437 | #pragma unroll | |
| 369 | 438 | for (int k = 0; k < 12; ++k) { | |
| 370 | 439 | const float4 q = __ldg(p + k); | |
| 371 | 440 | raw[4 * k] = q.x; | |
| 372 | 441 | raw[4 * k + 1] = q.y; | |
| 373 | 442 | raw[4 * k + 2] = q.z; | |
| 374 | 443 | raw[4 * k + 3] = q.w; | |
| 375 | 444 | } | |
| 376 | 445 | #pragma unroll | |
| 377 | 446 | for (int c = 0; c < kShCoeffs; ++c) { | |
| 378 | 447 | coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]}; | |
| 379 | 448 | } | |
| 380 | 449 | } | |
| 381 | 450 | GS_D static void StoreRow(float* row, const float3 dsh[kShCoeffs]) { | |
| 382 | 451 | float raw[3 * kShCoeffs]; | |
| 383 | 452 | #pragma unroll | |
| 384 | 453 | for (int c = 0; c < kShCoeffs; ++c) { | |
| 385 | 454 | raw[3 * c] = dsh[c].x; | |
| 386 | 455 | raw[3 * c + 1] = dsh[c].y; | |
| 387 | 456 | raw[3 * c + 2] = dsh[c].z; | |
| 388 | 457 | } | |
| 389 | 458 | float4* p = reinterpret_cast<float4*>(row); | |
| 390 | 459 | #pragma unroll | |
| 391 | 460 | for (int k = 0; k < 12; ++k) { | |
| 392 | 461 | p[k] = {raw[4 * k], raw[4 * k + 1], raw[4 * k + 2], raw[4 * k + 3]}; | |
| 393 | 462 | } | |
| 394 | 463 | } | |
| 395 | 464 | }; | |
| 396 | 465 | ||
| 397 | 466 | template <> | |
| 398 | 467 | struct ShIo<ShBf16> { | |
| 399 | 468 | GS_D static float Lo(uint32_t w) { return __uint_as_float(w << 16); } | |
| 400 | 469 | GS_D static float Hi(uint32_t w) { return __uint_as_float(w & 0xffff0000u); } | |
| 401 | 470 | // Round to nearest even (inputs are finite). | |
| 402 | 471 | GS_D static uint32_t Round(float f) { | |
| 403 | 472 | const uint32_t u = __float_as_uint(f); | |
| 404 | 473 | return (u + 0x7fffu + ((u >> 16) & 1u)) >> 16; | |
| 405 | 474 | } | |
| 406 | 475 | GS_D static uint32_t Pack(float lo, float hi) { | |
| 407 | 476 | return Round(lo) | (Round(hi) << 16); | |
| 408 | 477 | } | |
| 409 | 478 | GS_D static void LoadRow(const ShBf16* row, float3 coeffs[kShCoeffs]) { | |
| 410 | 479 | const uint2* p = reinterpret_cast<const uint2*>(row); | |
| 411 | 480 | float raw[3 * kShCoeffs]; | |
| 412 | 481 | #pragma unroll | |
| 413 | 482 | for (int k = 0; k < 12; ++k) { | |
| 414 | 483 | const uint2 w = __ldg(p + k); | |
| 415 | 484 | raw[4 * k] = Lo(w.x); | |
| 416 | 485 | raw[4 * k + 1] = Hi(w.x); | |
| 417 | 486 | raw[4 * k + 2] = Lo(w.y); | |
| 418 | 487 | raw[4 * k + 3] = Hi(w.y); | |
| 419 | 488 | } | |
| 420 | 489 | #pragma unroll | |
| 421 | 490 | for (int c = 0; c < kShCoeffs; ++c) { | |
| 422 | 491 | coeffs[c] = {raw[3 * c], raw[3 * c + 1], raw[3 * c + 2]}; | |
| 423 | 492 | } | |
| 424 | 493 | } | |
| 425 | 494 | GS_D static void StoreRow(ShBf16* row, const float3 dsh[kShCoeffs]) { | |
| 426 | 495 | float raw[3 * kShCoeffs]; | |
| 427 | 496 | #pragma unroll | |
| 428 | 497 | for (int c = 0; c < kShCoeffs; ++c) { | |
| 429 | 498 | raw[3 * c] = dsh[c].x; | |
| 430 | 499 | raw[3 * c + 1] = dsh[c].y; | |
| 431 | 500 | raw[3 * c + 2] = dsh[c].z; | |
| 432 | 501 | } | |
| 433 | 502 | uint2* p = reinterpret_cast<uint2*>(row); | |
| 434 | 503 | #pragma unroll | |
| 435 | 504 | for (int k = 0; k < 12; ++k) { | |
| 436 | 505 | p[k] = {Pack(raw[4 * k], raw[4 * k + 1]), | |
| 437 | 506 | Pack(raw[4 * k + 2], raw[4 * k + 3])}; | |
| 438 | 507 | } | |
| 439 | 508 | } | |
| 440 | 509 | }; | |
| 441 | 510 | ||
| 442 | 511 | template <typename ShT> | |
| 443 | 512 | GS_D const ShT* ShRow(const void* sh, int i) { | |
| 444 | 513 | return static_cast<const ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs; | |
| 445 | 514 | } | |
| 446 | 515 | template <typename ShT> | |
| 447 | 516 | GS_D ShT* ShRow(void* sh, int i) { | |
| 448 | 517 | return static_cast<ShT*>(sh) + static_cast<size_t>(i) * 3 * kShCoeffs; | |
| 449 | 518 | } | |
| 450 | 519 | ||
| 451 | 520 | // ---------------------------------------------------------------------------- | |
| 452 | 521 | // Spherical harmonics (degree 3, Inria sign convention). | |
| 453 | 522 | ||
| 454 | 523 | namespace sh { | |
| 455 | 524 | inline constexpr float kC0 = 0.28209479177387814f; | |
| 456 | 525 | inline constexpr float kC1 = 0.4886025119029199f; | |
| 457 | 526 | inline constexpr float kC2[5] = {1.0925484305920792f, -1.0925484305920792f, | |
| 458 | 527 | 0.31539156525252005f, -1.0925484305920792f, | |
| 459 | 528 | 0.5462742152960396f}; | |
| 460 | 529 | inline constexpr float kC3[7] = {-0.5900435899266435f, 2.890611442640554f, | |
| 461 | 530 | -0.4570457994644658f, 0.3731763325901154f, | |
| 462 | 531 | -0.4570457994644658f, 1.445305721320277f, | |
| 463 | 532 | -0.5900435899266435f}; | |
| 464 | 533 | ||
| 465 | 534 | // Basis values for a unit direction. | |
| 466 | 535 | GS_D void Basis(float3 d, float b[kShCoeffs]) { | |
| 467 | 536 | const float x = d.x, y = d.y, z = d.z; | |
| 468 | 537 | const float xx = x * x, yy = y * y, zz = z * z; | |
| 469 | 538 | b[0] = kC0; | |
| 470 | 539 | b[1] = -kC1 * y; | |
| 471 | 540 | b[2] = kC1 * z; | |
| 472 | 541 | b[3] = -kC1 * x; | |
| 473 | 542 | b[4] = kC2[0] * x * y; | |
| 474 | 543 | b[5] = kC2[1] * y * z; | |
| 475 | 544 | b[6] = kC2[2] * (2.0f * zz - xx - yy); | |
| 476 | 545 | b[7] = kC2[3] * x * z; | |
| 477 | 546 | b[8] = kC2[4] * (xx - yy); | |
| 478 | 547 | b[9] = kC3[0] * y * (3.0f * xx - yy); | |
| 479 | 548 | b[10] = kC3[1] * x * y * z; | |
| 480 | 549 | b[11] = kC3[2] * y * (4.0f * zz - xx - yy); | |
| 481 | 550 | b[12] = kC3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy); | |
| 482 | 551 | b[13] = kC3[4] * x * (4.0f * zz - xx - yy); | |
| 483 | 552 | b[14] = kC3[5] * z * (xx - yy); | |
| 484 | 553 | b[15] = kC3[6] * x * (xx - 3.0f * yy); | |
| 485 | 554 | } | |
| 486 | 555 | ||
| 487 | 556 | // Partial derivatives of every basis function w.r.t. (x, y, z) of the unit | |
| 488 | 557 | // direction (treated as free coordinates). | |
| 489 | 558 | GS_D void BasisGrad(float3 d, float3 g[kShCoeffs]) { | |
| 490 | 559 | const float x = d.x, y = d.y, z = d.z; | |
| 491 | 560 | const float xx = x * x, yy = y * y, zz = z * z; | |
| 492 | 561 | g[0] = {0.0f, 0.0f, 0.0f}; | |
| 493 | 562 | g[1] = {0.0f, -kC1, 0.0f}; | |
| 494 | 563 | g[2] = {0.0f, 0.0f, kC1}; | |
| 495 | 564 | g[3] = {-kC1, 0.0f, 0.0f}; | |
| 496 | 565 | g[4] = kC2[0] * float3{y, x, 0.0f}; | |
| 497 | 566 | g[5] = kC2[1] * float3{0.0f, z, y}; | |
| 498 | 567 | g[6] = kC2[2] * float3{-2.0f * x, -2.0f * y, 4.0f * z}; | |
| 499 | 568 | g[7] = kC2[3] * float3{z, 0.0f, x}; | |
| 500 | 569 | g[8] = kC2[4] * float3{2.0f * x, -2.0f * y, 0.0f}; | |
| 501 | 570 | g[9] = kC3[0] * float3{6.0f * x * y, 3.0f * (xx - yy), 0.0f}; | |
| 502 | 571 | g[10] = kC3[1] * float3{y * z, x * z, x * y}; | |
| 503 | 572 | g[11] = | |
| 504 | 573 | kC3[2] * float3{-2.0f * x * y, 4.0f * zz - xx - 3.0f * yy, 8.0f * y * z}; | |
| 505 | 574 | g[12] = kC3[3] * float3{-6.0f * x * z, -6.0f * y * z, | |
| 506 | 575 | 6.0f * zz - 3.0f * xx - 3.0f * yy}; | |
| 507 | 576 | g[13] = | |
| 508 | 577 | kC3[4] * float3{4.0f * zz - 3.0f * xx - yy, -2.0f * x * y, 8.0f * x * z}; | |
| 509 | 578 | g[14] = kC3[5] * float3{2.0f * x * z, -2.0f * y * z, xx - yy}; | |
| 510 | 579 | g[15] = kC3[6] * float3{3.0f * (xx - yy), -6.0f * x * y, 0.0f}; | |
| 511 | 580 | } | |
| 512 | 581 | ||
| 513 | 582 | // Pre-clamp color 0.5 + sum_k b_k sh_k, loading the full 16-basis SH row. | |
| 514 | 583 | template <typename ShT> | |
| 515 | 584 | GS_D float3 EvalRow(const ShT* __restrict__ sh_row, const float b[kShCoeffs]) { | |
| 516 | 585 | float3 coeffs[kShCoeffs]; | |
| 517 | 586 | ShIo<ShT>::LoadRow(sh_row, coeffs); | |
| 518 | 587 | float3 c = {0.5f, 0.5f, 0.5f}; | |
| 519 | 588 | #pragma unroll | |
| 520 | 589 | for (int k = 0; k < kShCoeffs; ++k) { | |
| 521 | 590 | c += b[k] * coeffs[k]; | |
| 522 | 591 | } | |
| 523 | 592 | return c; | |
| 524 | 593 | } | |
| 525 | 594 | } // namespace sh | |
| 526 | 595 | ||
| 527 | 596 | // ---------------------------------------------------------------------------- | |
| 528 | 597 | // Covariance helpers. | |
| 529 | 598 | ||
| 530 | 599 | // Standard rotation matrix of the (unnormalized) quaternion (w, x, y, z). | |
| 531 | 600 | GS_D Mat3 QuatToRotation(float4 q) { | |
| 532 | 601 | const float w = q.x, x = q.y, y = q.z, z = q.w; | |
| 533 | 602 | Mat3 r; | |
| 534 | 603 | r.m[0][0] = 1.0f - 2.0f * (y * y + z * z); | |
| 535 | 604 | r.m[0][1] = 2.0f * (x * y - w * z); | |
| 536 | 605 | r.m[0][2] = 2.0f * (x * z + w * y); | |
| 537 | 606 | r.m[1][0] = 2.0f * (x * y + w * z); | |
| 538 | 607 | r.m[1][1] = 1.0f - 2.0f * (x * x + z * z); | |
| 539 | 608 | r.m[1][2] = 2.0f * (y * z - w * x); | |
| 540 | 609 | r.m[2][0] = 2.0f * (x * z - w * y); | |
| 541 | 610 | r.m[2][1] = 2.0f * (y * z + w * x); | |
| 542 | 611 | r.m[2][2] = 1.0f - 2.0f * (x * x + y * y); | |
| 543 | 612 | return r; | |
| 544 | 613 | } | |
| 545 | 614 | ||
| 546 | 615 | // Symmetric 3x3 matrix. | |
| 547 | 616 | struct Sym3 { | |
| 548 | 617 | float xx, xy, xz, yy, yz, zz; | |
| 549 | 618 | GS_D float3 Mul(float3 v) const { | |
| 550 | 619 | return {xx * v.x + xy * v.y + xz * v.z, xy * v.x + yy * v.y + yz * v.z, | |
| 551 | 620 | xz * v.x + yz * v.y + zz * v.z}; | |
| 552 | 621 | } | |
| 553 | 622 | }; | |
| 554 | 623 | ||
| 555 | 624 | // Sigma = A A^T with A = R diag(s). | |
| 556 | 625 | GS_D Sym3 Covariance3D(const Mat3& r, float3 s) { | |
| 557 | 626 | Mat3 a; | |
| 558 | 627 | const float sv[3] = {s.x, s.y, s.z}; | |
| 559 | 628 | #pragma unroll | |
| 560 | 629 | for (int i = 0; i < 3; ++i) | |
| 561 | 630 | #pragma unroll | |
| 562 | 631 | for (int j = 0; j < 3; ++j) a.m[i][j] = r.m[i][j] * sv[j]; | |
| 563 | 632 | return {dot(a.row(0), a.row(0)), dot(a.row(0), a.row(1)), | |
| 564 | 633 | dot(a.row(0), a.row(2)), dot(a.row(1), a.row(1)), | |
| 565 | 634 | dot(a.row(1), a.row(2)), dot(a.row(2), a.row(2))}; | |
| 566 | 635 | } | |
| 567 | 636 | ||
| 568 | 637 | // Intermediate values of the projection shared by forward and VJP. | |
| 569 | 638 | struct ProjectionState { | |
| 570 | 639 | float3 t; // View-space mean. | |
| 571 | 640 | float tx_c, ty_c; // Clamped x, y used by the Jacobian. | |
| 572 | 641 | bool x_clamped, y_clamped; | |
| 573 | 642 | float3 m0, m1; // Rows of M = J W (2x3). | |
| 574 | 643 | Sym3 sigma; // World-space 3D covariance. | |
| 575 | 644 | float a, b, c; // Dilated 2D covariance. | |
| 576 | 645 | }; | |
| 577 | 646 | ||
| 578 | 647 | // Computes 2D covariance given view-space mean `t` (with t.z > kNearPlane). | |
| 579 | 648 | GS_D void ProjectCovarianceFromView(const Camera& cam, float3 t, float3 scale, | |
| 580 | 649 | float4 quat, ProjectionState& st) { | |
| 581 | 650 | st.t = t; | |
| 582 | 651 | const float tz = t.z; | |
| 583 | 652 | const float limx = kFovClamp * cam.TanHalfFovX(); | |
| 584 | 653 | const float limy = kFovClamp * cam.TanHalfFovY(); | |
| 585 | 654 | const float txtz = t.x / tz, tytz = t.y / tz; | |
| 586 | 655 | st.x_clamped = txtz < -limx || txtz > limx; | |
| 587 | 656 | st.y_clamped = tytz < -limy || tytz > limy; | |
| 588 | 657 | st.tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz; | |
| 589 | 658 | st.ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz; | |
| 590 | 659 | const float j00 = cam.fx / tz, j02 = -cam.fx * st.tx_c / (tz * tz); | |
| 591 | 660 | const float j11 = cam.fy / tz, j12 = -cam.fy * st.ty_c / (tz * tz); | |
| 592 | 661 | const Mat3& w = cam.rotation; | |
| 593 | 662 | st.m0 = j00 * w.row(0) + j02 * w.row(2); | |
| 594 | 663 | st.m1 = j11 * w.row(1) + j12 * w.row(2); | |
| 595 | 664 | st.sigma = Covariance3D(QuatToRotation(quat), scale); | |
| 596 | 665 | const float3 s0 = st.sigma.Mul(st.m0), s1 = st.sigma.Mul(st.m1); | |
| 597 | 666 | st.a = dot(st.m0, s0) + kLowPass; | |
| 598 | 667 | st.b = dot(st.m0, s1); | |
| 599 | 668 | st.c = dot(st.m1, s1) + kLowPass; | |
| 600 | 669 | } | |
| 601 | 670 | ||
| 602 | 671 | // Computes everything up to the 2D covariance. Returns false if culled by the | |
| 603 | 672 | // near plane. | |
| 604 | 673 | GS_D bool ProjectCovariance(const Camera& cam, float3 mean, float3 scale, | |
| 605 | 674 | float4 quat, ProjectionState& st) { | |
| 606 | 675 | const float3 t = cam.ToView(mean); | |
| 607 | 676 | if (t.z <= kNearPlane) return false; | |
| 608 | 677 | ProjectCovarianceFromView(cam, t, scale, quat, st); | |
| 609 | 678 | return true; | |
| 610 | 679 | } | |
| 611 | 680 | ||
| 612 | 681 | // Integer bounding radius (pixels) of the 2D covariance, as in the reference. | |
| 613 | 682 | GS_D int BoundingRadius(float a, float b, float c) { | |
| 614 | 683 | const float det = a * c - b * b; | |
| 615 | 684 | const float mid = 0.5f * (a + c); | |
| 616 | 685 | const float lambda = mid + sqrtf(fmaxf(0.1f, mid * mid - det)); | |
| 617 | 686 | return static_cast<int>(ceilf(3.0f * sqrtf(lambda))); | |
| 618 | 687 | } | |
| 619 | 688 | ||
| 620 | 689 | GS_D float2 ProjectMean(const Camera& cam, float3 t) { | |
| 621 | 690 | return {cam.fx * (t.x / t.z) + cam.cx, cam.fy * (t.y / t.z) + cam.cy}; | |
| 622 | 691 | } | |
| 623 | 692 | ||
| 624 | 693 | GS_D float3 LoadMean(const GaussianParams& p, int i) { | |
| 625 | 694 | return {p.means[3 * i], p.means[3 * i + 1], p.means[3 * i + 2]}; | |
| 626 | 695 | } | |
| 627 | 696 | ||
| 628 | 697 | // Unit direction from the camera center to `mean`, and 1 / distance. | |
| 629 | 698 | GS_D float3 ViewDir(const Camera& cam, float3 mean, float& inv_len) { | |
| 630 | 699 | const float3 v = mean - cam.position; | |
| 631 | 700 | inv_len = rsqrtf(dot(v, v)); | |
| 632 | 701 | return inv_len * v; | |
| 633 | 702 | } | |
| 634 | 703 | ||
| 635 | 704 | // Gradient of the loss w.r.t. one splat's screen-space quantities. | |
| 636 | 705 | struct ProjectionGrad { | |
| 637 | 706 | float2 xy; | |
| 638 | 707 | float3 conic; // w.r.t. the scalars (a, b, c) of `Projection::conic`. | |
| 639 | 708 | float opacity; | |
| 640 | 709 | float3 rgb; | |
| 641 | 710 | }; | |
| 642 | 711 | ||
| 643 | 712 | // Image geometry passed by value to every kernel. | |
| 644 | 713 | struct Frame { | |
| 645 | 714 | const float* camera; // Device pointer to the packed camera. | |
| 646 | 715 | int width, height; | |
| 647 | 716 | TileGrid grid; | |
| 648 | 717 | GS_D Camera LoadCamera() const { return Camera::Load(camera, width, height); } | |
| 649 | 718 | }; | |
| 650 | 719 | ||
| 651 | 720 | // --------------------------------------------------------------------------- | |
| 652 | 721 | // Per-Gaussian kernels run one thread per Gaussian. | |
| 653 | 722 | ||
| 654 | 723 | inline constexpr int kGaussianThreads = 128; | |
| 655 | 724 | ||
| 656 | 725 | inline int GaussianBlocks(int64_t n) { | |
| 657 | 726 | return static_cast<int>((n + kGaussianThreads - 1) / kGaussianThreads); | |
| 658 | 727 | } | |
| 659 | 728 | ||
| 660 | 729 | // Base-2 exponent scale: power2 = kPower2Scale * q = (-log2(e)/2) * q. | |
| 661 | 730 | inline constexpr float kPower2Scale = -0.72134752044448170f; // -log2(e)/2 | |
| 662 | 731 | inline constexpr float kInvPower2Scale = 1.0f / kPower2Scale; | |
| 663 | 732 | ||
| 664 | 733 | GS_HD float3 Power2Conic(float a, float b, float c) { | |
| 665 | 734 | return {kPower2Scale * a, 2.0f * kPower2Scale * b, kPower2Scale * c}; | |
| 666 | 735 | } | |
| 667 | 736 | ||
| 668 | 737 | GS_D float Exp2Approx(float x) { | |
| 669 | 738 | float y; | |
| 670 | 739 | asm("ex2.approx.ftz.f32 %0, %1;" : "=f"(y) : "f"(x)); | |
| 671 | 740 | return y; | |
| 672 | 741 | } | |
| 673 | 742 | ||
| 674 | 743 | // --------------------------------------------------------------------------- | |
| 675 | 744 | // Rasterization. One block per 16x16 tile, one pixel per thread, arranged into | |
| 676 | 745 | // eight 8x4 sub-tile warps (`RasterLayout<1>`). Splats are staged through | |
| 677 | 746 | // shared memory in batches of 256 with an 8-bit per-warp sub-tile mask. | |
| 678 | 747 | ||
| 679 | 748 | template <int kRows> | |
| 680 | 749 | struct RasterLayout { | |
| 681 | 750 | static_assert(kRows == 1 || kRows == 2 || kRows == 4); | |
| 682 | 751 | static constexpr int kThreads = kTilePixels / kRows; | |
| 683 | 752 | static constexpr int kWarps = kThreads / 32; | |
| 684 | 753 | static constexpr int kWarpWidth = kRows == 4 ? kTileSize : 8; | |
| 685 | 754 | static constexpr int kWarpRows = (32 / kWarpWidth) * kRows; | |
| 686 | 755 | static constexpr int kWarpsX = kTileSize / kWarpWidth; | |
| 687 | 756 | static_assert(kWarpsX * (kTileSize / kWarpRows) == kWarps); | |
| 688 | 757 | ||
| 689 | 758 | GS_HD static constexpr int WarpX(int w) { return (w % kWarpsX) * kWarpWidth; } | |
| 690 | 759 | GS_HD static constexpr int WarpY(int w) { return (w / kWarpsX) * kWarpRows; } | |
| 691 | 760 | }; | |
| 692 | 761 | ||
| 693 | 762 | struct RasterPixels { | |
| 694 | 763 | int x, y0; | |
| 695 | 764 | template <int kRows> | |
| 696 | 765 | GS_D static RasterPixels For(int tid) { | |
| 697 | 766 | using L = RasterLayout<kRows>; | |
| 698 | 767 | const int warp = tid / 32, lane = tid % 32; | |
| 699 | 768 | return {static_cast<int>(blockIdx.x) * kTileSize + L::WarpX(warp) + | |
| 700 | 769 | lane % L::kWarpWidth, | |
| 701 | 770 | static_cast<int>(blockIdx.y) * kTileSize + L::WarpY(warp) + | |
| 702 | 771 | (lane / L::kWarpWidth) * kRows}; | |
| 703 | 772 | } | |
| 704 | 773 | }; | |
| 705 | 774 | ||
| 706 | 775 | // Bit w is set if splat (p0, p1) can reach alpha >= 1/255 somewhere in the | |
| 707 | 776 | // pixel region of warp w of this block's tile. | |
| 708 | 777 | template <int kRows> | |
| 709 | 778 | GS_D uint32_t WarpMask(float4 p0, float4 p1) { | |
| 710 | 779 | using L = RasterLayout<kRows>; | |
| 711 | 780 | constexpr int kWarpsY = L::kWarps / L::kWarpsX; | |
| 712 | 781 | float limit = p1.z; | |
| 713 | 782 | if (limit <= 0.0f && !AlphaLimit(p1.y, limit)) return 0; | |
| 714 | 783 | const float a = p0.z, b = p0.w, c = p1.x; | |
| 715 | 784 | const float det = a * c - b * b; | |
| 716 | 785 | if (!(det > 0.0f && a > 0.0f && c > 0.0f)) return 0; | |
| 717 | 786 | const float l_over_det = limit / det; | |
| 718 | 787 | const float rx = sqrtf(c * l_over_det); | |
| 719 | 788 | const float ry = sqrtf(a * l_over_det); | |
| 720 | 789 | const float2 xy = {p0.x, p0.y}; | |
| 721 | 790 | const int x0 = static_cast<int>(ceilf(xy.x - rx - kSpanPadPx)); | |
| 722 | 791 | const int x1 = static_cast<int>(floorf(xy.x + rx + kSpanPadPx)); | |
| 723 | 792 | const int y0 = static_cast<int>(ceilf(xy.y - ry - kSpanPadPx)); | |
| 724 | 793 | const int y1 = static_cast<int>(floorf(xy.y + ry + kSpanPadPx)); | |
| 725 | 794 | if (x0 > x1 || y0 > y1) return 0; | |
| 726 | 795 | const int tx = blockIdx.x * kTileSize, ty = blockIdx.y * kTileSize; | |
| 727 | 796 | uint32_t mx = 0u; | |
| 728 | 797 | #pragma unroll | |
| 729 | 798 | for (int wx = 0; wx < L::kWarpsX; ++wx) { | |
| 730 | 799 | const int wx0 = tx + wx * L::kWarpWidth; | |
| 731 | 800 | if (x0 <= wx0 + L::kWarpWidth - 1 && x1 >= wx0) mx |= (1u << wx); | |
| 732 | 801 | } | |
| 733 | 802 | if (mx == 0u) return 0; | |
| 734 | 803 | uint32_t my = 0u; | |
| 735 | 804 | #pragma unroll | |
| 736 | 805 | for (int wy = 0; wy < kWarpsY; ++wy) { | |
| 737 | 806 | const int wy0 = ty + wy * L::kWarpRows; | |
| 738 | 807 | if (y0 <= wy0 + L::kWarpRows - 1 && y1 >= wy0) my |= (1u << wy); | |
| 739 | 808 | } | |
| 740 | 809 | if (my == 0u) return 0; | |
| 741 | 810 | if ((mx & (mx - 1u)) == 0u || (my & (my - 1u)) == 0u) { | |
| 742 | 811 | uint32_t mask = 0u; | |
| 743 | 812 | #pragma unroll | |
| 744 | 813 | for (int wy = 0; wy < kWarpsY; ++wy) { | |
| 745 | 814 | if (!((my >> wy) & 1u)) continue; | |
| 746 | 815 | #pragma unroll | |
| 747 | 816 | for (int wx = 0; wx < L::kWarpsX; ++wx) { | |
| 748 | 817 | if ((mx >> wx) & 1u) mask |= (1u << (wy * L::kWarpsX + wx)); | |
| 749 | 818 | } | |
| 750 | 819 | } | |
| 751 | 820 | return mask; | |
| 752 | 821 | } | |
| 753 | 822 | EllipseBounds eb; | |
| 754 | 823 | eb.a = a; | |
| 755 | 824 | eb.b = b; | |
| 756 | 825 | eb.c = c; | |
| 757 | 826 | eb.det = det; | |
| 758 | 827 | eb.inv_a = 1.0f / a; | |
| 759 | 828 | eb.a_limit = a * limit; | |
| 760 | 829 | eb.y_ext = ry; | |
| 761 | 830 | eb.dy_peak = b * sqrtf(l_over_det / c); | |
| 762 | 831 | uint32_t mask = 0u; | |
| 763 | 832 | #pragma unroll | |
| 764 | 833 | for (int wy = 0; wy < kWarpsY; ++wy) { | |
| 765 | 834 | if (!((my >> wy) & 1u)) continue; | |
| 766 | 835 | const int wy0 = ty + wy * L::kWarpRows; | |
| 767 | 836 | int x0_px, x1_px; | |
| 768 | 837 | if (!eb.SpanForPixelRows(xy, wy0, wy0 + L::kWarpRows - 1, x0_px, x1_px)) { | |
| 769 | 838 | continue; | |
| 770 | 839 | } | |
| 771 | 840 | #pragma unroll | |
| 772 | 841 | for (int wx = 0; wx < L::kWarpsX; ++wx) { | |
| 773 | 842 | const int wx0 = tx + wx * L::kWarpWidth; | |
| 774 | 843 | if (x0_px <= wx0 + L::kWarpWidth - 1 && x1_px >= wx0) { | |
| 775 | 844 | mask |= 1u << (wy * L::kWarpsX + wx); | |
| 776 | 845 | } | |
| 777 | 846 | } | |
| 778 | 847 | } | |
| 779 | 848 | return mask; | |
| 780 | 849 | } | |
| 781 | 850 | ||
| 782 | 851 | } // namespace gs | |
| 783 | 852 | ||
| 784 | 853 | #endif | |
s01_literature/csrc/backward.cu.ccs02_reduce_scatter/csrc/backward.cu.cc Expand 167 unchanged lines (lines 1–167) · in ProjectVjp | |||
| 1 | /* | 1 | /* |
| 2 | * Copyright (C) 2023, Inria | 2 | * Copyright (C) 2023, Inria |
| 3 | * GRAPHDECO research group, https://team.inria.fr/graphdeco | 3 | * 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 use | 6 | * 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.fr | 9 | * For inquiries contact george.drettakis@inria.fr |
| 10 | */ | 10 | */ |
| 11 | 11 | ||
| 12 | #include "backward.h" | 12 | #include "backward.h" |
| 13 | 13 | ||
| 14 | #include "auxiliary.h" | 14 | #include "auxiliary.h" |
| 15 | 15 | ||
| 16 | namespace gs { | 16 | namespace gs { |
| 17 | 17 | ||
| 18 | namespace sh { | 18 | namespace sh { |
| 19 | // Evaluates SH color VJP loading the full 16-basis row `sh_in` and storing | 19 | // Evaluates SH color VJP loading the full 16-basis row `sh_in` and storing |
| 20 | // `dsh` to `sh_out`. | 20 | // `dsh` to `sh_out`. |
| 21 | template <typename ShT> | 21 | template <typename ShT> |
| 22 | GS_D float3 VjpRow(float3 d, float3 g_rgb, const ShT* __restrict__ sh_in, | 22 | GS_D float3 VjpRow(float3 d, float3 g_rgb, const ShT* __restrict__ sh_in, |
| 23 | ShT* __restrict__ sh_out) { | 23 | ShT* __restrict__ sh_out) { |
| 24 | float b[kShCoeffs]; | 24 | float b[kShCoeffs]; |
| 25 | Basis(d, b); | 25 | Basis(d, b); |
| 26 | float3 coeffs[kShCoeffs]; | 26 | float3 coeffs[kShCoeffs]; |
| 27 | ShIo<ShT>::LoadRow(sh_in, coeffs); | 27 | ShIo<ShT>::LoadRow(sh_in, coeffs); |
| 28 | float3 raw = {0.5f, 0.5f, 0.5f}; | 28 | float3 raw = {0.5f, 0.5f, 0.5f}; |
| 29 | #pragma unroll | 29 | #pragma unroll |
| 30 | for (int k = 0; k < kShCoeffs; ++k) { | 30 | for (int k = 0; k < kShCoeffs; ++k) { |
| 31 | raw += b[k] * coeffs[k]; | 31 | raw += b[k] * coeffs[k]; |
| 32 | } | 32 | } |
| 33 | const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x, | 33 | const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x, |
| 34 | raw.y < 0.0f ? 0.0f : g_rgb.y, | 34 | raw.y < 0.0f ? 0.0f : g_rgb.y, |
| 35 | raw.z < 0.0f ? 0.0f : g_rgb.z}; | 35 | raw.z < 0.0f ? 0.0f : g_rgb.z}; |
| 36 | float3 bgrad[kShCoeffs]; | 36 | float3 bgrad[kShCoeffs]; |
| 37 | BasisGrad(d, bgrad); | 37 | BasisGrad(d, bgrad); |
| 38 | float3 dsh[kShCoeffs]; | 38 | float3 dsh[kShCoeffs]; |
| 39 | float3 ddir = {0.0f, 0.0f, 0.0f}; | 39 | float3 ddir = {0.0f, 0.0f, 0.0f}; |
| 40 | #pragma unroll | 40 | #pragma unroll |
| 41 | for (int k = 0; k < kShCoeffs; ++k) { | 41 | for (int k = 0; k < kShCoeffs; ++k) { |
| 42 | dsh[k] = b[k] * drgb; | 42 | dsh[k] = b[k] * drgb; |
| 43 | const float s = dot(coeffs[k], drgb); | 43 | const float s = dot(coeffs[k], drgb); |
| 44 | ddir += s * bgrad[k]; | 44 | ddir += s * bgrad[k]; |
| 45 | } | 45 | } |
| 46 | ShIo<ShT>::StoreRow(sh_out, dsh); | 46 | ShIo<ShT>::StoreRow(sh_out, dsh); |
| 47 | return ddir; | 47 | return ddir; |
| 48 | } | 48 | } |
| 49 | } // namespace sh | 49 | } // namespace sh |
| 50 | 50 | ||
| 51 | // VJP of the projection for a touched Gaussian `i`; reads its SH row from | 51 | // VJP of the projection for a touched Gaussian `i`; reads its SH row from |
| 52 | // `sh_in` and writes dL/dsh directly to `sh_out`. | 52 | // `sh_in` and writes dL/dsh directly to `sh_out`. |
| 53 | // Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`. | 53 | // Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`. |
| 54 | template <typename ShT> | 54 | template <typename ShT> |
| 55 | GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i, | 55 | GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i, |
| 56 | const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out, | 56 | const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out, |
| 57 | const ProjectionGrad& g, float3& dmean_out, | 57 | const ProjectionGrad& g, float3& dmean_out, |
| 58 | float3& dscale_out, float4& dquat_out) { | 58 | float3& dscale_out, float4& dquat_out) { |
| 59 | const float3 mean = LoadMean(p, i); | 59 | const float3 mean = LoadMean(p, i); |
| 60 | const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1], | 60 | const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1], |
| 61 | p.scales[3 * i + 2]}; | 61 | p.scales[3 * i + 2]}; |
| 62 | const float4 quat = reinterpret_cast<const float4*>(p.quats)[i]; | 62 | const float4 quat = reinterpret_cast<const float4*>(p.quats)[i]; |
| 63 | 63 | ||
| 64 | // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0). | 64 | // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0). |
| 65 | float3 dmean = {0.0f, 0.0f, 0.0f}; | 65 | float3 dmean = {0.0f, 0.0f, 0.0f}; |
| 66 | { | 66 | { |
| 67 | float inv_len; | 67 | float inv_len; |
| 68 | const float3 dir = ViewDir(cam, mean, inv_len); | 68 | const float3 dir = ViewDir(cam, mean, inv_len); |
| 69 | const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out); | 69 | const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out); |
| 70 | // d(v/|v|)/dv = (I - dir dir^T) / |v|. | 70 | // d(v/|v|)/dv = (I - dir dir^T) / |v|. |
| 71 | dmean += inv_len * (ddir - dot(dir, ddir) * dir); | 71 | dmean += inv_len * (ddir - dot(dir, ddir) * dir); |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | // --- Forward covariance intermediates (reusing R, s0, s1 in VJP). | 74 | // --- Forward covariance intermediates (reusing R, s0, s1 in VJP). |
| 75 | const float3 t = cam.ToView(mean); | 75 | const float3 t = cam.ToView(mean); |
| 76 | const float tz = t.z; | 76 | const float tz = t.z; |
| 77 | const float limx = kFovClamp * cam.TanHalfFovX(); | 77 | const float limx = kFovClamp * cam.TanHalfFovX(); |
| 78 | const float limy = kFovClamp * cam.TanHalfFovY(); | 78 | const float limy = kFovClamp * cam.TanHalfFovY(); |
| 79 | const float txtz = t.x / tz, tytz = t.y / tz; | 79 | const float txtz = t.x / tz, tytz = t.y / tz; |
| 80 | const bool x_clamped = txtz < -limx || txtz > limx; | 80 | const bool x_clamped = txtz < -limx || txtz > limx; |
| 81 | const bool y_clamped = tytz < -limy || tytz > limy; | 81 | const bool y_clamped = tytz < -limy || tytz > limy; |
| 82 | const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz; | 82 | const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz; |
| 83 | const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz; | 83 | const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz; |
| 84 | const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz); | 84 | const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz); |
| 85 | const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz); | 85 | const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz); |
| 86 | const Mat3& w = cam.rotation; | 86 | const Mat3& w = cam.rotation; |
| 87 | const float3 m0 = j00 * w.row(0) + j02 * w.row(2); | 87 | const float3 m0 = j00 * w.row(0) + j02 * w.row(2); |
| 88 | const float3 m1 = j11 * w.row(1) + j12 * w.row(2); | 88 | const float3 m1 = j11 * w.row(1) + j12 * w.row(2); |
| 89 | 89 | ||
| 90 | const Mat3 r = QuatToRotation(quat); | 90 | const Mat3 r = QuatToRotation(quat); |
| 91 | const Sym3 sigma = Covariance3D(r, scale); | 91 | const Sym3 sigma = Covariance3D(r, scale); |
| 92 | const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1); | 92 | const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1); |
| 93 | const float cov_a = dot(m0, s0) + kLowPass; | 93 | const float cov_a = dot(m0, s0) + kLowPass; |
| 94 | const float cov_b = dot(m0, s1); | 94 | const float cov_b = dot(m0, s1); |
| 95 | const float cov_c = dot(m1, s1) + kLowPass; | 95 | const float cov_c = dot(m1, s1) + kLowPass; |
| 96 | 96 | ||
| 97 | // --- Conic -> 2D covariance: G_cov = -K G_K K. | 97 | // --- Conic -> 2D covariance: G_cov = -K G_K K. |
| 98 | const float det = cov_a * cov_c - cov_b * cov_b; | 98 | const float det = cov_a * cov_c - cov_b * cov_b; |
| 99 | const float inv = 1.0f / det; | 99 | const float inv = 1.0f / det; |
| 100 | const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv; | 100 | const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv; |
| 101 | const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z; | 101 | const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z; |
| 102 | // P = G_K K. | 102 | // P = G_K K. |
| 103 | const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc; | 103 | const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc; |
| 104 | const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc; | 104 | const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc; |
| 105 | // G_cov = -K P (symmetric). | 105 | // G_cov = -K P (symmetric). |
| 106 | const float ca = -(ka * p00 + kb * p10); | 106 | const float ca = -(ka * p00 + kb * p10); |
| 107 | const float cb = -(ka * p01 + kb * p11); | 107 | const float cb = -(ka * p01 + kb * p11); |
| 108 | const float cc = -(kb * p01 + kc * p11); | 108 | const float cc = -(kb * p01 + kc * p11); |
| 109 | 109 | ||
| 110 | // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M. | 110 | // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M. |
| 111 | const float3 dm0 = 2.0f * (ca * s0 + cb * s1); | 111 | const float3 dm0 = 2.0f * (ca * s0 + cb * s1); |
| 112 | const float3 dm1 = 2.0f * (cb * s0 + cc * s1); | 112 | const float3 dm1 = 2.0f * (cb * s0 + cc * s1); |
| 113 | // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T. | 113 | // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T. |
| 114 | Sym3 dsig; | 114 | Sym3 dsig; |
| 115 | dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x; | 115 | dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x; |
| 116 | dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y; | 116 | dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y; |
| 117 | dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z; | 117 | dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z; |
| 118 | dsig.xy = | 118 | dsig.xy = |
| 119 | ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y; | 119 | ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y; |
| 120 | dsig.xz = | 120 | dsig.xz = |
| 121 | ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z; | 121 | ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z; |
| 122 | dsig.yz = | 122 | dsig.yz = |
| 123 | ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z; | 123 | ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z; |
| 124 | 124 | ||
| 125 | // --- M = J W: dJ = dM W^T (only the nonzero entries of J). | 125 | // --- M = J W: dJ = dM W^T (only the nonzero entries of J). |
| 126 | const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2)); | 126 | const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2)); |
| 127 | const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2)); | 127 | const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2)); |
| 128 | const float tz2 = tz * tz, tz3 = tz2 * tz; | 128 | const float tz2 = tz * tz, tz3 = tz2 * tz; |
| 129 | float3 dt; | 129 | float3 dt; |
| 130 | dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02; | 130 | dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02; |
| 131 | dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12; | 131 | dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12; |
| 132 | dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 + | 132 | dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 + |
| 133 | 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12; | 133 | 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12; |
| 134 | // --- Screen-space mean u = fx x / z + cx. | 134 | // --- Screen-space mean u = fx x / z + cx. |
| 135 | dt.x += cam.fx / tz * g.xy.x; | 135 | dt.x += cam.fx / tz * g.xy.x; |
| 136 | dt.y += cam.fy / tz * g.xy.y; | 136 | dt.y += cam.fy / tz * g.xy.y; |
| 137 | dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2; | 137 | dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2; |
| 138 | dmean += MulT(w, dt); | 138 | dmean += MulT(w, dt); |
| 139 | dmean_out = dmean; | 139 | dmean_out = dmean; |
| 140 | 140 | ||
| 141 | // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A. | 141 | // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A. |
| 142 | const float sv[3] = {scale.x, scale.y, scale.z}; | 142 | const float sv[3] = {scale.x, scale.y, scale.z}; |
| 143 | float ds[3]; | 143 | float ds[3]; |
| 144 | Mat3 dr; | 144 | Mat3 dr; |
| 145 | #pragma unroll | 145 | #pragma unroll |
| 146 | for (int j = 0; j < 3; ++j) { | 146 | for (int j = 0; j < 3; ++j) { |
| 147 | const float3 r_col = r.col(j); | 147 | const float3 r_col = r.col(j); |
| 148 | const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col); | 148 | const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col); |
| 149 | ds[j] = dot(da_col, r_col); | 149 | ds[j] = dot(da_col, r_col); |
| 150 | dr.m[0][j] = da_col.x * sv[j]; | 150 | dr.m[0][j] = da_col.x * sv[j]; |
| 151 | dr.m[1][j] = da_col.y * sv[j]; | 151 | dr.m[1][j] = da_col.y * sv[j]; |
| 152 | dr.m[2][j] = da_col.z * sv[j]; | 152 | dr.m[2][j] = da_col.z * sv[j]; |
| 153 | } | 153 | } |
| 154 | dscale_out = {ds[0], ds[1], ds[2]}; | 154 | dscale_out = {ds[0], ds[1], ds[2]}; |
| 155 | 155 | ||
| 156 | // --- R(q) -> q. | 156 | // --- R(q) -> q. |
| 157 | const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w; | 157 | const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w; |
| 158 | const auto& G = dr.m; | 158 | const auto& G = dr.m; |
| 159 | dquat_out = { | 159 | dquat_out = { |
| 160 | 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] - | 160 | 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] - |
| 161 | qy * G[2][0] + qx * G[2][1]), | 161 | qy * G[2][0] + qx * G[2][1]), |
| 162 | 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] - | 162 | 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] - |
| 163 | qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]), | 163 | qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]), |
| 164 | 2.0f * | 164 | 2.0f * |
| 165 | (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] + | 165 | (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] + |
| 166 | qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]), | 166 | qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]), |
| 167 | 2.0f * | 167 | 2.0f * |
| 168 | (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] - | 168 | (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] - |
| 169 | 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])}; | 169 | 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])}; |
| 170 | } | 170 | } |
| 171 | 171 | ||
| 172 | // Reverse traversal per pixel; accumulates splat gradients with standard | 172 | // Reverse traversal per pixel; accumulates splat gradients via Half-Warp |
| 173 | // 5-step full-warp butterfly reductions (45 shuffles) and 9 lane-0 atomics. | ||
| 173 | // Two-Splat Folding and Halving Register Reduce-Scatter. | ||
| 174 | __global__ void __launch_bounds__(RasterLayout<1>::kThreads, 6) | 174 | __global__ void __launch_bounds__(RasterLayout<1>::kThreads, 4) |
| 175 | RasterBackwardKernel(const int2* __restrict__ ranges, | 175 | RasterBackwardKernel(const int2* __restrict__ ranges, |
| 176 | const int32_t* __restrict__ point_list, | 176 | const int32_t* __restrict__ point_list, |
| 177 | const Splat* __restrict__ splats, Frame frame, | 177 | const Splat* __restrict__ splats, Frame frame, |
| 178 | const float* __restrict__ final_t, | 178 | const float* __restrict__ final_t, |
Expand 44 unchanged lines (lines 179–222) · in RasterBackwardKernel | |||
| 179 | const int32_t* __restrict__ n_contrib, | 179 | const int32_t* __restrict__ n_contrib, |
| 180 | const float* __restrict__ dl_dimage, | 180 | const float* __restrict__ dl_dimage, |
| 181 | float* __restrict__ splat_grads) { | 181 | float* __restrict__ splat_grads) { |
| 182 | using L = RasterLayout<1>; | 182 | using L = RasterLayout<1>; |
| 183 | constexpr int kRasterThreads = L::kThreads; | 183 | constexpr int kRasterThreads = L::kThreads; |
| 184 | constexpr int kWarps = L::kWarps; | 184 | constexpr int kWarps = L::kWarps; |
| 185 | const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x; | 185 | const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x; |
| 186 | const int tid = threadIdx.x; | 186 | const int tid = threadIdx.x; |
| 187 | const int warp = tid / 32; | 187 | const int warp = tid / 32; |
| 188 | const int lane = tid % 32; | 188 | const int lane = tid % 32; |
| 189 | const RasterPixels px = RasterPixels::For<1>(tid); | 189 | const RasterPixels px = RasterPixels::For<1>(tid); |
| 190 | const float fx = static_cast<float>(px.x); | 190 | const float fx = static_cast<float>(px.x); |
| 191 | const float fy = static_cast<float>(px.y0); | 191 | const float fy = static_cast<float>(px.y0); |
| 192 | const int2 range = ranges[tile]; | 192 | const int2 range = ranges[tile]; |
| 193 | 193 | ||
| 194 | const bool inside = px.x < frame.width && px.y0 < frame.height; | 194 | const bool inside = px.x < frame.width && px.y0 < frame.height; |
| 195 | const int pixels = frame.width * frame.height; | 195 | const int pixels = frame.width * frame.height; |
| 196 | const int p = px.y0 * frame.width + px.x; | 196 | const int p = px.y0 * frame.width + px.x; |
| 197 | float t = inside ? final_t[p] : 0.0f; | 197 | float t = inside ? final_t[p] : 0.0f; |
| 198 | const float3 dpix = | 198 | const float3 dpix = |
| 199 | inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p], | 199 | inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p], |
| 200 | dl_dimage[2 * pixels + p]} | 200 | dl_dimage[2 * pixels + p]} |
| 201 | : float3{0.0f, 0.0f, 0.0f}; | 201 | : float3{0.0f, 0.0f, 0.0f}; |
| 202 | float accum = 0.0f; // dot(color accumulated behind, dpix) | 202 | float accum = 0.0f; // dot(color accumulated behind, dpix) |
| 203 | const int last = inside ? n_contrib[p] : 0; | 203 | const int last = inside ? n_contrib[p] : 0; |
| 204 | 204 | ||
| 205 | int warp_max_last = last; | 205 | int warp_max_last = last; |
| 206 | #pragma unroll | 206 | #pragma unroll |
| 207 | for (int offset = 16; offset > 0; offset /= 2) { | 207 | for (int offset = 16; offset > 0; offset /= 2) { |
| 208 | warp_max_last = max( | 208 | warp_max_last = max( |
| 209 | warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset)); | 209 | warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset)); |
| 210 | } | 210 | } |
| 211 | __shared__ int shared_max_last[kWarps]; | 211 | __shared__ int shared_max_last[kWarps]; |
| 212 | if (lane == 0) shared_max_last[warp] = warp_max_last; | 212 | if (lane == 0) shared_max_last[warp] = warp_max_last; |
| 213 | __syncthreads(); | 213 | __syncthreads(); |
| 214 | int block_max_last = 0; | 214 | int block_max_last = 0; |
| 215 | #pragma unroll | 215 | #pragma unroll |
| 216 | for (int w = 0; w < kWarps; ++w) { | 216 | for (int w = 0; w < kWarps; ++w) { |
| 217 | block_max_last = max(block_max_last, shared_max_last[w]); | 217 | block_max_last = max(block_max_last, shared_max_last[w]); |
| 218 | } | 218 | } |
| 219 | if (block_max_last <= 0) return; | 219 | if (block_max_last <= 0) return; |
| 220 | 220 | ||
| 221 | __shared__ float4 batch_geom0[kRasterThreads]; | 221 | __shared__ float4 batch_geom0[kRasterThreads]; |
| 222 | __shared__ float2 batch_geom1[kRasterThreads]; | 222 | __shared__ float2 batch_geom1[kRasterThreads]; |
| 223 | __shared__ float4 batch_rgb_o[kRasterThreads]; | 223 | __shared__ float4 batch_rgb_o[kRasterThreads]; |
| 224 | __shared__ uint32_t batch_id[kRasterThreads]; | 224 | __shared__ uint32_t batch_id[kRasterThreads]; |
| 225 | __shared__ uint8_t batch_mask[kRasterThreads]; | 225 | __shared__ uint8_t batch_mask[kRasterThreads]; |
| 226 | const uint8_t warp_bit = static_cast<uint8_t>(1u << warp); | 226 | const uint8_t warp_bit = static_cast<uint8_t>(1u << warp); |
| 227 | int scatter = -1; | ||
| 228 | int scatter2 = -1; | ||
| 229 | { | ||
| 230 | const int sub_lane = lane & 15; | ||
| 231 | const int row = sub_lane >> 3, col = sub_lane & 7; | ||
| 232 | if (col == 0) { | ||
| 233 | scatter2 = row; | ||
| 234 | } else if (col == 2) { | ||
| 235 | scatter2 = 2 + row; | ||
| 236 | } else if (col == 4) { | ||
| 237 | scatter2 = row == 0 ? 4 : 7; | ||
| 238 | } else if (col == 6) { | ||
| 239 | scatter2 = row == 0 ? 6 : 8; | ||
| 240 | } else if (col == 1 && row == 0) { | ||
| 241 | scatter2 = 5; | ||
| 242 | } | ||
| 243 | const int full_row = lane >> 3, full_col = lane & 7; | ||
| 244 | if (full_col == 0) { | ||
| 245 | scatter = full_row; | ||
| 246 | } else if (full_col == 2) { | ||
| 247 | scatter = full_row == 0 ? 4 : 5 + full_row; | ||
| 248 | } else if (full_col == 4 && full_row == 0) { | ||
| 249 | scatter = 5; | ||
| 250 | } | ||
| 251 | } | ||
| 227 | 252 | ||
| 253 | auto eval_splat = [&](float4 g0, float2 g1, float4 ro, int entry, float& dx, | ||
| 254 | float& ga_sum, float& ga_dy, float& ga_dy2, | ||
| 255 | float3& drgb) -> bool { | ||
| 256 | dx = g0.x - fx; | ||
| 257 | ga_sum = 0.0f; | ||
| 258 | ga_dy = 0.0f; | ||
| 259 | ga_dy2 = 0.0f; | ||
| 260 | drgb = {0.0f, 0.0f, 0.0f}; | ||
| 261 | if (entry < last) { | ||
| 262 | const float dy = g0.y - fy; | ||
| 263 | const float power = g0.z * dx * dx + g1.x * dy * dy + g0.w * dx * dy; | ||
| 264 | if (power <= 0.0f && power >= g1.y) { | ||
| 265 | const float g_val = Exp2Approx(power); | ||
| 266 | const float alpha = fminf(kMaxAlpha, ro.w * g_val); | ||
| 267 | if (alpha >= kMinAlpha) { | ||
| 268 | t = __fdividef(t, 1.0f - alpha); | ||
| 269 | const float w = alpha * t; | ||
| 270 | const float color_dot = ro.x * dpix.x + ro.y * dpix.y + ro.z * dpix.z; | ||
| 271 | const float diff = color_dot - accum; | ||
| 272 | const float dl_dalpha = t * diff; | ||
| 273 | accum += alpha * diff; | ||
| 274 | const float ga = g_val * dl_dalpha; | ||
| 275 | ga_sum = ga; | ||
| 276 | const float ga_y = ga * dy; | ||
| 277 | ga_dy = ga_y; | ||
| 278 | ga_dy2 = ga_y * dy; | ||
| 279 | drgb = w * dpix; | ||
| 280 | return true; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | } | ||
| 284 | return false; | ||
| 285 | }; | ||
| 228 | auto raw_conic = [](float4 g0, float2 g1) { | 286 | auto raw_conic = [](float4 g0, float2 g1) { |
| 229 | return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w, | 287 | return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w, |
| 230 | kInvPower2Scale * g1.x}; | 288 | kInvPower2Scale * g1.x}; |
| 231 | }; | 289 | }; |
Expand 21 unchanged lines (lines 290–310) · in RasterBackwardKernel | |||
| 232 | 290 | ||
| 233 | for (int end = block_max_last; end > 0; end -= kRasterThreads) { | 291 | for (int end = block_max_last; end > 0; end -= kRasterThreads) { |
| 234 | const int e = end - 1 - tid; | 292 | const int e = end - 1 - tid; |
| 235 | if (e >= 0) { | 293 | if (e >= 0) { |
| 236 | const uint32_t id = static_cast<uint32_t>(point_list[range.x + e]); | 294 | const uint32_t id = static_cast<uint32_t>(point_list[range.x + e]); |
| 237 | const float4 p0 = __ldg(&splats[id].p0); | 295 | const float4 p0 = __ldg(&splats[id].p0); |
| 238 | const float4 p1 = __ldg(&splats[id].p1); | 296 | const float4 p1 = __ldg(&splats[id].p1); |
| 239 | const float4 p2 = __ldg(&splats[id].p2); | 297 | const float4 p2 = __ldg(&splats[id].p2); |
| 240 | const float3 c2 = Power2Conic(p0.z, p0.w, p1.x); | 298 | const float3 c2 = Power2Conic(p0.z, p0.w, p1.x); |
| 241 | batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y}; | 299 | batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y}; |
| 242 | batch_geom1[tid] = {c2.z, p2.w}; | 300 | batch_geom1[tid] = {c2.z, p2.w}; |
| 243 | batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y}; | 301 | batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y}; |
| 244 | batch_id[tid] = id; | 302 | batch_id[tid] = id; |
| 245 | batch_mask[tid] = static_cast<uint8_t>(WarpMask<1>(p0, p1)); | 303 | batch_mask[tid] = static_cast<uint8_t>(WarpMask<1>(p0, p1)); |
| 246 | } else { | 304 | } else { |
| 247 | batch_mask[tid] = 0u; | 305 | batch_mask[tid] = 0u; |
| 248 | } | 306 | } |
| 249 | __syncthreads(); | 307 | __syncthreads(); |
| 250 | const int count = min(kRasterThreads, end); | 308 | const int count = min(kRasterThreads, end); |
| 251 | #pragma unroll | 309 | #pragma unroll |
| 252 | for (int c = 0; c < kRasterThreads; c += 32) { | 310 | for (int c = 0; c < kRasterThreads; c += 32) { |
| 253 | if (c < count && end - 32 - c < warp_max_last) { | 311 | if (c < count && end - 32 - c < warp_max_last) { |
| 254 | uint32_t mask = __ballot_sync( | 312 | uint32_t mask = __ballot_sync( |
| 255 | warp::kFullMask, | 313 | warp::kFullMask, |
| 256 | c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u); | 314 | c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u); |
| 315 | while ((mask & (mask - 1u)) != 0u) { | ||
| 316 | const int bit0 = __ffs(mask) - 1; | ||
| 317 | mask &= mask - 1u; | ||
| 318 | const int bit1 = __ffs(mask) - 1; | ||
| 319 | mask &= mask - 1u; | ||
| 320 | const int j0 = c + bit0, j1 = c + bit1; | ||
| 321 | const float4 g0_0 = batch_geom0[j0]; | ||
| 322 | const float2 g1_0 = batch_geom1[j0]; | ||
| 323 | const float4 ro_0 = batch_rgb_o[j0]; | ||
| 324 | const float4 g0_1 = batch_geom0[j1]; | ||
| 325 | const float2 g1_1 = batch_geom1[j1]; | ||
| 326 | const float4 ro_1 = batch_rgb_o[j1]; | ||
| 327 | |||
| 328 | float dx0, ga_sum0, ga_dy0, ga_dy2_0; | ||
| 329 | float3 drgb0; | ||
| 330 | const bool hit0 = eval_splat(g0_0, g1_0, ro_0, end - 1 - j0, dx0, | ||
| 331 | ga_sum0, ga_dy0, ga_dy2_0, drgb0); | ||
| 332 | |||
| 333 | float dx1, ga_sum1, ga_dy1, ga_dy2_1; | ||
| 334 | float3 drgb1; | ||
| 335 | const bool hit1 = eval_splat(g0_1, g1_1, ro_1, end - 1 - j1, dx1, | ||
| 336 | ga_sum1, ga_dy1, ga_dy2_1, drgb1); | ||
| 337 | // As in s01: a pair that no pixel of the warp blends has zero | ||
| 338 | // gradients, so skip its reduction and atomics entirely. | ||
| 339 | if (!__any_sync(warp::kFullMask, hit0 || hit1)) continue; | ||
| 340 | |||
| 341 | const bool hi16 = (lane & 16) != 0; | ||
| 342 | const float opacity = hi16 ? ro_1.w : ro_0.w; | ||
| 343 | const float dx = hi16 ? dx1 : dx0; | ||
| 344 | const float3 cn = | ||
| 345 | hi16 ? raw_conic(g0_1, g1_1) : raw_conic(g0_0, g1_0); | ||
| 346 | |||
| 347 | auto fold16 = [&](float v0, float v1) { | ||
| 348 | const float send = hi16 ? v0 : v1; | ||
| 349 | const float recv = __shfl_xor_sync(warp::kFullMask, send, 16); | ||
| 350 | return (hi16 ? v1 : v0) + recv; | ||
| 351 | }; | ||
| 352 | float ga_sum = fold16(ga_sum0, ga_sum1); | ||
| 353 | float ga_dy = fold16(ga_dy0, ga_dy1); | ||
| 354 | float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1), | ||
| 355 | fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y), | ||
| 356 | fold16(drgb0.z, drgb1.z)}; | ||
| 357 | warp::internal::HalvingStep<4, 8>(b4, lane); | ||
| 358 | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8); | ||
| 359 | ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8); | ||
| 360 | |||
| 361 | const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy; | ||
| 362 | const float p_dx = dx * p_sum; | ||
| 363 | const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy) | ||
| 364 | : -(cn.y * p_dx + cn.z * p_dy); | ||
| 365 | const float v_dx1 = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy; | ||
| 366 | float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]}; | ||
| 367 | warp::internal::HalvingStep<4, 4>(v4, lane); | ||
| 368 | warp::internal::HalvingStep<2, 2>(v4, lane); | ||
| 369 | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4); | ||
| 370 | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2); | ||
| 371 | const float v_sum = | ||
| 372 | v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1); | ||
| 373 | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1); | ||
| 374 | const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum; | ||
| 375 | if (scatter2 >= 0 && out_grad != 0.0f) { | ||
| 376 | const uint32_t target_id = batch_id[hi16 ? j1 : j0]; | ||
| 377 | atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2, | ||
| 378 | out_grad); | ||
| 379 | } | ||
| 380 | } | ||
| 257 | while (mask != 0u) { | 381 | while (mask != 0u) { |
| 258 | const int bit = __ffs(mask) - 1; | 382 | const int bit = __ffs(mask) - 1; |
| 259 | mask &= mask - 1u; | 383 | mask &= mask - 1u; |
| 260 | const int j = c + bit; | 384 | const int j = c + bit; |
| 261 | const int entry = end - 1 - j; | ||
| 262 | float gr[kSplatGradFloats] = {}; | ||
| 263 | bool hit = false; | ||
| 264 | if (entry < last) { | ||
| 265 | const float4 g0 = batch_geom0[j]; | 385 | const float4 g0 = batch_geom0[j]; |
| 266 | const float2 g1 = batch_geom1[j]; | 386 | const float2 g1 = batch_geom1[j]; |
| 267 | const float dx = g0.x - fx; | ||
| 268 | const float dy = g0.y - fy; | ||
| 269 | const float power = | ||
| 270 | g0.z * dx * dx + g1.x * dy * dy + g0.w * dx * dy; | ||
| 271 | if (power <= 0.0f && power >= g1.y) { | ||
| 272 | const float4 ro = batch_rgb_o[j]; | 387 | const float4 ro = batch_rgb_o[j]; |
| 273 | const float g_val = Exp2Approx(power); | ||
| 274 | const float alpha = fminf(kMaxAlpha, ro.w * g_val); | ||
| 275 | if (alpha >= kMinAlpha) { | ||
| 276 | hit = true; | ||
| 277 | t = __fdividef(t, 1.0f - alpha); | ||
| 278 | const float w = alpha * t; | ||
| 279 | const float color_dot = | ||
| 280 | ro.x * dpix.x + ro.y * dpix.y + ro.z * dpix.z; | ||
| 281 | const float diff = color_dot - accum; | ||
| 282 | const float dl_dalpha = t * diff; | ||
| 283 | accum += alpha * diff; | ||
| 284 | const float ga = g_val * dl_dalpha; | 388 | float dx, ga_sum, ga_dy, ga_dy2; |
| 285 | const float dl_dpower = ro.w * ga; | 389 | float3 drgb; |
| 286 | const float p_dx = dx * dl_dpower; | ||
| 390 | const bool hit = | ||
| 391 | eval_splat(g0, g1, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2, drgb); | ||
| 392 | if (!__any_sync(warp::kFullMask, hit)) continue; | ||
| 287 | const float p_dy = dy * dl_dpower; | 393 | const float opacity = ro.w; |
| 288 | const float3 cn = raw_conic(g0, g1); | 394 | const float3 cn = raw_conic(g0, g1); |
| 395 | float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z}; | ||
| 396 | warp::internal::HalvingStep<4, 16>(b4, lane); | ||
| 397 | warp::internal::HalvingStep<2, 8>(b4, lane); | ||
| 398 | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16); | ||
| 399 | ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16); | ||
| 400 | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8); | ||
| 401 | ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8); | ||
| 402 | const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy; | ||
| 403 | const float p_dx = dx * p_sum; | ||
| 404 | float v_dx; | ||
| 405 | if ((lane & 16) == 0) { | ||
| 289 | gr[0] = -(cn.x * p_dx + cn.y * p_dy); | 406 | v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy) |
| 290 | gr[1] = -(cn.y * p_dx + cn.z * p_dy); | 407 | : -(cn.y * p_dx + cn.z * p_dy); |
| 408 | } else { | ||
| 291 | gr[2] = -0.5f * dx * p_dx; | 409 | v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy; |
| 292 | gr[3] = -dx * p_dy; | ||
| 293 | gr[4] = -0.5f * dy * p_dy; | ||
| 294 | gr[5] = ga; | ||
| 295 | gr[6] = w * dpix.x; | ||
| 296 | gr[7] = w * dpix.y; | ||
| 297 | gr[8] = w * dpix.z; | ||
| 298 | } | 410 | } |
| 299 | } | ||
| 300 | } | ||
| 411 | float v[3] = {v_dx, b4[0], ga_sum}; | ||
| 412 | warp::internal::HalvingStep<3, 4>(v, lane); | ||
| 413 | warp::internal::HalvingStep<2, 2>(v, lane); | ||
| 414 | const float out_grad = | ||
| 301 | if (!__any_sync(warp::kFullMask, hit)) continue; | 415 | v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1); |
| 302 | warp::ButterflyReduce(gr); | ||
| 303 | if (lane == 0) { | 416 | if (scatter >= 0 && out_grad != 0.0f) { |
| 304 | float* dst = splat_grads + kSplatGradFloats * batch_id[j]; | 417 | atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter, |
| 305 | #pragma unroll | ||
| 306 | for (int q = 0; q < kSplatGradFloats; ++q) { | ||
| 307 | atomicAdd(dst + q, gr[q]); | ||
| 308 | } | ||
| 418 | out_grad); | ||
| 309 | } | 419 | } |
| 310 | } | 420 | } |
| 311 | } | 421 | } |
| 312 | } | 422 | } |
Expand 91 unchanged lines (lines 423–513) · in PreprocessBackwardKernel | |||
| 313 | __syncthreads(); | 423 | __syncthreads(); |
| 314 | } | 424 | } |
| 315 | } | 425 | } |
| 316 | 426 | ||
| 317 | // --------------------------------------------------------------------------- | 427 | // --------------------------------------------------------------------------- |
| 318 | // Preprocess backward: splat gradients -> parameter gradients. | 428 | // Preprocess backward: splat gradients -> parameter gradients. |
| 319 | // Parameter gradient buffers are bulk-zeroed beforehand via `cudaMemsetAsync`; | 429 | // Parameter gradient buffers are bulk-zeroed beforehand via `cudaMemsetAsync`; |
| 320 | // culled or zero-gradient Gaussians return early. | 430 | // culled or zero-gradient Gaussians return early. |
| 321 | 431 | ||
| 322 | template <typename ShT> | 432 | template <typename ShT> |
| 323 | __global__ void __launch_bounds__(kGaussianThreads) | 433 | __global__ void __launch_bounds__(kGaussianThreads) |
| 324 | PreprocessBackwardKernel(GaussianParams params, Frame frame, | 434 | PreprocessBackwardKernel(GaussianParams params, Frame frame, |
| 325 | const Splat* __restrict__ splats, | 435 | const Splat* __restrict__ splats, |
| 326 | const float* __restrict__ splat_grads, | 436 | const float* __restrict__ splat_grads, |
| 327 | GaussianGrads grads) { | 437 | GaussianGrads grads) { |
| 328 | const int i = blockIdx.x * blockDim.x + threadIdx.x; | 438 | const int i = blockIdx.x * blockDim.x + threadIdx.x; |
| 329 | if (i >= params.n || splats[i].radius() == 0) return; | 439 | if (i >= params.n || splats[i].radius() == 0) return; |
| 330 | float g[kSplatGradFloats]; | 440 | float g[kSplatGradFloats]; |
| 331 | bool nonzero = false; | 441 | bool nonzero = false; |
| 332 | #pragma unroll | 442 | #pragma unroll |
| 333 | for (int q = 0; q < kSplatGradFloats; ++q) { | 443 | for (int q = 0; q < kSplatGradFloats; ++q) { |
| 334 | g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]); | 444 | g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]); |
| 335 | nonzero = nonzero || g[q] != 0.0f; | 445 | nonzero = nonzero || g[q] != 0.0f; |
| 336 | } | 446 | } |
| 337 | if (!nonzero) return; | 447 | if (!nonzero) return; |
| 338 | ProjectionGrad pg; | 448 | ProjectionGrad pg; |
| 339 | pg.xy = {g[0], g[1]}; | 449 | pg.xy = {g[0], g[1]}; |
| 340 | pg.conic = {g[2], g[3], g[4]}; | 450 | pg.conic = {g[2], g[3], g[4]}; |
| 341 | pg.opacity = g[5]; | 451 | pg.opacity = g[5]; |
| 342 | pg.rgb = {g[6], g[7], g[8]}; | 452 | pg.rgb = {g[6], g[7], g[8]}; |
| 343 | float3 dmean, dscale; | 453 | float3 dmean, dscale; |
| 344 | float4 dquat; | 454 | float4 dquat; |
| 345 | ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i), | 455 | ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i), |
| 346 | ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat); | 456 | ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat); |
| 347 | grads.means[3 * i] = dmean.x; | 457 | grads.means[3 * i] = dmean.x; |
| 348 | grads.means[3 * i + 1] = dmean.y; | 458 | grads.means[3 * i + 1] = dmean.y; |
| 349 | grads.means[3 * i + 2] = dmean.z; | 459 | grads.means[3 * i + 2] = dmean.z; |
| 350 | grads.scales[3 * i] = dscale.x; | 460 | grads.scales[3 * i] = dscale.x; |
| 351 | grads.scales[3 * i + 1] = dscale.y; | 461 | grads.scales[3 * i + 1] = dscale.y; |
| 352 | grads.scales[3 * i + 2] = dscale.z; | 462 | grads.scales[3 * i + 2] = dscale.z; |
| 353 | reinterpret_cast<float4*>(grads.quats)[i] = dquat; | 463 | reinterpret_cast<float4*>(grads.quats)[i] = dquat; |
| 354 | grads.opacities[i] = g[5]; | 464 | grads.opacities[i] = g[5]; |
| 355 | } | 465 | } |
| 356 | 466 | ||
| 357 | template <typename ShT> | 467 | template <typename ShT> |
| 358 | cudaError_t LaunchPreprocessBackward(const GaussianParams& params, | 468 | cudaError_t LaunchPreprocessBackward(const GaussianParams& params, |
| 359 | const Frame& frame, const Splat* splats, | 469 | const Frame& frame, const Splat* splats, |
| 360 | const float* splat_grads, | 470 | const float* splat_grads, |
| 361 | const GaussianGrads& grads, | 471 | const GaussianGrads& grads, |
| 362 | cudaStream_t stream) { | 472 | cudaStream_t stream) { |
| 363 | const int n = params.n; | 473 | const int n = params.n; |
| 364 | // As Inria's PyTorch wrapper (`torch::zeros`): bulk-zero every parameter | 474 | // As Inria's PyTorch wrapper (`torch::zeros`): bulk-zero every parameter |
| 365 | // gradient up front; culled Gaussians then exit early in the kernel. | 475 | // gradient up front; culled Gaussians then exit early in the kernel. |
| 366 | if (auto e = cudaMemsetAsync(grads.means, 0, sizeof(float) * 3 * n, stream); | 476 | if (auto e = cudaMemsetAsync(grads.means, 0, sizeof(float) * 3 * n, stream); |
| 367 | e != cudaSuccess) | 477 | e != cudaSuccess) |
| 368 | return e; | 478 | return e; |
| 369 | if (auto e = cudaMemsetAsync(grads.scales, 0, sizeof(float) * 3 * n, stream); | 479 | if (auto e = cudaMemsetAsync(grads.scales, 0, sizeof(float) * 3 * n, stream); |
| 370 | e != cudaSuccess) | 480 | e != cudaSuccess) |
| 371 | return e; | 481 | return e; |
| 372 | if (auto e = cudaMemsetAsync(grads.quats, 0, sizeof(float) * 4 * n, stream); | 482 | if (auto e = cudaMemsetAsync(grads.quats, 0, sizeof(float) * 4 * n, stream); |
| 373 | e != cudaSuccess) | 483 | e != cudaSuccess) |
| 374 | return e; | 484 | return e; |
| 375 | if (auto e = cudaMemsetAsync(grads.opacities, 0, sizeof(float) * n, stream); | 485 | if (auto e = cudaMemsetAsync(grads.opacities, 0, sizeof(float) * n, stream); |
| 376 | e != cudaSuccess) | 486 | e != cudaSuccess) |
| 377 | return e; | 487 | return e; |
| 378 | if (auto e = | 488 | if (auto e = |
| 379 | cudaMemsetAsync(grads.sh, 0, sizeof(ShT) * 3 * kShCoeffs * n, stream); | 489 | cudaMemsetAsync(grads.sh, 0, sizeof(ShT) * 3 * kShCoeffs * n, stream); |
| 380 | e != cudaSuccess) | 490 | e != cudaSuccess) |
| 381 | return e; | 491 | return e; |
| 382 | PreprocessBackwardKernel<ShT> | 492 | PreprocessBackwardKernel<ShT> |
| 383 | <<<GaussianBlocks(n), kGaussianThreads, 0, stream>>>( | 493 | <<<GaussianBlocks(n), kGaussianThreads, 0, stream>>>( |
| 384 | params, frame, splats, splat_grads, grads); | 494 | params, frame, splats, splat_grads, grads); |
| 385 | return cudaSuccess; | 495 | return cudaSuccess; |
| 386 | } | 496 | } |
| 387 | 497 | ||
| 388 | template __global__ void PreprocessBackwardKernel<float>( | 498 | template __global__ void PreprocessBackwardKernel<float>( |
| 389 | GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__, | 499 | GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__, |
| 390 | GaussianGrads); | 500 | GaussianGrads); |
| 391 | template __global__ void PreprocessBackwardKernel<ShBf16>( | 501 | template __global__ void PreprocessBackwardKernel<ShBf16>( |
| 392 | GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__, | 502 | GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__, |
| 393 | GaussianGrads); | 503 | GaussianGrads); |
| 394 | template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&, | 504 | template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&, |
| 395 | const Frame&, const Splat*, | 505 | const Frame&, const Splat*, |
| 396 | const float*, | 506 | const float*, |
| 397 | const GaussianGrads&, | 507 | const GaussianGrads&, |
| 398 | cudaStream_t); | 508 | cudaStream_t); |
| 399 | template cudaError_t LaunchPreprocessBackward<ShBf16>( | 509 | template cudaError_t LaunchPreprocessBackward<ShBf16>( |
| 400 | const GaussianParams&, const Frame&, const Splat*, const float*, | 510 | const GaussianParams&, const Frame&, const Splat*, const float*, |
| 401 | const GaussianGrads&, cudaStream_t); | 511 | const GaussianGrads&, cudaStream_t); |
| 402 | 512 | ||
| 403 | } // namespace gs | 513 | } // namespace gs |
Expand 167 unchanged lines (lines 1–167) · in ProjectVjp | |||
| 1 | 1 | /* | |
| 2 | 2 | * Copyright (C) 2023, Inria | |
| 3 | 3 | * GRAPHDECO research group, https://team.inria.fr/graphdeco | |
| 4 | 4 | * All rights reserved. | |
| 5 | 5 | * | |
| 6 | 6 | * This software is free for non-commercial, research and evaluation use | |
| 7 | 7 | * under the terms of the LICENSE.md file. | |
| 8 | 8 | * | |
| 9 | 9 | * For inquiries contact george.drettakis@inria.fr | |
| 10 | 10 | */ | |
| 11 | 11 | ||
| 12 | 12 | #include "backward.h" | |
| 13 | 13 | ||
| 14 | 14 | #include "auxiliary.h" | |
| 15 | 15 | ||
| 16 | 16 | namespace gs { | |
| 17 | 17 | ||
| 18 | 18 | namespace sh { | |
| 19 | 19 | // Evaluates SH color VJP loading the full 16-basis row `sh_in` and storing | |
| 20 | 20 | // `dsh` to `sh_out`. | |
| 21 | 21 | template <typename ShT> | |
| 22 | 22 | GS_D float3 VjpRow(float3 d, float3 g_rgb, const ShT* __restrict__ sh_in, | |
| 23 | 23 | ShT* __restrict__ sh_out) { | |
| 24 | 24 | float b[kShCoeffs]; | |
| 25 | 25 | Basis(d, b); | |
| 26 | 26 | float3 coeffs[kShCoeffs]; | |
| 27 | 27 | ShIo<ShT>::LoadRow(sh_in, coeffs); | |
| 28 | 28 | float3 raw = {0.5f, 0.5f, 0.5f}; | |
| 29 | 29 | #pragma unroll | |
| 30 | 30 | for (int k = 0; k < kShCoeffs; ++k) { | |
| 31 | 31 | raw += b[k] * coeffs[k]; | |
| 32 | 32 | } | |
| 33 | 33 | const float3 drgb = {raw.x < 0.0f ? 0.0f : g_rgb.x, | |
| 34 | 34 | raw.y < 0.0f ? 0.0f : g_rgb.y, | |
| 35 | 35 | raw.z < 0.0f ? 0.0f : g_rgb.z}; | |
| 36 | 36 | float3 bgrad[kShCoeffs]; | |
| 37 | 37 | BasisGrad(d, bgrad); | |
| 38 | 38 | float3 dsh[kShCoeffs]; | |
| 39 | 39 | float3 ddir = {0.0f, 0.0f, 0.0f}; | |
| 40 | 40 | #pragma unroll | |
| 41 | 41 | for (int k = 0; k < kShCoeffs; ++k) { | |
| 42 | 42 | dsh[k] = b[k] * drgb; | |
| 43 | 43 | const float s = dot(coeffs[k], drgb); | |
| 44 | 44 | ddir += s * bgrad[k]; | |
| 45 | 45 | } | |
| 46 | 46 | ShIo<ShT>::StoreRow(sh_out, dsh); | |
| 47 | 47 | return ddir; | |
| 48 | 48 | } | |
| 49 | 49 | } // namespace sh | |
| 50 | 50 | ||
| 51 | 51 | // VJP of the projection for a touched Gaussian `i`; reads its SH row from | |
| 52 | 52 | // `sh_in` and writes dL/dsh directly to `sh_out`. | |
| 53 | 53 | // Writes non-SH parameter gradients to `dmean_out`, `dscale_out`, `dquat_out`. | |
| 54 | 54 | template <typename ShT> | |
| 55 | 55 | GS_D void ProjectVjp(const GaussianParams& p, const Camera& cam, int i, | |
| 56 | 56 | const ShT* __restrict__ sh_in, ShT* __restrict__ sh_out, | |
| 57 | 57 | const ProjectionGrad& g, float3& dmean_out, | |
| 58 | 58 | float3& dscale_out, float4& dquat_out) { | |
| 59 | 59 | const float3 mean = LoadMean(p, i); | |
| 60 | 60 | const float3 scale = {p.scales[3 * i], p.scales[3 * i + 1], | |
| 61 | 61 | p.scales[3 * i + 2]}; | |
| 62 | 62 | const float4 quat = reinterpret_cast<const float4*>(p.quats)[i]; | |
| 63 | 63 | ||
| 64 | 64 | // --- Color: rgb = max(0.5 + sum_k B_k(dir) sh_k, 0). | |
| 65 | 65 | float3 dmean = {0.0f, 0.0f, 0.0f}; | |
| 66 | 66 | { | |
| 67 | 67 | float inv_len; | |
| 68 | 68 | const float3 dir = ViewDir(cam, mean, inv_len); | |
| 69 | 69 | const float3 ddir = sh::VjpRow(dir, g.rgb, sh_in, sh_out); | |
| 70 | 70 | // d(v/|v|)/dv = (I - dir dir^T) / |v|. | |
| 71 | 71 | dmean += inv_len * (ddir - dot(dir, ddir) * dir); | |
| 72 | 72 | } | |
| 73 | 73 | ||
| 74 | 74 | // --- Forward covariance intermediates (reusing R, s0, s1 in VJP). | |
| 75 | 75 | const float3 t = cam.ToView(mean); | |
| 76 | 76 | const float tz = t.z; | |
| 77 | 77 | const float limx = kFovClamp * cam.TanHalfFovX(); | |
| 78 | 78 | const float limy = kFovClamp * cam.TanHalfFovY(); | |
| 79 | 79 | const float txtz = t.x / tz, tytz = t.y / tz; | |
| 80 | 80 | const bool x_clamped = txtz < -limx || txtz > limx; | |
| 81 | 81 | const bool y_clamped = tytz < -limy || tytz > limy; | |
| 82 | 82 | const float tx_c = fminf(limx, fmaxf(-limx, txtz)) * tz; | |
| 83 | 83 | const float ty_c = fminf(limy, fmaxf(-limy, tytz)) * tz; | |
| 84 | 84 | const float j00 = cam.fx / tz, j02 = -cam.fx * tx_c / (tz * tz); | |
| 85 | 85 | const float j11 = cam.fy / tz, j12 = -cam.fy * ty_c / (tz * tz); | |
| 86 | 86 | const Mat3& w = cam.rotation; | |
| 87 | 87 | const float3 m0 = j00 * w.row(0) + j02 * w.row(2); | |
| 88 | 88 | const float3 m1 = j11 * w.row(1) + j12 * w.row(2); | |
| 89 | 89 | ||
| 90 | 90 | const Mat3 r = QuatToRotation(quat); | |
| 91 | 91 | const Sym3 sigma = Covariance3D(r, scale); | |
| 92 | 92 | const float3 s0 = sigma.Mul(m0), s1 = sigma.Mul(m1); | |
| 93 | 93 | const float cov_a = dot(m0, s0) + kLowPass; | |
| 94 | 94 | const float cov_b = dot(m0, s1); | |
| 95 | 95 | const float cov_c = dot(m1, s1) + kLowPass; | |
| 96 | 96 | ||
| 97 | 97 | // --- Conic -> 2D covariance: G_cov = -K G_K K. | |
| 98 | 98 | const float det = cov_a * cov_c - cov_b * cov_b; | |
| 99 | 99 | const float inv = 1.0f / det; | |
| 100 | 100 | const float ka = cov_c * inv, kb = -cov_b * inv, kc = cov_a * inv; | |
| 101 | 101 | const float ga = g.conic.x, gb = 0.5f * g.conic.y, gc = g.conic.z; | |
| 102 | 102 | // P = G_K K. | |
| 103 | 103 | const float p00 = ga * ka + gb * kb, p01 = ga * kb + gb * kc; | |
| 104 | 104 | const float p10 = gb * ka + gc * kb, p11 = gb * kb + gc * kc; | |
| 105 | 105 | // G_cov = -K P (symmetric). | |
| 106 | 106 | const float ca = -(ka * p00 + kb * p10); | |
| 107 | 107 | const float cb = -(ka * p01 + kb * p11); | |
| 108 | 108 | const float cc = -(kb * p01 + kc * p11); | |
| 109 | 109 | ||
| 110 | 110 | // --- cov = M Sigma M^T: dM = 2 G_cov M Sigma, dSigma = M^T G_cov M. | |
| 111 | 111 | const float3 dm0 = 2.0f * (ca * s0 + cb * s1); | |
| 112 | 112 | const float3 dm1 = 2.0f * (cb * s0 + cc * s1); | |
| 113 | 113 | // dSigma = ca m0 m0^T + cb (m0 m1^T + m1 m0^T) + cc m1 m1^T. | |
| 114 | 114 | Sym3 dsig; | |
| 115 | 115 | dsig.xx = ca * m0.x * m0.x + 2.0f * cb * m0.x * m1.x + cc * m1.x * m1.x; | |
| 116 | 116 | dsig.yy = ca * m0.y * m0.y + 2.0f * cb * m0.y * m1.y + cc * m1.y * m1.y; | |
| 117 | 117 | dsig.zz = ca * m0.z * m0.z + 2.0f * cb * m0.z * m1.z + cc * m1.z * m1.z; | |
| 118 | 118 | dsig.xy = | |
| 119 | 119 | ca * m0.x * m0.y + cb * (m0.x * m1.y + m1.x * m0.y) + cc * m1.x * m1.y; | |
| 120 | 120 | dsig.xz = | |
| 121 | 121 | ca * m0.x * m0.z + cb * (m0.x * m1.z + m1.x * m0.z) + cc * m1.x * m1.z; | |
| 122 | 122 | dsig.yz = | |
| 123 | 123 | ca * m0.y * m0.z + cb * (m0.y * m1.z + m1.y * m0.z) + cc * m1.y * m1.z; | |
| 124 | 124 | ||
| 125 | 125 | // --- M = J W: dJ = dM W^T (only the nonzero entries of J). | |
| 126 | 126 | const float dj00 = dot(dm0, w.row(0)), dj02 = dot(dm0, w.row(2)); | |
| 127 | 127 | const float dj11 = dot(dm1, w.row(1)), dj12 = dot(dm1, w.row(2)); | |
| 128 | 128 | const float tz2 = tz * tz, tz3 = tz2 * tz; | |
| 129 | 129 | float3 dt; | |
| 130 | 130 | dt.x = x_clamped ? 0.0f : -cam.fx / tz2 * dj02; | |
| 131 | 131 | dt.y = y_clamped ? 0.0f : -cam.fy / tz2 * dj12; | |
| 132 | 132 | dt.z = -cam.fx / tz2 * dj00 - cam.fy / tz2 * dj11 + | |
| 133 | 133 | 2.0f * cam.fx * tx_c / tz3 * dj02 + 2.0f * cam.fy * ty_c / tz3 * dj12; | |
| 134 | 134 | // --- Screen-space mean u = fx x / z + cx. | |
| 135 | 135 | dt.x += cam.fx / tz * g.xy.x; | |
| 136 | 136 | dt.y += cam.fy / tz * g.xy.y; | |
| 137 | 137 | dt.z -= (cam.fx * t.x * g.xy.x + cam.fy * t.y * g.xy.y) / tz2; | |
| 138 | 138 | dmean += MulT(w, dt); | |
| 139 | 139 | dmean_out = dmean; | |
| 140 | 140 | ||
| 141 | 141 | // --- Sigma = A A^T, A = R diag(s): dA = 2 dSigma A. | |
| 142 | 142 | const float sv[3] = {scale.x, scale.y, scale.z}; | |
| 143 | 143 | float ds[3]; | |
| 144 | 144 | Mat3 dr; | |
| 145 | 145 | #pragma unroll | |
| 146 | 146 | for (int j = 0; j < 3; ++j) { | |
| 147 | 147 | const float3 r_col = r.col(j); | |
| 148 | 148 | const float3 da_col = (2.0f * sv[j]) * dsig.Mul(r_col); | |
| 149 | 149 | ds[j] = dot(da_col, r_col); | |
| 150 | 150 | dr.m[0][j] = da_col.x * sv[j]; | |
| 151 | 151 | dr.m[1][j] = da_col.y * sv[j]; | |
| 152 | 152 | dr.m[2][j] = da_col.z * sv[j]; | |
| 153 | 153 | } | |
| 154 | 154 | dscale_out = {ds[0], ds[1], ds[2]}; | |
| 155 | 155 | ||
| 156 | 156 | // --- R(q) -> q. | |
| 157 | 157 | const float qw = quat.x, qx = quat.y, qy = quat.z, qz = quat.w; | |
| 158 | 158 | const auto& G = dr.m; | |
| 159 | 159 | dquat_out = { | |
| 160 | 160 | 2.0f * (-qz * G[0][1] + qy * G[0][2] + qz * G[1][0] - qx * G[1][2] - | |
| 161 | 161 | qy * G[2][0] + qx * G[2][1]), | |
| 162 | 162 | 2.0f * (qy * G[0][1] + qz * G[0][2] + qy * G[1][0] - 2.0f * qx * G[1][1] - | |
| 163 | 163 | qw * G[1][2] + qz * G[2][0] + qw * G[2][1] - 2.0f * qx * G[2][2]), | |
| 164 | 164 | 2.0f * | |
| 165 | 165 | (-2.0f * qy * G[0][0] + qx * G[0][1] + qw * G[0][2] + qx * G[1][0] + | |
| 166 | 166 | qz * G[1][2] - qw * G[2][0] + qz * G[2][1] - 2.0f * qy * G[2][2]), | |
| 167 | 167 | 2.0f * | |
| 168 | 168 | (-2.0f * qz * G[0][0] - qw * G[0][1] + qx * G[0][2] + qw * G[1][0] - | |
| 169 | 169 | 2.0f * qz * G[1][1] + qy * G[1][2] + qx * G[2][0] + qy * G[2][1])}; | |
| 170 | 170 | } | |
| 171 | 171 | ||
| 172 | − | // Reverse traversal per pixel; accumulates splat gradients with standard | |
| 172 | + | // Reverse traversal per pixel; accumulates splat gradients via Half-Warp | |
| 173 | − | // 5-step full-warp butterfly reductions (45 shuffles) and 9 lane-0 atomics. | |
| 173 | + | // Two-Splat Folding and Halving Register Reduce-Scatter. | |
| 174 | − | __global__ void __launch_bounds__(RasterLayout<1>::kThreads, 6) | |
| 174 | + | __global__ void __launch_bounds__(RasterLayout<1>::kThreads, 4) | |
| 175 | 175 | RasterBackwardKernel(const int2* __restrict__ ranges, | |
| 176 | 176 | const int32_t* __restrict__ point_list, | |
| 177 | 177 | const Splat* __restrict__ splats, Frame frame, | |
| 178 | 178 | const float* __restrict__ final_t, | |
Expand 44 unchanged lines (lines 179–222) · in RasterBackwardKernel | |||
| 179 | 179 | const int32_t* __restrict__ n_contrib, | |
| 180 | 180 | const float* __restrict__ dl_dimage, | |
| 181 | 181 | float* __restrict__ splat_grads) { | |
| 182 | 182 | using L = RasterLayout<1>; | |
| 183 | 183 | constexpr int kRasterThreads = L::kThreads; | |
| 184 | 184 | constexpr int kWarps = L::kWarps; | |
| 185 | 185 | const int tile = blockIdx.y * frame.grid.tiles_x + blockIdx.x; | |
| 186 | 186 | const int tid = threadIdx.x; | |
| 187 | 187 | const int warp = tid / 32; | |
| 188 | 188 | const int lane = tid % 32; | |
| 189 | 189 | const RasterPixels px = RasterPixels::For<1>(tid); | |
| 190 | 190 | const float fx = static_cast<float>(px.x); | |
| 191 | 191 | const float fy = static_cast<float>(px.y0); | |
| 192 | 192 | const int2 range = ranges[tile]; | |
| 193 | 193 | ||
| 194 | 194 | const bool inside = px.x < frame.width && px.y0 < frame.height; | |
| 195 | 195 | const int pixels = frame.width * frame.height; | |
| 196 | 196 | const int p = px.y0 * frame.width + px.x; | |
| 197 | 197 | float t = inside ? final_t[p] : 0.0f; | |
| 198 | 198 | const float3 dpix = | |
| 199 | 199 | inside ? float3{dl_dimage[0 * pixels + p], dl_dimage[1 * pixels + p], | |
| 200 | 200 | dl_dimage[2 * pixels + p]} | |
| 201 | 201 | : float3{0.0f, 0.0f, 0.0f}; | |
| 202 | 202 | float accum = 0.0f; // dot(color accumulated behind, dpix) | |
| 203 | 203 | const int last = inside ? n_contrib[p] : 0; | |
| 204 | 204 | ||
| 205 | 205 | int warp_max_last = last; | |
| 206 | 206 | #pragma unroll | |
| 207 | 207 | for (int offset = 16; offset > 0; offset /= 2) { | |
| 208 | 208 | warp_max_last = max( | |
| 209 | 209 | warp_max_last, __shfl_xor_sync(warp::kFullMask, warp_max_last, offset)); | |
| 210 | 210 | } | |
| 211 | 211 | __shared__ int shared_max_last[kWarps]; | |
| 212 | 212 | if (lane == 0) shared_max_last[warp] = warp_max_last; | |
| 213 | 213 | __syncthreads(); | |
| 214 | 214 | int block_max_last = 0; | |
| 215 | 215 | #pragma unroll | |
| 216 | 216 | for (int w = 0; w < kWarps; ++w) { | |
| 217 | 217 | block_max_last = max(block_max_last, shared_max_last[w]); | |
| 218 | 218 | } | |
| 219 | 219 | if (block_max_last <= 0) return; | |
| 220 | 220 | ||
| 221 | 221 | __shared__ float4 batch_geom0[kRasterThreads]; | |
| 222 | 222 | __shared__ float2 batch_geom1[kRasterThreads]; | |
| 223 | 223 | __shared__ float4 batch_rgb_o[kRasterThreads]; | |
| 224 | 224 | __shared__ uint32_t batch_id[kRasterThreads]; | |
| 225 | 225 | __shared__ uint8_t batch_mask[kRasterThreads]; | |
| 226 | 226 | const uint8_t warp_bit = static_cast<uint8_t>(1u << warp); | |
| 227 | + | int scatter = -1; | |
| 228 | + | int scatter2 = -1; | |
| 229 | + | { | |
| 230 | + | const int sub_lane = lane & 15; | |
| 231 | + | const int row = sub_lane >> 3, col = sub_lane & 7; | |
| 232 | + | if (col == 0) { | |
| 233 | + | scatter2 = row; | |
| 234 | + | } else if (col == 2) { | |
| 235 | + | scatter2 = 2 + row; | |
| 236 | + | } else if (col == 4) { | |
| 237 | + | scatter2 = row == 0 ? 4 : 7; | |
| 238 | + | } else if (col == 6) { | |
| 239 | + | scatter2 = row == 0 ? 6 : 8; | |
| 240 | + | } else if (col == 1 && row == 0) { | |
| 241 | + | scatter2 = 5; | |
| 242 | + | } | |
| 243 | + | const int full_row = lane >> 3, full_col = lane & 7; | |
| 244 | + | if (full_col == 0) { | |
| 245 | + | scatter = full_row; | |
| 246 | + | } else if (full_col == 2) { | |
| 247 | + | scatter = full_row == 0 ? 4 : 5 + full_row; | |
| 248 | + | } else if (full_col == 4 && full_row == 0) { | |
| 249 | + | scatter = 5; | |
| 250 | + | } | |
| 251 | + | } | |
| 227 | 252 | ||
| 253 | + | auto eval_splat = [&](float4 g0, float2 g1, float4 ro, int entry, float& dx, | |
| 254 | + | float& ga_sum, float& ga_dy, float& ga_dy2, | |
| 255 | + | float3& drgb) -> bool { | |
| 256 | + | dx = g0.x - fx; | |
| 257 | + | ga_sum = 0.0f; | |
| 258 | + | ga_dy = 0.0f; | |
| 259 | + | ga_dy2 = 0.0f; | |
| 260 | + | drgb = {0.0f, 0.0f, 0.0f}; | |
| 261 | + | if (entry < last) { | |
| 262 | + | const float dy = g0.y - fy; | |
| 263 | + | const float power = g0.z * dx * dx + g1.x * dy * dy + g0.w * dx * dy; | |
| 264 | + | if (power <= 0.0f && power >= g1.y) { | |
| 265 | + | const float g_val = Exp2Approx(power); | |
| 266 | + | const float alpha = fminf(kMaxAlpha, ro.w * g_val); | |
| 267 | + | if (alpha >= kMinAlpha) { | |
| 268 | + | t = __fdividef(t, 1.0f - alpha); | |
| 269 | + | const float w = alpha * t; | |
| 270 | + | const float color_dot = ro.x * dpix.x + ro.y * dpix.y + ro.z * dpix.z; | |
| 271 | + | const float diff = color_dot - accum; | |
| 272 | + | const float dl_dalpha = t * diff; | |
| 273 | + | accum += alpha * diff; | |
| 274 | + | const float ga = g_val * dl_dalpha; | |
| 275 | + | ga_sum = ga; | |
| 276 | + | const float ga_y = ga * dy; | |
| 277 | + | ga_dy = ga_y; | |
| 278 | + | ga_dy2 = ga_y * dy; | |
| 279 | + | drgb = w * dpix; | |
| 280 | + | return true; | |
| 281 | + | } | |
| 282 | + | } | |
| 283 | + | } | |
| 284 | + | return false; | |
| 285 | + | }; | |
| 228 | 286 | auto raw_conic = [](float4 g0, float2 g1) { | |
| 229 | 287 | return float3{kInvPower2Scale * g0.z, 0.5f * kInvPower2Scale * g0.w, | |
| 230 | 288 | kInvPower2Scale * g1.x}; | |
| 231 | 289 | }; | |
Expand 21 unchanged lines (lines 290–310) · in RasterBackwardKernel | |||
| 232 | 290 | ||
| 233 | 291 | for (int end = block_max_last; end > 0; end -= kRasterThreads) { | |
| 234 | 292 | const int e = end - 1 - tid; | |
| 235 | 293 | if (e >= 0) { | |
| 236 | 294 | const uint32_t id = static_cast<uint32_t>(point_list[range.x + e]); | |
| 237 | 295 | const float4 p0 = __ldg(&splats[id].p0); | |
| 238 | 296 | const float4 p1 = __ldg(&splats[id].p1); | |
| 239 | 297 | const float4 p2 = __ldg(&splats[id].p2); | |
| 240 | 298 | const float3 c2 = Power2Conic(p0.z, p0.w, p1.x); | |
| 241 | 299 | batch_geom0[tid] = {p0.x, p0.y, c2.x, c2.y}; | |
| 242 | 300 | batch_geom1[tid] = {c2.z, p2.w}; | |
| 243 | 301 | batch_rgb_o[tid] = {p2.x, p2.y, p2.z, p1.y}; | |
| 244 | 302 | batch_id[tid] = id; | |
| 245 | 303 | batch_mask[tid] = static_cast<uint8_t>(WarpMask<1>(p0, p1)); | |
| 246 | 304 | } else { | |
| 247 | 305 | batch_mask[tid] = 0u; | |
| 248 | 306 | } | |
| 249 | 307 | __syncthreads(); | |
| 250 | 308 | const int count = min(kRasterThreads, end); | |
| 251 | 309 | #pragma unroll | |
| 252 | 310 | for (int c = 0; c < kRasterThreads; c += 32) { | |
| 253 | 311 | if (c < count && end - 32 - c < warp_max_last) { | |
| 254 | 312 | uint32_t mask = __ballot_sync( | |
| 255 | 313 | warp::kFullMask, | |
| 256 | 314 | c + lane < count && (batch_mask[c + lane] & warp_bit) != 0u); | |
| 315 | + | while ((mask & (mask - 1u)) != 0u) { | |
| 316 | + | const int bit0 = __ffs(mask) - 1; | |
| 317 | + | mask &= mask - 1u; | |
| 318 | + | const int bit1 = __ffs(mask) - 1; | |
| 319 | + | mask &= mask - 1u; | |
| 320 | + | const int j0 = c + bit0, j1 = c + bit1; | |
| 321 | + | const float4 g0_0 = batch_geom0[j0]; | |
| 322 | + | const float2 g1_0 = batch_geom1[j0]; | |
| 323 | + | const float4 ro_0 = batch_rgb_o[j0]; | |
| 324 | + | const float4 g0_1 = batch_geom0[j1]; | |
| 325 | + | const float2 g1_1 = batch_geom1[j1]; | |
| 326 | + | const float4 ro_1 = batch_rgb_o[j1]; | |
| 327 | + | ||
| 328 | + | float dx0, ga_sum0, ga_dy0, ga_dy2_0; | |
| 329 | + | float3 drgb0; | |
| 330 | + | const bool hit0 = eval_splat(g0_0, g1_0, ro_0, end - 1 - j0, dx0, | |
| 331 | + | ga_sum0, ga_dy0, ga_dy2_0, drgb0); | |
| 332 | + | ||
| 333 | + | float dx1, ga_sum1, ga_dy1, ga_dy2_1; | |
| 334 | + | float3 drgb1; | |
| 335 | + | const bool hit1 = eval_splat(g0_1, g1_1, ro_1, end - 1 - j1, dx1, | |
| 336 | + | ga_sum1, ga_dy1, ga_dy2_1, drgb1); | |
| 337 | + | // As in s01: a pair that no pixel of the warp blends has zero | |
| 338 | + | // gradients, so skip its reduction and atomics entirely. | |
| 339 | + | if (!__any_sync(warp::kFullMask, hit0 || hit1)) continue; | |
| 340 | + | ||
| 341 | + | const bool hi16 = (lane & 16) != 0; | |
| 342 | + | const float opacity = hi16 ? ro_1.w : ro_0.w; | |
| 343 | + | const float dx = hi16 ? dx1 : dx0; | |
| 344 | + | const float3 cn = | |
| 345 | + | hi16 ? raw_conic(g0_1, g1_1) : raw_conic(g0_0, g1_0); | |
| 346 | + | ||
| 347 | + | auto fold16 = [&](float v0, float v1) { | |
| 348 | + | const float send = hi16 ? v0 : v1; | |
| 349 | + | const float recv = __shfl_xor_sync(warp::kFullMask, send, 16); | |
| 350 | + | return (hi16 ? v1 : v0) + recv; | |
| 351 | + | }; | |
| 352 | + | float ga_sum = fold16(ga_sum0, ga_sum1); | |
| 353 | + | float ga_dy = fold16(ga_dy0, ga_dy1); | |
| 354 | + | float b4[4] = {-0.5f * opacity * fold16(ga_dy2_0, ga_dy2_1), | |
| 355 | + | fold16(drgb0.x, drgb1.x), fold16(drgb0.y, drgb1.y), | |
| 356 | + | fold16(drgb0.z, drgb1.z)}; | |
| 357 | + | warp::internal::HalvingStep<4, 8>(b4, lane); | |
| 358 | + | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8); | |
| 359 | + | ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8); | |
| 360 | + | ||
| 361 | + | const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy; | |
| 362 | + | const float p_dx = dx * p_sum; | |
| 363 | + | const float v_dx0 = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy) | |
| 364 | + | : -(cn.y * p_dx + cn.z * p_dy); | |
| 365 | + | const float v_dx1 = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy; | |
| 366 | + | float v4[4] = {v_dx0, v_dx1, b4[0], b4[1]}; | |
| 367 | + | warp::internal::HalvingStep<4, 4>(v4, lane); | |
| 368 | + | warp::internal::HalvingStep<2, 2>(v4, lane); | |
| 369 | + | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 4); | |
| 370 | + | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 2); | |
| 371 | + | const float v_sum = | |
| 372 | + | v4[0] + __shfl_xor_sync(warp::kFullMask, v4[0], 1); | |
| 373 | + | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 1); | |
| 374 | + | const float out_grad = ((lane & 7) == 1) ? ga_sum : v_sum; | |
| 375 | + | if (scatter2 >= 0 && out_grad != 0.0f) { | |
| 376 | + | const uint32_t target_id = batch_id[hi16 ? j1 : j0]; | |
| 377 | + | atomicAdd(splat_grads + kSplatGradFloats * target_id + scatter2, | |
| 378 | + | out_grad); | |
| 379 | + | } | |
| 380 | + | } | |
| 257 | 381 | while (mask != 0u) { | |
| 258 | 382 | const int bit = __ffs(mask) - 1; | |
| 259 | 383 | mask &= mask - 1u; | |
| 260 | 384 | const int j = c + bit; | |
| 261 | − | const int entry = end - 1 - j; | |
| 262 | − | float gr[kSplatGradFloats] = {}; | |
| 263 | − | bool hit = false; | |
| 264 | − | if (entry < last) { | |
| 265 | 385 | ⇥ | const float4 g0 = batch_geom0[j]; |
| 266 | 386 | ⇥ | const float2 g1 = batch_geom1[j]; |
| 267 | − | const float dx = g0.x - fx; | |
| 268 | − | const float dy = g0.y - fy; | |
| 269 | − | const float power = | |
| 270 | − | g0.z * dx * dx + g1.x * dy * dy + g0.w * dx * dy; | |
| 271 | − | if (power <= 0.0f && power >= g1.y) { | |
| 272 | 387 | ⇥ | const float4 ro = batch_rgb_o[j]; |
| 273 | − | const float g_val = Exp2Approx(power); | |
| 274 | − | const float alpha = fminf(kMaxAlpha, ro.w * g_val); | |
| 275 | − | if (alpha >= kMinAlpha) { | |
| 276 | − | hit = true; | |
| 277 | − | t = __fdividef(t, 1.0f - alpha); | |
| 278 | − | const float w = alpha * t; | |
| 279 | − | const float color_dot = | |
| 280 | − | ro.x * dpix.x + ro.y * dpix.y + ro.z * dpix.z; | |
| 281 | − | const float diff = color_dot - accum; | |
| 282 | − | const float dl_dalpha = t * diff; | |
| 283 | − | accum += alpha * diff; | |
| 284 | − | const float ga = g_val * dl_dalpha; | |
| 388 | + | float dx, ga_sum, ga_dy, ga_dy2; | |
| 285 | − | const float dl_dpower = ro.w * ga; | |
| 389 | + | float3 drgb; | |
| 286 | − | const float p_dx = dx * dl_dpower; | |
| 390 | + | const bool hit = | |
| 391 | + | eval_splat(g0, g1, ro, end - 1 - j, dx, ga_sum, ga_dy, ga_dy2, drgb); | |
| 392 | + | if (!__any_sync(warp::kFullMask, hit)) continue; | |
| 287 | − | const float p_dy = dy * dl_dpower; | |
| 393 | + | const float opacity = ro.w; | |
| 288 | 394 | ⇥ | const float3 cn = raw_conic(g0, g1); |
| 395 | + | float b4[4] = {-0.5f * opacity * ga_dy2, drgb.x, drgb.y, drgb.z}; | |
| 396 | + | warp::internal::HalvingStep<4, 16>(b4, lane); | |
| 397 | + | warp::internal::HalvingStep<2, 8>(b4, lane); | |
| 398 | + | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 16); | |
| 399 | + | ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 16); | |
| 400 | + | ga_sum += __shfl_xor_sync(warp::kFullMask, ga_sum, 8); | |
| 401 | + | ga_dy += __shfl_xor_sync(warp::kFullMask, ga_dy, 8); | |
| 402 | + | const float p_sum = opacity * ga_sum, p_dy = opacity * ga_dy; | |
| 403 | + | const float p_dx = dx * p_sum; | |
| 404 | + | float v_dx; | |
| 405 | + | if ((lane & 16) == 0) { | |
| 289 | − | gr[0] = -(cn.x * p_dx + cn.y * p_dy); | |
| 406 | + | v_dx = (lane & 8) == 0 ? -(cn.x * p_dx + cn.y * p_dy) | |
| 290 | − | gr[1] = -(cn.y * p_dx + cn.z * p_dy); | |
| 407 | + | : -(cn.y * p_dx + cn.z * p_dy); | |
| 408 | + | } else { | |
| 291 | − | gr[2] = -0.5f * dx * p_dx; | |
| 409 | + | v_dx = (lane & 8) == 0 ? -0.5f * dx * p_dx : -dx * p_dy; | |
| 292 | − | gr[3] = -dx * p_dy; | |
| 293 | − | gr[4] = -0.5f * dy * p_dy; | |
| 294 | − | gr[5] = ga; | |
| 295 | − | gr[6] = w * dpix.x; | |
| 296 | − | gr[7] = w * dpix.y; | |
| 297 | − | gr[8] = w * dpix.z; | |
| 298 | 410 | ⇥ | } |
| 299 | − | } | |
| 300 | − | } | |
| 411 | + | float v[3] = {v_dx, b4[0], ga_sum}; | |
| 412 | + | warp::internal::HalvingStep<3, 4>(v, lane); | |
| 413 | + | warp::internal::HalvingStep<2, 2>(v, lane); | |
| 414 | + | const float out_grad = | |
| 301 | − | if (!__any_sync(warp::kFullMask, hit)) continue; | |
| 415 | + | v[0] + __shfl_xor_sync(warp::kFullMask, v[0], 1); | |
| 302 | − | warp::ButterflyReduce(gr); | |
| 303 | − | if (lane == 0) { | |
| 416 | + | if (scatter >= 0 && out_grad != 0.0f) { | |
| 304 | − | float* dst = splat_grads + kSplatGradFloats * batch_id[j]; | |
| 417 | + | atomicAdd(splat_grads + kSplatGradFloats * batch_id[j] + scatter, | |
| 305 | − | #pragma unroll | |
| 306 | − | for (int q = 0; q < kSplatGradFloats; ++q) { | |
| 307 | − | atomicAdd(dst + q, gr[q]); | |
| 308 | − | } | |
| 418 | + | out_grad); | |
| 309 | 419 | } | |
| 310 | 420 | } | |
| 311 | 421 | } | |
| 312 | 422 | } | |
Expand 91 unchanged lines (lines 423–513) · in PreprocessBackwardKernel | |||
| 313 | 423 | __syncthreads(); | |
| 314 | 424 | } | |
| 315 | 425 | } | |
| 316 | 426 | ||
| 317 | 427 | // --------------------------------------------------------------------------- | |
| 318 | 428 | // Preprocess backward: splat gradients -> parameter gradients. | |
| 319 | 429 | // Parameter gradient buffers are bulk-zeroed beforehand via `cudaMemsetAsync`; | |
| 320 | 430 | // culled or zero-gradient Gaussians return early. | |
| 321 | 431 | ||
| 322 | 432 | template <typename ShT> | |
| 323 | 433 | __global__ void __launch_bounds__(kGaussianThreads) | |
| 324 | 434 | PreprocessBackwardKernel(GaussianParams params, Frame frame, | |
| 325 | 435 | const Splat* __restrict__ splats, | |
| 326 | 436 | const float* __restrict__ splat_grads, | |
| 327 | 437 | GaussianGrads grads) { | |
| 328 | 438 | const int i = blockIdx.x * blockDim.x + threadIdx.x; | |
| 329 | 439 | if (i >= params.n || splats[i].radius() == 0) return; | |
| 330 | 440 | float g[kSplatGradFloats]; | |
| 331 | 441 | bool nonzero = false; | |
| 332 | 442 | #pragma unroll | |
| 333 | 443 | for (int q = 0; q < kSplatGradFloats; ++q) { | |
| 334 | 444 | g[q] = __ldg(&splat_grads[kSplatGradFloats * i + q]); | |
| 335 | 445 | nonzero = nonzero || g[q] != 0.0f; | |
| 336 | 446 | } | |
| 337 | 447 | if (!nonzero) return; | |
| 338 | 448 | ProjectionGrad pg; | |
| 339 | 449 | pg.xy = {g[0], g[1]}; | |
| 340 | 450 | pg.conic = {g[2], g[3], g[4]}; | |
| 341 | 451 | pg.opacity = g[5]; | |
| 342 | 452 | pg.rgb = {g[6], g[7], g[8]}; | |
| 343 | 453 | float3 dmean, dscale; | |
| 344 | 454 | float4 dquat; | |
| 345 | 455 | ProjectVjp(params, frame.LoadCamera(), i, ShRow<ShT>(params.sh, i), | |
| 346 | 456 | ShRow<ShT>(grads.sh, i), pg, dmean, dscale, dquat); | |
| 347 | 457 | grads.means[3 * i] = dmean.x; | |
| 348 | 458 | grads.means[3 * i + 1] = dmean.y; | |
| 349 | 459 | grads.means[3 * i + 2] = dmean.z; | |
| 350 | 460 | grads.scales[3 * i] = dscale.x; | |
| 351 | 461 | grads.scales[3 * i + 1] = dscale.y; | |
| 352 | 462 | grads.scales[3 * i + 2] = dscale.z; | |
| 353 | 463 | reinterpret_cast<float4*>(grads.quats)[i] = dquat; | |
| 354 | 464 | grads.opacities[i] = g[5]; | |
| 355 | 465 | } | |
| 356 | 466 | ||
| 357 | 467 | template <typename ShT> | |
| 358 | 468 | cudaError_t LaunchPreprocessBackward(const GaussianParams& params, | |
| 359 | 469 | const Frame& frame, const Splat* splats, | |
| 360 | 470 | const float* splat_grads, | |
| 361 | 471 | const GaussianGrads& grads, | |
| 362 | 472 | cudaStream_t stream) { | |
| 363 | 473 | const int n = params.n; | |
| 364 | 474 | // As Inria's PyTorch wrapper (`torch::zeros`): bulk-zero every parameter | |
| 365 | 475 | // gradient up front; culled Gaussians then exit early in the kernel. | |
| 366 | 476 | if (auto e = cudaMemsetAsync(grads.means, 0, sizeof(float) * 3 * n, stream); | |
| 367 | 477 | e != cudaSuccess) | |
| 368 | 478 | return e; | |
| 369 | 479 | if (auto e = cudaMemsetAsync(grads.scales, 0, sizeof(float) * 3 * n, stream); | |
| 370 | 480 | e != cudaSuccess) | |
| 371 | 481 | return e; | |
| 372 | 482 | if (auto e = cudaMemsetAsync(grads.quats, 0, sizeof(float) * 4 * n, stream); | |
| 373 | 483 | e != cudaSuccess) | |
| 374 | 484 | return e; | |
| 375 | 485 | if (auto e = cudaMemsetAsync(grads.opacities, 0, sizeof(float) * n, stream); | |
| 376 | 486 | e != cudaSuccess) | |
| 377 | 487 | return e; | |
| 378 | 488 | if (auto e = | |
| 379 | 489 | cudaMemsetAsync(grads.sh, 0, sizeof(ShT) * 3 * kShCoeffs * n, stream); | |
| 380 | 490 | e != cudaSuccess) | |
| 381 | 491 | return e; | |
| 382 | 492 | PreprocessBackwardKernel<ShT> | |
| 383 | 493 | <<<GaussianBlocks(n), kGaussianThreads, 0, stream>>>( | |
| 384 | 494 | params, frame, splats, splat_grads, grads); | |
| 385 | 495 | return cudaSuccess; | |
| 386 | 496 | } | |
| 387 | 497 | ||
| 388 | 498 | template __global__ void PreprocessBackwardKernel<float>( | |
| 389 | 499 | GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__, | |
| 390 | 500 | GaussianGrads); | |
| 391 | 501 | template __global__ void PreprocessBackwardKernel<ShBf16>( | |
| 392 | 502 | GaussianParams, Frame, const Splat* __restrict__, const float* __restrict__, | |
| 393 | 503 | GaussianGrads); | |
| 394 | 504 | template cudaError_t LaunchPreprocessBackward<float>(const GaussianParams&, | |
| 395 | 505 | const Frame&, const Splat*, | |
| 396 | 506 | const float*, | |
| 397 | 507 | const GaussianGrads&, | |
| 398 | 508 | cudaStream_t); | |
| 399 | 509 | template cudaError_t LaunchPreprocessBackward<ShBf16>( | |
| 400 | 510 | const GaussianParams&, const Frame&, const Splat*, const float*, | |
| 401 | 511 | const GaussianGrads&, cudaStream_t); | |
| 402 | 512 | ||
| 403 | 513 | } // namespace gs | |
s02_reduce_scatter (identical to s01_literature): csrc/config.h, csrc/forward.h, csrc/forward.cu.cc, csrc/backward.h, csrc/rasterizer_impl.h, csrc/rasterizer_impl.cu.cc