@mytec: stack done, rust next
This commit is contained in:
281
docs/devlog/gpu_supp/RFCP-WebGL-Smooth-Coverage-Task.md
Normal file
281
docs/devlog/gpu_supp/RFCP-WebGL-Smooth-Coverage-Task.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# RFCP v3.10.5: WebGL Smooth Coverage Implementation
|
||||
|
||||
## Контекст проблеми
|
||||
|
||||
**Поточний стан:**
|
||||
- Backend повертає grid точок з lat/lon/RSRP (50m = 6,675 pts, 200m = 1,975 pts)
|
||||
- WebGL texture-based rendering: points → texture → GL_LINEAR → colormap
|
||||
- **Проблема:** Видимі grid squares/pixelation, особливо при zoom in або sparse grids (200m)
|
||||
|
||||
**Причина:**
|
||||
- `GL_LINEAR` дає тільки C0 continuity (значення співпадають на краях, але похідні — ні)
|
||||
- Це створює видимі "шви" між клітинками
|
||||
|
||||
## Рішення з ресерчу
|
||||
|
||||
### Ключовий інсайт
|
||||
|
||||
**Catmull-Rom spline interpolation** дає C1 continuity (smooth derivatives) І проходить через exact data values (на відміну від B-spline який blurs peaks).
|
||||
|
||||
**9-tap Catmull-Rom** замість `texture2D()`:
|
||||
- 9 texture fetches замість 1
|
||||
- ~0.32ms vs ~0.30ms на GTX 980 при 1920×1080
|
||||
- Для нашої ~80×85 текстури — практично безкоштовно
|
||||
|
||||
### Критичне правило
|
||||
|
||||
**Інтерполювати RAW RSRP values ПЕРЕД colormap!**
|
||||
- ❌ Неправильно: texture → colormap → interpolate (muddy colors)
|
||||
- ✅ Правильно: texture → interpolate → colormap (clean gradients)
|
||||
|
||||
---
|
||||
|
||||
## Етап 1: Quick Fix (30 хвилин)
|
||||
|
||||
### Smoothstep coordinate remapping
|
||||
|
||||
Найшвидший спосіб прибрати grid edges — одна зміна в shader:
|
||||
|
||||
```glsl
|
||||
// ЗАМІСТЬ:
|
||||
vec4 texColor = texture2D(u_texture, v_uv);
|
||||
|
||||
// ВИКОРИСТАТИ:
|
||||
vec4 textureSmooth(sampler2D tex, vec2 uv, vec2 texSize) {
|
||||
vec2 p = uv * texSize + 0.5;
|
||||
vec2 i = floor(p);
|
||||
vec2 f = p - i;
|
||||
f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); // quintic hermite
|
||||
return texture2D(tex, (i + f - 0.5) / texSize);
|
||||
}
|
||||
|
||||
// В main():
|
||||
vec4 texColor = textureSmooth(u_texture, v_uv, u_textureSize);
|
||||
```
|
||||
|
||||
**Що це дає:**
|
||||
- C2 continuity з одним texture read
|
||||
- Прибирає видимі grid edges
|
||||
- Мінімальний positional bias
|
||||
|
||||
**Потрібно додати uniform:**
|
||||
```javascript
|
||||
const textureSizeLocation = gl.getUniformLocation(program, 'u_textureSize');
|
||||
gl.uniform2f(textureSizeLocation, textureWidth, textureHeight);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Етап 2: Production Implementation (1-2 години)
|
||||
|
||||
### 9-tap Catmull-Rom Shader
|
||||
|
||||
```glsl
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec2 u_textureSize;
|
||||
uniform float u_opacity;
|
||||
varying vec2 v_uv;
|
||||
|
||||
// Catmull-Rom 9-tap interpolation
|
||||
// Source: TheRealMJP's gist (108 GitHub stars)
|
||||
vec4 SampleTextureCatmullRom(sampler2D tex, vec2 uv, vec2 texSize) {
|
||||
vec2 samplePos = uv * texSize;
|
||||
vec2 texPos1 = floor(samplePos - 0.5) + 0.5;
|
||||
vec2 f = samplePos - texPos1;
|
||||
|
||||
// Catmull-Rom weights
|
||||
vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
|
||||
vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
|
||||
vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
|
||||
vec2 w3 = f * f * (-0.5 + 0.5 * f);
|
||||
|
||||
// Combine weights for optimized sampling
|
||||
vec2 w12 = w1 + w2;
|
||||
vec2 offset12 = w2 / (w1 + w2);
|
||||
|
||||
// Compute texture coordinates
|
||||
vec2 texPos0 = (texPos1 - 1.0) / texSize;
|
||||
vec2 texPos3 = (texPos1 + 2.0) / texSize;
|
||||
vec2 texPos12 = (texPos1 + offset12) / texSize;
|
||||
|
||||
// 9 texture fetches (optimized from 16)
|
||||
vec4 result = vec4(0.0);
|
||||
result += texture2D(tex, vec2(texPos0.x, texPos0.y)) * w0.x * w0.y;
|
||||
result += texture2D(tex, vec2(texPos12.x, texPos0.y)) * w12.x * w0.y;
|
||||
result += texture2D(tex, vec2(texPos3.x, texPos0.y)) * w3.x * w0.y;
|
||||
result += texture2D(tex, vec2(texPos0.x, texPos12.y)) * w0.x * w12.y;
|
||||
result += texture2D(tex, vec2(texPos12.x, texPos12.y)) * w12.x * w12.y;
|
||||
result += texture2D(tex, vec2(texPos3.x, texPos12.y)) * w3.x * w12.y;
|
||||
result += texture2D(tex, vec2(texPos0.x, texPos3.y)) * w0.x * w3.y;
|
||||
result += texture2D(tex, vec2(texPos12.x, texPos3.y)) * w12.x * w3.y;
|
||||
result += texture2D(tex, vec2(texPos3.x, texPos3.y)) * w3.x * w3.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
// RSRP to color mapping (cyan -> green -> yellow -> orange -> red)
|
||||
vec3 rsrpToColor(float rsrp) {
|
||||
// rsrp: normalized 0.0 (weak, -110dBm) to 1.0 (strong, -50dBm)
|
||||
|
||||
// Color stops: red -> orange -> yellow -> green -> cyan
|
||||
vec3 c0 = vec3(1.0, 0.0, 0.0); // red (weak)
|
||||
vec3 c1 = vec3(1.0, 0.5, 0.0); // orange
|
||||
vec3 c2 = vec3(1.0, 1.0, 0.0); // yellow
|
||||
vec3 c3 = vec3(0.0, 1.0, 0.0); // green
|
||||
vec3 c4 = vec3(0.0, 1.0, 1.0); // cyan (strong)
|
||||
|
||||
float t = clamp(rsrp, 0.0, 1.0);
|
||||
|
||||
if (t < 0.25) {
|
||||
return mix(c0, c1, t / 0.25);
|
||||
} else if (t < 0.5) {
|
||||
return mix(c1, c2, (t - 0.25) / 0.25);
|
||||
} else if (t < 0.75) {
|
||||
return mix(c2, c3, (t - 0.5) / 0.25);
|
||||
} else {
|
||||
return mix(c3, c4, (t - 0.75) / 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
// 1. Sample with Catmull-Rom interpolation (RAW value)
|
||||
vec4 texColor = SampleTextureCatmullRom(u_texture, v_uv, u_textureSize);
|
||||
float rsrpNormalized = texColor.r;
|
||||
|
||||
// 2. Discard if no coverage (validity check)
|
||||
if (rsrpNormalized < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// 3. Apply colormap AFTER interpolation
|
||||
vec3 color = rsrpToColor(rsrpNormalized);
|
||||
|
||||
// 4. Smooth boundary fading (optional)
|
||||
float boundaryAlpha = smoothstep(0.01, 0.05, rsrpNormalized);
|
||||
|
||||
gl_FragColor = vec4(color, boundaryAlpha * u_opacity);
|
||||
}
|
||||
```
|
||||
|
||||
### JavaScript зміни
|
||||
|
||||
```javascript
|
||||
// 1. Vertex shader (без змін)
|
||||
const vertexShaderSource = `
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_texCoord;
|
||||
varying vec2 v_uv;
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
v_uv = a_texCoord;
|
||||
}
|
||||
`;
|
||||
|
||||
// 2. При створенні texture — зберегти розміри
|
||||
const textureWidth = gridWidth;
|
||||
const textureHeight = gridHeight;
|
||||
|
||||
// 3. Передати uniform
|
||||
const textureSizeLocation = gl.getUniformLocation(program, 'u_textureSize');
|
||||
if (textureSizeLocation) {
|
||||
gl.uniform2f(textureSizeLocation, textureWidth, textureHeight);
|
||||
} else {
|
||||
console.error('[WebGL] u_textureSize uniform NOT FOUND!');
|
||||
}
|
||||
|
||||
// 4. Texture filtering — можна залишити LINEAR для fallback
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Етап 3: Texture Data Format
|
||||
|
||||
### Поточний формат (перевірити)
|
||||
|
||||
```javascript
|
||||
// Normalized RSRP value (0-255 mapped to 0.0-1.0 in shader)
|
||||
const normalized = (rsrp - minRsrp) / (maxRsrp - minRsrp);
|
||||
const value = Math.round(normalized * 255);
|
||||
|
||||
// Store in R channel
|
||||
textureData[idx] = value; // R = normalized RSRP
|
||||
textureData[idx + 1] = value; // G (можна використати для validity mask)
|
||||
textureData[idx + 2] = value; // B
|
||||
textureData[idx + 3] = 255; // A = fully opaque
|
||||
```
|
||||
|
||||
### Альтернатива: Float texture (краща точність)
|
||||
|
||||
```javascript
|
||||
// Якщо браузер підтримує OES_texture_float
|
||||
const ext = gl.getExtension('OES_texture_float');
|
||||
if (ext) {
|
||||
const floatData = new Float32Array(width * height);
|
||||
for (const point of points) {
|
||||
const normalized = (point.rsrp - minRsrp) / (maxRsrp - minRsrp);
|
||||
floatData[gridY * width + gridX] = normalized;
|
||||
}
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0,
|
||||
gl.LUMINANCE, gl.FLOAT, floatData);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Чеклист імплементації
|
||||
|
||||
### Phase 1: Quick Test (Smoothstep)
|
||||
- [ ] Додати `u_textureSize` uniform
|
||||
- [ ] Замінити `texture2D()` на `textureSmooth()`
|
||||
- [ ] Тест на 50m і 200m
|
||||
- [ ] Тест zoom in/out
|
||||
|
||||
### Phase 2: Production (Catmull-Rom)
|
||||
- [ ] Імплементувати `SampleTextureCatmullRom()`
|
||||
- [ ] Оновити colormap function
|
||||
- [ ] Додати boundary fading
|
||||
- [ ] Тест edge cases (краї текстури)
|
||||
- [ ] Performance benchmark
|
||||
|
||||
### Phase 3: Polish
|
||||
- [ ] Видалити старі CSS blur workarounds
|
||||
- [ ] Видалити cellSize multiplication (не потрібно з Catmull-Rom)
|
||||
- [ ] Cleanup debug logs
|
||||
- [ ] Update version to v3.10.5
|
||||
|
||||
---
|
||||
|
||||
## Очікуваний результат
|
||||
|
||||
**До (GL_LINEAR):**
|
||||
```
|
||||
┌───┬───┬───┐
|
||||
│ A │ B │ C │ ← Видимі краї між клітинками
|
||||
├───┼───┼───┤ C0 continuity
|
||||
│ D │ E │ F │
|
||||
└───┴───┴───┘
|
||||
```
|
||||
|
||||
**Після (Catmull-Rom):**
|
||||
```
|
||||
╭───────────────╮
|
||||
│ ░░░▒▒▓▓██ │ ← Smooth gradient
|
||||
│ ░░░▒▒▓▓██▓▓ │ C1 continuity
|
||||
│ ░░▒▒▓▓██ │ Exact values at grid points
|
||||
╰───────────────╯
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Референси
|
||||
|
||||
1. [TheRealMJP's 9-tap Catmull-Rom HLSL](https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1)
|
||||
2. [Inigo Quilez - Better Texture Filtering](https://iquilezles.org/articles/texture/)
|
||||
3. [2D Catmull-Rom in 4 samples - Shadertoy](https://www.shadertoy.com/view/4tyGDD)
|
||||
4. [mapbox-gl-interpolate-heatmap](https://github.com/vinayakkulkarni/mapbox-gl-interpolate-heatmap)
|
||||
5. [NVIDIA GPU Gems 2 - Fast Third-Order Texture Filtering](https://developer.nvidia.com/gpugems/gpugems2/part-iii-high-quality-rendering/chapter-20-fast-third-order-texture-filtering)
|
||||
Reference in New Issue
Block a user