| … | … | @@ -0,0 +1,610 @@ |
| 1 | # Blender-Bridge: Next-Gen Design |
| 2 | |
| 3 | **Date**: 2026-03-04 |
| 4 | **Status**: Design (pending approval) |
| 5 | |
| 6 | --- |
| 7 | |
| 8 | ## 1. Vision |
| 9 | |
| 10 | A real-time bidirectional Blender-to-Godot bridge that correctly translates |
| 11 | materials, textures, and shaders between both applications. Same-machine |
| 12 | first (SHM+UDS), with P2P extension (Hyperswarm+Autobase) planned for later. |
| 13 | |
| 14 | The bridge should be the **only** real-time bidirectional Blender-Godot tool |
| 15 | in existence — which it already is. This design makes it production-quality. |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## 2. Research Findings Summary |
| 20 | |
| 21 | ### 2.1 Competitive Landscape |
| 22 | |
| 23 | - **No** real-time bidirectional Blender-Godot bridge exists anywhere else. |
| 24 | - Closest reference: Unity MeshSync (TCP, custom binary, bidirectional). |
| 25 | - All other Blender-Godot tools are file-based (glTF export/import). |
| 26 | - Multi-user Blender tools (Ubisoft Mixer: abandoned, Multi-User addon: |
| 27 | active but uses object-level locking, no CRDT). |
| 28 | - NVIDIA Omniverse: enterprise USD layers + Nucleus server. Not P2P. |
| 29 | |
| 30 | ### 2.2 Blender Internals (from source code reading) |
| 31 | |
| 32 | **Image/Texture architecture** (3 layers): |
| 33 | - `Image` datablock → `ImBuf` pixel buffers → GPU texture |
| 34 | - Dual pixel buffers: `byte_buffer` (sRGB RGBA8) + `float_buffer` (linear RGBAF) |
| 35 | - UDIM: tiles packed into 2D array texture + 1D tile mapping texture |
| 36 | - `PartialUpdateChecker`: tracks dirty regions for incremental GPU upload |
| 37 | |
| 38 | **Material architecture**: |
| 39 | - `Material` → `bNodeTree` → `bNode` chain → GPU shader |
| 40 | - Principled BSDF: 31 inputs, each accepts scalar/color OR node connection |
| 41 | - Multi-material: per-face `material_index` attribute, up to 32766 slots |
| 42 | - EEVEE compiles node tree → GLSL via `GPUCodegen` |
| 43 | |
| 44 | **Texture node types**: TEX_IMAGE (file), TEX_NOISE, TEX_VORONOI, TEX_CHECKER, |
| 45 | TEX_GRADIENT, TEX_BRICK, TEX_ENVIRONMENT, TEX_GABOR. Each has a Vector input |
| 46 | (coordinates) and Color/Alpha outputs. |
| 47 | |
| 48 | ### 2.3 Godot Internals |
| 49 | |
| 50 | **Material architecture**: |
| 51 | - `StandardMaterial3D`: 19 texture slots, 13 feature toggles |
| 52 | - Internally generates GLSL shader via `_update_shader()` (~1400 lines) |
| 53 | - Pattern: `final_value = scalar_uniform * texture_sample` |
| 54 | - `ShaderMaterial`: custom shader + arbitrary uniforms (full control) |
| 55 | - Cannot mix StandardMaterial3D and ShaderMaterial |
| 56 | |
| 57 | **Texture architecture**: |
| 58 | - `ImageTexture`: CPU Image → GPU upload (simple, full replacement) |
| 59 | - `RenderingDevice` + `Texture2DRD`: low-level GPU control, sub-region copy |
| 60 | - `DrawableTexture2D` (4.7+): high-level blit API, no compute shader needed |
| 61 | |
| 62 | ### 2.4 SVT (Sparse Virtual Textures) |
| 63 | |
| 64 | **Terminology**: SVT is the correct general term (Sean Barrett, GDC 2008). |
| 65 | Our code uses "VT" in class/file names but comments say "SVT". Should be |
| 66 | consistent. |
| 67 | |
| 68 | **How Unreal does it**: Per-texture page table + shared tile cache atlas. |
| 69 | GPU feedback pass writes `{texture_id, page, mip}`. CPU reads back, loads tiles. |
| 70 | |
| 71 | **How Blender's UDIM tiles compare**: Same GPU pattern (indirection → atlas), |
| 72 | but UDIM tiles are all loaded upfront (not demand-driven). Blender's partial |
| 73 | update system tracks dirty regions for incremental GPU upload — same concept |
| 74 | as SVT tile invalidation. |
| 75 | |
| 76 | **Godot has no native SVT** but has all building blocks (RenderingDevice, |
| 77 | Texture2DRD, texture_copy for sub-region updates, compute shaders). |
| 78 | |
| 79 | **Hardware sparse textures**: 2025 paper ("The Sad State of Hardware Virtual |
| 80 | Textures") found tile binding latency is unusable. Software SVT is the |
| 81 | production standard. |
| 82 | |
| 83 | ### 2.5 Material Translation (the hard problem) |
| 84 | |
| 85 | Blender Principled BSDF → Godot StandardMaterial3D mapping: |
| 86 | |
| 87 | **Direct (no loss)**: Base Color, Metallic, Roughness, Normal, Emission, |
| 88 | Clearcoat Weight/Roughness, Anisotropic, Alpha. |
| 89 | |
| 90 | **Lossy**: IOR → specular (math conversion), SSS Weight → SSS strength |
| 91 | (no per-channel radius), Transmission → refraction (screen-space only). |
| 92 | |
| 93 | **Lost (no StandardMaterial3D equivalent)**: Sheen, Specular Tint, |
| 94 | Coat IOR/Tint/Normal, Subsurface Radius, Thin Film/Iridescence, |
| 95 | Diffuse Roughness. |
| 96 | |
| 97 | **Pattern complexity tiers**: |
| 98 | 1. **Simple**: Image Texture → BSDF input (direct slot mapping) |
| 99 | 2. **Medium**: Image + Mapping node, packed channel textures (UV params + channel select) |
| 100 | 3. **Complex**: Multi-texture mix, procedurals, custom UV chains (must bake or generate shader) |
| 101 | |
| 102 | --- |
| 103 | |
| 104 | ## 3. Architecture |
| 105 | |
| 106 | ### 3.1 Layer Diagram |
| 107 | |
| 108 | ``` |
| 109 | ┌──────────────────────────────────────────────────────────┐ |
| 110 | │ Layer 4: Material Translation │ |
| 111 | │ Node graph walker → Godot material selection │ |
| 112 | │ Simple → StandardMaterial3D │ |
| 113 | │ Medium → StandardMaterial3D + UV transform │ |
| 114 | │ Complex → Bake first, then treat as Simple │ |
| 115 | ├──────────────────────────────────────────────────────────┤ |
| 116 | │ Layer 3: Texture Transfer │ |
| 117 | │ Whole-image: ImBuf → SHM → ImageTexture │ |
| 118 | │ SVT streaming: TileBaker → SHM ring → per-tex PageTable│ |
| 119 | ├──────────────────────────────────────────────────────────┤ |
| 120 | │ Layer 2: Scene Sync (existing, working) │ |
| 121 | │ Transforms, geometry, object CRUD │ |
| 122 | │ 44-byte material scalars (albedo, roughness, etc.) │ |
| 123 | ├──────────────────────────────────────────────────────────┤ |
| 124 | │ Layer 1: Transport (existing, working) │ |
| 125 | │ SHM dual-region + UDS signaling │ |
| 126 | │ Future: Hyperswarm + Autobase for P2P │ |
| 127 | └──────────────────────────────────────────────────────────┘ |
| 128 | ``` |
| 129 | |
| 130 | ### 3.2 Texture Transfer: Two Paths |
| 131 | |
| 132 | **Path A: Whole-Image Transfer** (Phase 1 — simple, works now) |
| 133 | |
| 134 | ``` |
| 135 | Blender Image |
| 136 | │ image.pixels.foreach_get() → flat RGBA8 bytes |
| 137 | │ OR float_buffer → convert to RGBA8 |
| 138 | v |
| 139 | SHM blob (after geometry+material data in object blob) |
| 140 | │ [uint32 img_width][uint32 img_height][uint8 rgba_data[w*h*4]] |
| 141 | v |
| 142 | Godot import_changes() |
| 143 | │ Image::create_from_data(w, h, false, FORMAT_RGBA8, data) |
| 144 | │ ImageTexture::create_from_image(img) |
| 145 | v |
| 146 | StandardMaterial3D::set_texture(TEXTURE_ALBEDO, tex) |
| 147 | ``` |
| 148 | |
| 149 | - Pro: Simple, correct, no SVT complexity |
| 150 | - Con: Full image retransmit on any edit, memory proportional to total textures |
| 151 | - Good for: <10 textures, <4K resolution, same-machine |
| 152 | |
| 153 | **Path B: SVT Tile Streaming** (Phase 2 — existing infra, needs per-texture fix) |
| 154 | |
| 155 | ``` |
| 156 | Blender Image |
| 157 | │ TileBaker extracts 128×128 RGBA8 tiles |
| 158 | │ PartialUpdate dirty regions → only re-bake changed tiles |
| 159 | v |
| 160 | SHM Tile Ring (8 MB circular buffer) |
| 161 | │ tile_ready:{texture_id, tx, ty, mip, offset, size} via UDS |
| 162 | v |
| 163 | VirtualTexture node |
| 164 | │ PageTable[texture_id] maps (mip, tx, ty) → PhysicalLocation |
| 165 | │ TileCache (shared atlas, LRU eviction) |
| 166 | │ upload_tile() → texture_copy() to atlas |
| 167 | v |
| 168 | Per-mesh ShaderMaterial |
| 169 | │ Shader selects page_table by texture_id uniform |
| 170 | │ UV → virtual tile → page table lookup → atlas sample |
| 171 | ``` |
| 172 | |
| 173 | - Pro: Fixed memory budget, incremental updates, scales to many/large textures |
| 174 | - Con: More complex, requires per-texture page table refactor, custom shader |
| 175 | - Good for: >10 textures, >4K resolution, future P2P streaming |
| 176 | |
| 177 | ### 3.3 SVT Per-Texture Page Table Refactor |
| 178 | |
| 179 | Current (broken): |
| 180 | ```cpp |
| 181 | class VirtualTexture { |
| 182 | PageTable _page_table; // ONE global — textures overwrite each other |
| 183 | TileCache _tile_cache; // Shared atlas — correct |
| 184 | }; |
| 185 | ``` |
| 186 | |
| 187 | Fixed: |
| 188 | ```cpp |
| 189 | class VirtualTexture { |
| 190 | std::vector<PageTable> _page_tables; // One per registered texture |
| 191 | std::vector<RID> _page_table_rids; // One GPU texture per page table |
| 192 | std::vector<Ref<Texture2DRD>> _pt_textures; // Wrapped for shader use |
| 193 | TileCache _tile_cache; // Shared atlas — stays shared |
| 194 | }; |
| 195 | ``` |
| 196 | |
| 197 | Shader changes: |
| 198 | ```glsl |
| 199 | // Current (broken): single page table |
| 200 | uniform sampler2D vt_page_table; |
| 201 | |
| 202 | // Fixed: per-mesh page table selection |
| 203 | uniform sampler2D page_table; // Set per-mesh to the right texture's page table |
| 204 | uniform sampler2D tile_atlas; // Shared across all meshes |
| 205 | ``` |
| 206 | |
| 207 | The `create_vt_material()` method would take the texture_id and set the |
| 208 | correct page table texture as a shader parameter on the returned ShaderMaterial. |
| 209 | |
| 210 | ### 3.4 Material Node Graph Walker |
| 211 | |
| 212 | New component in `change_detector.py` (or new file `material_analyzer.py`): |
| 213 | |
| 214 | ```python |
| 215 | def analyze_material(material) -> MaterialDesc: |
| 216 | """Walk a Blender material node tree and classify it. |
| 217 | |
| 218 | Returns a MaterialDesc with: |
| 219 | - tier: SIMPLE, MEDIUM, COMPLEX |
| 220 | - scalar_values: dict of BSDF input defaults |
| 221 | - texture_slots: list of (input_name, image_name, uv_settings) |
| 222 | - features_needed: set of Godot features to enable |
| 223 | """ |
| 224 | tree = material.node_tree |
| 225 | bsdf = find_principled_bsdf(tree) |
| 226 | if bsdf is None: |
| 227 | return MaterialDesc(tier=COMPLEX) # Non-standard material |
| 228 | |
| 229 | desc = MaterialDesc(tier=SIMPLE) |
| 230 | |
| 231 | for input in bsdf.inputs: |
| 232 | if not input.is_linked: |
| 233 | # Scalar/color default value |
| 234 | desc.scalar_values[input.name] = input.default_value |
| 235 | else: |
| 236 | # Trace the connection |
| 237 | from_node = input.links[0].from_node |
| 238 | chain = trace_node_chain(from_node) |
| 239 | |
| 240 | if is_simple_image(chain): |
| 241 | # Direct Image Texture → BSDF input |
| 242 | desc.texture_slots.append(TextureSlot( |
| 243 | bsdf_input=input.name, |
| 244 | image=chain.image, |
| 245 | uv_source='UV', |
| 246 | )) |
| 247 | elif is_image_with_mapping(chain): |
| 248 | # Image Texture → Mapping → BSDF input |
| 249 | desc.tier = max(desc.tier, MEDIUM) |
| 250 | desc.texture_slots.append(TextureSlot( |
| 251 | bsdf_input=input.name, |
| 252 | image=chain.image, |
| 253 | uv_source='UV', |
| 254 | uv_transform=chain.mapping_params, |
| 255 | )) |
| 256 | else: |
| 257 | # Complex: procedural, mix, math chain |
| 258 | desc.tier = COMPLEX |
| 259 | |
| 260 | return desc |
| 261 | ``` |
| 262 | |
| 263 | ### 3.5 Material Blob Format (extended) |
| 264 | |
| 265 | Current: 44 bytes (scalars only, no texture references) |
| 266 | |
| 267 | Extended format for texture support: |
| 268 | |
| 269 | ``` |
| 270 | [uint32 has_material] |
| 271 | if has_material: |
| 272 | [float32[4] base_color] 16 bytes |
| 273 | [float32 roughness] 4 bytes |
| 274 | [float32 metallic] 4 bytes |
| 275 | [float32[4] emission_color] 16 bytes |
| 276 | [float32 emission_strength] 4 bytes |
| 277 | --- existing 44 bytes above --- |
| 278 | [float32 ior] 4 bytes (NEW) |
| 279 | [float32 clearcoat] 4 bytes (NEW) |
| 280 | [float32 clearcoat_roughness] 4 bytes (NEW) |
| 281 | [float32 alpha] 4 bytes (NEW) |
| 282 | [uint8 tier] 1 byte (NEW: 0=SIMPLE, 1=MEDIUM, 2=COMPLEX) |
| 283 | [uint8 texture_count] 1 byte (NEW: 0-8) |
| 284 | [uint8[2] _pad] 2 bytes |
| 285 | --- total scalars: 64 bytes --- |
| 286 | |
| 287 | for each texture: |
| 288 | [uint8 slot] 1 byte (BSDF input: 0=albedo, 1=metallic, ...) |
| 289 | [uint8 channel] 1 byte (0=RGBA, 1=R, 2=G, 3=B, 4=A) |
| 290 | [uint16 image_name_len] 2 bytes |
| 291 | [char[] image_name] N bytes (UTF-8, padded to 4-byte align) |
| 292 | [float32[6] uv_transform] 24 bytes (location xyz + scale xyz, or zeros) |
| 293 | ``` |
| 294 | |
| 295 | This tells Godot: "this mesh uses these textures from these BSDF inputs". |
| 296 | Godot can then look up whether the image has been transferred (whole-image |
| 297 | or SVT) and create the appropriate material. |
| 298 | |
| 299 | --- |
| 300 | |
| 301 | ## 4. Phased Implementation |
| 302 | |
| 303 | ### Phase 0: Bug Fixes + Rename (immediate) |
| 304 | |
| 305 | **0A. Revert dead texture_id code (B5)** |
| 306 | - Remove 4-byte `texture_id` from material blob writers (3 spots in C++, 1 in Python) |
| 307 | - Remove `texture_id` read in `import_changes()` and `scene_import.py` |
| 308 | - Revert all `48` → `44` size checks |
| 309 | - Remove `_texture_lookup`, `set_texture_lookup()`, `mark_texture_objects_dirty()` |
| 310 | |
| 311 | **0B. Fix SVT material application (B3)** |
| 312 | - Remove VT material conditional in `import_changes()` (lines 1101-1113) |
| 313 | - Always use StandardMaterial3D for now |
| 314 | - SVT materials will be re-enabled in Phase 2 with per-texture page tables |
| 315 | |
| 316 | **0C. Fix reconnect texture loss (B4)** |
| 317 | - Clear `_baker._texture_ids` in `_attempt_reconnect()` so textures re-register |
| 318 | |
| 319 | **0D. Rename VT → SVT consistently** |
| 320 | - Variable names: `_vt_shader` → `_svt_shader`, `vt_lookup.gdshader` → `svt_lookup.gdshader` |
| 321 | - Method names: `create_vt_material()` → `create_svt_material()` |
| 322 | - File names: `vt_lookup.gdshader` → `svt_lookup.gdshader` |
| 323 | - Comments and SPEC.md references |
| 324 | |
| 325 | **0E. Update SPEC.md** |
| 326 | - Section 3.4: Confirm 44-byte material format |
| 327 | - Section 7: Rename VT → SVT throughout |
| 328 | - Mark B3, B4, B5 as fixed |
| 329 | |
| 330 | ### Phase 1: Whole-Image Texture Transfer |
| 331 | |
| 332 | **1A. Material node graph walker** (`blender_addon/material_analyzer.py`) |
| 333 | - Walk Principled BSDF inputs |
| 334 | - Classify as SIMPLE / MEDIUM / COMPLEX |
| 335 | - Extract scalar values + texture slot info |
| 336 | - Return structured `MaterialDesc` |
| 337 | |
| 338 | **1B. Image transfer via SHM** |
| 339 | - Add `image_transfer` UDS message type |
| 340 | - Blender sends: `image_data:{name},{width},{height},{format},{offset},{size}` |
| 341 | - Image pixels written to Region 1 blob area (after object data) |
| 342 | - Godot reads: `Image::create_from_data()` → `ImageTexture::create_from_image()` |
| 343 | |
| 344 | **1C. Extended material blob** |
| 345 | - Extend 44 → 64 byte scalar section (add IOR, clearcoat, alpha) |
| 346 | - Add texture reference list (slot + image_name per texture) |
| 347 | - Godot reads texture refs, looks up transferred images, assigns to material |
| 348 | |
| 349 | **1D. StandardMaterial3D texture assignment** |
| 350 | - Map BSDF input names to Godot texture slots |
| 351 | - Enable required features (`FEATURE_NORMAL_MAPPING`, etc.) |
| 352 | - Apply IOR → specular conversion |
| 353 | |
| 354 | **1E. Blender → Godot texture paint sync** |
| 355 | - Detect image edits via `depsgraph_update_post` (existing `_dirty_images`) |
| 356 | - Re-transfer dirty images (whole-image for Phase 1) |
| 357 | |
| 358 | ### Phase 2: SVT Per-Texture Page Tables |
| 359 | |
| 360 | **2A. Refactor VirtualTexture** |
| 361 | - `std::vector<PageTable>` instead of single `PageTable` |
| 362 | - Per-texture GPU page table textures |
| 363 | - Per-texture `Texture2DRD` wrappers |
| 364 | |
| 365 | **2B. Update SVT shader** |
| 366 | - `svt_lookup.gdshader`: accept per-mesh `page_table` uniform |
| 367 | - Shared `tile_atlas` uniform |
| 368 | - Proper indirection: UV → virtual tile → page table lookup → atlas sample |
| 369 | |
| 370 | **2C. Update TileBaker** |
| 371 | - Use Blender `PartialUpdateChecker` pattern (dirty regions only) |
| 372 | - Send only changed tiles after texture paint edits |
| 373 | - Mip chain generation for each texture independently |
| 374 | |
| 375 | **2D. Material creation with SVT** |
| 376 | - When SVT is active for a texture, use ShaderMaterial with SVT shader |
| 377 | - Set per-mesh page table uniform based on texture_id |
| 378 | - Fallback to StandardMaterial3D when SVT not needed |
| 379 | |
| 380 | **2E. Demand-driven tile loading (feedback buffer)** |
| 381 | - Implement GPU feedback pass in Godot (compute shader) |
| 382 | - Read back tile requests |
| 383 | - Send requests to Blender via UDS |
| 384 | - Blender bakes and sends only requested tiles |
| 385 | |
| 386 | ### Phase 3: Advanced Material Translation |
| 387 | |
| 388 | **3A. UV transform support** |
| 389 | - Transfer Blender Mapping node params (location, rotation, scale) |
| 390 | - Apply in Godot material (UV transform in StandardMaterial3D or shader) |
| 391 | |
| 392 | **3B. Multi-material meshes** |
| 393 | - Per-face material_index in mesh data |
| 394 | - Multiple surfaces in Godot ArrayMesh |
| 395 | - Per-surface material assignment |
| 396 | |
| 397 | **3C. Procedural texture baking** |
| 398 | - Detect procedural texture chains |
| 399 | - Auto-bake in Blender to image |
| 400 | - Transfer baked image |
| 401 | - Re-bake on parameter change |
| 402 | |
| 403 | **3D. Complex material → ShaderMaterial generation** |
| 404 | - Walk node graph → emit Godot Shading Language |
| 405 | - Handle common patterns: MixRGB, ColorRamp, Math nodes |
| 406 | - Fallback: bake to textures |
| 407 | |
| 408 | ### Phase 4: P2P Extension (future) |
| 409 | |
| 410 | **4A. Autobase scene graph** |
| 411 | - Each participant has a Hypercore writer |
| 412 | - Autobase linearizes into consistent scene view |
| 413 | - Property-level LWW (like Figma) for conflict resolution |
| 414 | |
| 415 | **4B. Hyperdrive for assets** |
| 416 | - Mesh geometry and textures as content-addressed blobs |
| 417 | - Sparse replication (only download what's needed) |
| 418 | |
| 419 | **4C. SVT over Hyperswarm** |
| 420 | - Tile requests sent as P2P messages instead of UDS |
| 421 | - Tile responses streamed via encrypted Hyperswarm connections |
| 422 | - UASTC/KTX2 compression for network transfer |
| 423 | |
| 424 | **4D. Multi-Blender collaboration** |
| 425 | - Multiple Blender instances as Autobase writers |
| 426 | - Godot as downstream consumer |
| 427 | - Kleppmann-style movable tree CRDT for scene graph invariants |
| 428 | |
| 429 | --- |
| 430 | |
| 431 | ## 5. Blender-Godot Material Property Mapping (Complete) |
| 432 | |
| 433 | ### 5.1 Safe 1:1 Mappings |
| 434 | |
| 435 | | Blender Principled BSDF | Godot StandardMaterial3D | Notes | |
| 436 | |---|---|---| |
| 437 | | Base Color (default) | `albedo` Color | Direct | |
| 438 | | Base Color (image) | `TEXTURE_ALBEDO` | Direct | |
| 439 | | Metallic (default) | `metallic` float | Direct | |
| 440 | | Metallic (image) | `TEXTURE_METALLIC` + channel | Direct | |
| 441 | | Roughness (default) | `roughness` float | Direct | |
| 442 | | Roughness (image) | `TEXTURE_ROUGHNESS` + channel | Direct | |
| 443 | | Normal (image → Normal Map) | `TEXTURE_NORMAL` + `normal_scale` | Enable `FEATURE_NORMAL_MAPPING` | |
| 444 | | Emission Color | `emission` Color | Enable `FEATURE_EMISSION` | |
| 445 | | Emission Strength | `emission_energy_multiplier` | Direct | |
| 446 | | Emission (image) | `TEXTURE_EMISSION` | Direct | |
| 447 | | Coat Weight | `clearcoat` float | Enable `FEATURE_CLEARCOAT` | |
| 448 | | Coat Roughness | `clearcoat_roughness` float | Direct | |
| 449 | | Anisotropic | `anisotropy` float | Enable `FEATURE_ANISOTROPY` | |
| 450 | | Alpha | `albedo.a` | Enable transparency mode | |
| 451 | |
| 452 | ### 5.2 Lossy Mappings (approximation) |
| 453 | |
| 454 | | Blender | Godot | Conversion | |
| 455 | |---|---|---| |
| 456 | | IOR | `specular` float | `specular = ((ior-1)/(ior+1))^2 / 0.08` | |
| 457 | | SSS Weight | `sss_strength` float | Enable `FEATURE_SSS` | |
| 458 | | Transmission Weight | `refraction` float | Enable `FEATURE_REFRACTION` (screen-space only) | |
| 459 | |
| 460 | ### 5.3 Lost Properties (no StandardMaterial3D equivalent) |
| 461 | |
| 462 | Sheen (weight/roughness/tint), Specular Tint, Coat IOR, Coat Tint, |
| 463 | Coat Normal, Subsurface Radius (per-channel), Subsurface Scale, |
| 464 | Subsurface Anisotropy, Thin Film/Iridescence, Diffuse Roughness. |
| 465 | |
| 466 | These require ShaderMaterial (Phase 3) or are silently dropped. |
| 467 | |
| 468 | --- |
| 469 | |
| 470 | ## 6. SVT vs Blender UDIM: Divergence Analysis |
| 471 | |
| 472 | | Aspect | Blender UDIM | SVT (Unreal-style) | Our Bridge | |
| 473 | |---|---|---|---| |
| 474 | | Purpose | UV-space partitioning | Demand-driven streaming | Streaming across process boundary | |
| 475 | | Tile size | Variable per tile | Fixed (128²/256²) | Fixed 128² | |
| 476 | | Loading | All tiles upfront | Only visible tiles | Push-based (Phase 2E: demand-driven) | |
| 477 | | Memory | Proportional to total | Fixed budget (LRU) | Fixed budget (LRU) | |
| 478 | | Multi-texture | Separate GPU texture per Image | Per-texture page table | Per-texture page table (Phase 2) | |
| 479 | | Dirty tracking | PartialUpdateChecker (region rects) | Feedback buffer (tile IDs) | Both: Blender partial → tile invalidate | |
| 480 | | Mip selection | Standard GPU mipmaps | Per-tile mip in page table | Per-tile mip in page table | |
| 481 | | GPU pattern | 2D array + tile mapping texture | Page table texture + atlas | Page table texture + atlas | |
| 482 | |
| 483 | **Key insight**: Both use indirection → physical atlas, but for different |
| 484 | reasons. Blender's is spatial (artist-authored UV regions). SVT's is temporal |
| 485 | (load what's needed now). For a bridge, we use SVT's temporal model driven by |
| 486 | Blender's spatial dirty tracking. |
| 487 | |
| 488 | --- |
| 489 | |
| 490 | ## 7. Files Changed (Phase 0 + Phase 1) |
| 491 | |
| 492 | | File | Phase | Changes | |
| 493 | |---|---|---| |
| 494 | | `src/blender_bridge.cpp` | 0 | Remove texture_id write/read, disable SVT material, rename VT→SVT | |
| 495 | | `src/virtual_texture.h` | 0 | Rename VT→SVT in comments | |
| 496 | | `src/virtual_texture.cpp` | 0 | Rename methods, keep infra intact | |
| 497 | | `blender_addon/__init__.py` | 0 | Remove set_texture_lookup, clear baker IDs on reconnect | |
| 498 | | `blender_addon/change_detector.py` | 0 | Remove _texture_lookup, mark_texture_objects_dirty | |
| 499 | | `blender_addon/scene_import.py` | 0 | Revert 48→44 byte reads | |
| 500 | | `docs/SPEC.md` | 0 | Update material format, rename VT→SVT, mark bugs fixed | |
| 501 | | `blender_addon/material_analyzer.py` | 1 | NEW: node graph walker | |
| 502 | | `src/blender_bridge.cpp` | 1 | Extended material blob read, image transfer, texture assignment | |
| 503 | | `blender_addon/change_detector.py` | 1 | Extended material blob write, texture slot info | |
| 504 | | `blender_addon/__init__.py` | 1 | Image transfer UDS messages, dirty image re-transfer | |
| 505 | |
| 506 | --- |
| 507 | |
| 508 | ## 8. Skill Creation (from cloned repos) |
| 509 | |
| 510 | Repos cloned during research: |
| 511 | - `/storage/projects/code/godot/blender-source/` (2 GB) — Blender source |
| 512 | - `/storage/projects/code/godot/blender-manual/` (576 MB) — Blender docs |
| 513 | |
| 514 | ### Skills to create: |
| 515 | |
| 516 | 1. **blender-image-system** — Image, ImBuf, PartialUpdateChecker, GPU textures |
| 517 | Source: `source/blender/blenkernel/intern/image*.cc`, `BKE_image*.hh` |
| 518 | |
| 519 | 2. **blender-material-nodes** — Material, NodeTree, Principled BSDF, EEVEE compilation |
| 520 | Source: `source/blender/nodes/shader/`, `DNA_material_types.h` |
| 521 | |
| 522 | 3. **blender-depsgraph** — Dependency graph, change detection |
| 523 | Source: `source/blender/depsgraph/` |
| 524 | |
| 525 | 4. **blender-coordinate-system** — Z-up RH, transform matrices, axis conventions |
| 526 | Source: `source/blender/io/`, coordinate conversion code |
| 527 | |
| 528 | 5. **blender-python-api** — bpy.types.Image, Material, Object, Mesh |
| 529 | Source: `source/blender/makesrna/intern/rna_image.cc`, `rna_material.cc` |
| 530 | |
| 531 | --- |
| 532 | |
| 533 | ## 9. Test Infrastructure |
| 534 | |
| 535 | ### Phase 0 verification: |
| 536 | ```bash |
| 537 | scons -j$(nproc) && ./test/test_shm && ./test/test_uds && \ |
| 538 | ./test/test_page_table && ./test/test_tile_cache && \ |
| 539 | ./test/test_tile_requests && ./test/test_validation |
| 540 | python3 test/test_python_shm.py && python3 test/test_tile_baker.py |
| 541 | grep -rn "cursor + 48\|texture_lookup\|mark_texture_objects_dirty" \ |
| 542 | src/ blender_addon/ |
| 543 | ``` |
| 544 | |
| 545 | ### Phase 1 new tests: |
| 546 | - `test/test_material_analyzer.py` — node graph walking, tier classification |
| 547 | - `test/test_image_transfer.py` — SHM image write/read round-trip |
| 548 | - `test/test_material_mapping.py` — Blender BSDF → Godot StandardMaterial3D |
| 549 | - `test/test_bridge_cli.gd` — Godot headless: material + texture assignment |
| 550 | |
| 551 | ### Phase 2 new tests: |
| 552 | - Per-texture page table unit tests |
| 553 | - SVT shader rendering test |
| 554 | - Multi-texture tile streaming test |
| 555 | |
| 556 | --- |
| 557 | |
| 558 | ## 10. Open Questions |
| 559 | |
| 560 | 1. **Image format for SHM transfer**: RGBA8 (4 bytes/px, 16 MB for 4K) vs |
| 561 | compressed (UASTC ~1 byte/px, 4 MB for 4K)? RGBA8 is simpler for Phase 1. |
| 562 | |
| 563 | 2. **Color space handling**: Blender stores linear float internally, Godot |
| 564 | expects sRGB for albedo textures. Must convert during transfer. Data |
| 565 | textures (normal, roughness) stay linear. |
| 566 | |
| 567 | 3. **Max concurrent images**: SHM Region 1 is 8 MB default. A single 2K |
| 568 | RGBA8 image is 16 MB. Need SHM resize for image transfer, or use a |
| 569 | separate SHM segment for images. |
| 570 | |
| 571 | 4. **Multi-surface mesh support**: Current bridge only exports surface 0. |
| 572 | Phase 3 adds per-face material_index → multiple Godot surfaces. |
| 573 | |
| 574 | 5. **SVT shader vs StandardMaterial3D**: When SVT is active for a texture, |
| 575 | we must use ShaderMaterial (custom sampling). This means losing some |
| 576 | StandardMaterial3D features unless we replicate them in the SVT shader. |
| 577 | Alternative: use StandardMaterial3D for scalars + next_pass ShaderMaterial |
| 578 | for SVT texture overlay. |
| 579 | |
| 580 | --- |
| 581 | |
| 582 | ## 11. Future: P2P Architecture (not in scope, for reference) |
| 583 | |
| 584 | ``` |
| 585 | Layer 0: Hyperswarm + UDX (transport) |
| 586 | Layer 1: Autobase + Hyperbee (scene graph state, property-level LWW) |
| 587 | Layer 2: Hyperdrive (binary assets, content-addressed) |
| 588 | Layer 3: Ephemeral presence (UDX, lossy) |
| 589 | Layer 4: Bridge protocol (SHM for local, Autobase for P2P) |
| 590 | ``` |
| 591 | |
| 592 | Migration: SHM stays as local fast path. Autobase adds P2P. Scene operations |
| 593 | dual-written to both. Loro-style movable tree CRDT logic in Autobase apply |
| 594 | handler for cycle-free scene graph invariants. |
| 595 | |
| 596 | --- |
| 597 | |
| 598 | ## 12. Success Criteria |
| 599 | |
| 600 | **Phase 0**: All 5 active bugs fixed. VT→SVT renamed. All 150+ tests pass. |
| 601 | Dead code removed. SPEC.md accurate. |
| 602 | |
| 603 | **Phase 1**: Blender materials with image textures render correctly in Godot |
| 604 | with StandardMaterial3D. Albedo, normal, roughness, metallic, emission |
| 605 | texture maps all transfer via SHM. Texture paint edits sync in real-time |
| 606 | (whole-image re-transfer). No SVT involved yet. |
| 607 | |
| 608 | **Phase 2**: SVT works with multiple textures simultaneously. Per-texture |
| 609 | page tables. Tile-level incremental updates. Texture paint edits only |
| 610 | re-transfer changed tiles. Fixed memory budget regardless of texture count. |