OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge commits / 236121c

CPU image reconstruction for texture persistence (v0.14.0)
Extend VirtualTexture to keep CPU copies of streamed tiles so Godot can save real .png textures without Blender connected. RegisteredTexture now stores width, height, and a CPU Image that accumulates mip-0 tile blits. Protocol extended: request_texture:name,width,height (backward compatible). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
236121cff8f7be2712aa5a5d7dfa1e110fa39155
by plankatron <fleeky@gmail.com> · 2026-03-03T21:54:43.000Z

Changed files (5) +108 -16

blender_addon/__init__.py +6 -4
……@@ -11,7 +11,7 @@ Usage:
1111
1212import bpy
1313
14ADDON_VERSION = "0.13.4"
14ADDON_VERSION = "0.14.0"
1515
1616# Support hot-reload (F3 > Reload Scripts)
1717if "shm_protocol" in locals():
……@@ -82,12 +82,14 @@ def _request_texture_registrations():
8282 for link in base_color_input.links:
8383 from_node = link.from_node
8484 if from_node.type == "TEX_IMAGE" and from_node.image is not None:
85 img_name = from_node.image.name
85 img = from_node.image
86 img_name = img.name
8687 if img_name not in requested and img_name not in _baker._texture_ids:
8788 requested.add(img_name)
88 msg = f"request_texture:{img_name}"
89 img_w, img_h = img.size[0], img.size[1]
90 msg = f"request_texture:{img_name},{img_w},{img_h}"
8991 _conn.send_message(msg.encode())
90 print(f"BlenderBridge: Requesting texture registration for '{img_name}'")
92 print(f"BlenderBridge: Requesting texture registration for '{img_name}' ({img_w}x{img_h})")
9193
9294 if requested:
9395 print(f"BlenderBridge: Requested {len(requested)} texture(s)")
blender_addon/blender_manifest.toml +1 -1
……@@ -2,7 +2,7 @@ schema_version = "1.0.0"
22id = "blender_godot_bridge"
33name = "Blender-Godot Bridge"
44tagline = "Real-time bidirectional scene sync with Godot"
5version = "0.13.4"
5version = "0.14.0"
66blender_version_min = "4.2.0"
77type = "add-on"
88maintainer = "blender-bridge <noreply@localhost>"
src/blender_bridge.cpp +19 -6
……@@ -163,7 +163,7 @@ Error BlenderBridge::start_server(const String &socket_path) {
163163 _known_instance_ids.clear();
164164 _next_object_id = 1;
165165
166 UtilityFunctions::print("BlenderBridge v0.13.4: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")");
166 UtilityFunctions::print("BlenderBridge v0.14.0: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")");
167167 return OK;
168168}
169169
……@@ -1086,7 +1086,7 @@ void BlenderBridge::_handle_uds_message(const UdsMessage &msg) {
10861086
10871087 // Messages are simple text-based signaling:
10881088 // "hello" — handshake
1089 // "request_texture:name" — Blender asks Godot to register a texture with VT
1089 // "request_texture:name,width,height" — Blender asks Godot to register a texture with VT
10901090 // "texture_dirty:tex_id,tile_x,tile_y,w,h,mip" — texture invalidation
10911091 // "tile_ready:tex_id,tile_x,tile_y,mip,shm_offset,size" — tile data ready in shm
10921092
……@@ -1111,15 +1111,28 @@ void BlenderBridge::_handle_uds_message(const UdsMessage &msg) {
11111111 }
11121112
11131113 if (text.substr(0, 16) == "request_texture:") {
1114 // Blender discovered an image texture and wants Godot to register it with VT
1115 std::string tex_name = text.substr(16);
1114 // Parse: request_texture:name,width,height
1115 std::string payload = text.substr(16);
1116 std::string tex_name;
1117 int img_w = 0, img_h = 0;
1118 size_t comma1 = payload.find(',');
1119 if (comma1 != std::string::npos) {
1120 tex_name = payload.substr(0, comma1);
1121 size_t comma2 = payload.find(',', comma1 + 1);
1122 if (comma2 != std::string::npos) {
1123 img_w = std::atoi(payload.substr(comma1 + 1, comma2 - comma1 - 1).c_str());
1124 img_h = std::atoi(payload.substr(comma2 + 1).c_str());
1125 }
1126 } else {
1127 tex_name = payload; // backward compat: no dimensions
1128 }
1129
11161130 if (!tex_name.empty() && !_virtual_texture_path.is_empty()) {
11171131 Node *vt_node = get_node_or_null(_virtual_texture_path);
11181132 VirtualTexture *vt = Object::cast_to<VirtualTexture>(vt_node);
11191133 if (vt) {
11201134 String gd_name = String::utf8(tex_name.c_str(), (int)tex_name.size());
1121 int vt_id = vt->register_texture(gd_name);
1122 // Reply so Blender's baker knows the ID
1135 int vt_id = vt->register_texture(gd_name, img_w, img_h);
11231136 String reply = "texture_registered:" + gd_name + "," + String::num_int64(vt_id);
11241137 CharString reply_utf8 = reply.utf8();
11251138 _uds.send_to(msg.client_id, reply_utf8.get_data(), (uint32_t)reply_utf8.length());
src/virtual_texture.cpp +71 -4
……@@ -19,7 +19,8 @@ VirtualTexture::~VirtualTexture() {
1919
2020void VirtualTexture::_bind_methods() {
2121 // Texture registration
22 ClassDB::bind_method(D_METHOD("register_texture", "name"), &VirtualTexture::register_texture);
22 ClassDB::bind_method(D_METHOD("register_texture", "name", "width", "height"),
23 &VirtualTexture::register_texture, DEFVAL(0), DEFVAL(0));
2324 ClassDB::bind_method(D_METHOD("unregister_texture", "vt_id"), &VirtualTexture::unregister_texture);
2425 ClassDB::bind_method(D_METHOD("get_texture_count"), &VirtualTexture::get_texture_count);
2526
……@@ -40,6 +41,10 @@ void VirtualTexture::_bind_methods() {
4041 // Tile upload
4142 ClassDB::bind_method(D_METHOD("upload_tile", "texture_id", "tile_x", "tile_y", "mip", "rgba_data"), &VirtualTexture::upload_tile);
4243
44 // CPU image reconstruction
45 ClassDB::bind_method(D_METHOD("get_reconstructed_image", "vt_id"), &VirtualTexture::get_reconstructed_image);
46 ClassDB::bind_method(D_METHOD("get_all_textures"), &VirtualTexture::get_all_textures);
47
4348 // Stats
4449 ClassDB::bind_method(D_METHOD("get_stats"), &VirtualTexture::get_stats);
4550
……@@ -86,10 +91,22 @@ int VirtualTexture::get_mip_levels() const { return _mip_levels; }
8691
8792// ── Texture registration ──────────────────────────────────────────────────────
8893
89int VirtualTexture::register_texture(const String &name) {
94int VirtualTexture::register_texture(const String &name, int width, int height) {
9095 int id = _next_texture_id++;
91 _textures.push_back({ id, name, true });
92 UtilityFunctions::print("VirtualTexture: Registered texture '", name, "' as id=", id);
96 RegisteredTexture tex;
97 tex.id = id;
98 tex.name = name;
99 tex.active = true;
100 tex.width = width;
101 tex.height = height;
102 // Pre-allocate CPU image if dimensions known
103 if (width > 0 && height > 0) {
104 tex.cpu_image = Image::create(width, height, false, Image::FORMAT_RGBA8);
105 tex.cpu_image->fill(Color(0, 0, 0, 0));
106 }
107 _textures.push_back(tex);
108 UtilityFunctions::print("VirtualTexture: Registered texture '", name, "' as id=", id,
109 " (", width, "x", height, ")");
93110 return id;
94111}
95112
……@@ -202,6 +219,25 @@ Error VirtualTexture::upload_tile(int texture_id, int tile_x, int tile_y, int mi
202219 _tile_cache.lock(alloc.location);
203220 }
204221
222 // Blit mip-0 tiles into the CPU reconstruction image
223 if (mip == 0) {
224 for (auto &tex : _textures) {
225 if (tex.id == texture_id && tex.cpu_image.is_valid()) {
226 Ref<Image> tile_img = Image::create_from_data(
227 _tile_size, _tile_size, false, Image::FORMAT_RGBA8, rgba_data);
228 int dst_x = tile_x * _tile_size;
229 int dst_y = tile_y * _tile_size;
230 int copy_w = MIN(_tile_size, tex.width - dst_x);
231 int copy_h = MIN(_tile_size, tex.height - dst_y);
232 if (copy_w > 0 && copy_h > 0) {
233 tex.cpu_image->blit_rect(tile_img, Rect2i(0, 0, copy_w, copy_h),
234 Vector2i(dst_x, dst_y));
235 }
236 break;
237 }
238 }
239 }
240
205241 emit_signal("tile_loaded", texture_id, tile_x, tile_y, mip);
206242 _tiles_uploaded_this_frame++;
207243 return OK;
……@@ -443,6 +479,37 @@ void VirtualTexture::_process_tile_requests() {
443479 }
444480}
445481
482// ── CPU image reconstruction ──────────────────────────────────────────────────
483
484Ref<Image> VirtualTexture::get_reconstructed_image(int vt_id) const {
485 for (const auto &tex : _textures) {
486 if (tex.id == vt_id && tex.active) {
487 return tex.cpu_image;
488 }
489 }
490 return Ref<Image>();
491}
492
493TypedArray<Dictionary> VirtualTexture::get_all_textures() const {
494 TypedArray<Dictionary> result;
495 for (const auto &tex : _textures) {
496 if (!tex.active)
497 continue;
498 Dictionary d;
499 d["id"] = tex.id;
500 d["name"] = tex.name;
501 d["width"] = tex.width;
502 d["height"] = tex.height;
503 d["has_image"] = tex.cpu_image.is_valid();
504 result.push_back(d);
505 }
506 return result;
507}
508
509Ref<Shader> VirtualTexture::get_vt_shader() const {
510 return _vt_shader;
511}
512
446513// ── Stats ─────────────────────────────────────────────────────────────────────
447514
448515Dictionary VirtualTexture::get_stats() const {
src/virtual_texture.h +11 -1
……@@ -1,5 +1,6 @@
11#pragma once
22
3#include <godot_cpp/classes/image.hpp>
34#include <godot_cpp/classes/node.hpp>
45#include <godot_cpp/classes/rendering_device.hpp>
56#include <godot_cpp/classes/rendering_server.hpp>
……@@ -12,6 +13,7 @@
1213#include <godot_cpp/variant/packed_byte_array.hpp>
1314#include <godot_cpp/variant/rid.hpp>
1415#include <godot_cpp/variant/string.hpp>
16#include <godot_cpp/variant/typed_array.hpp>
1517
1618#include "page_table.h"
1719#include "tile_cache.h"
……@@ -33,10 +35,14 @@ public:
3335 ~VirtualTexture();
3436
3537 // ── Texture registration ────────────────────────────────────────────
36 int register_texture(const String &name);
38 int register_texture(const String &name, int width = 0, int height = 0);
3739 void unregister_texture(int vt_id);
3840 int get_texture_count() const;
3941
42 // ── CPU image reconstruction ────────────────────────────────────────
43 Ref<Image> get_reconstructed_image(int vt_id) const;
44 TypedArray<Dictionary> get_all_textures() const;
45
4046 // ── Invalidation (called by BlenderBridge on texture edits) ─────────
4147 void invalidate_region(int texture_id, int tile_x, int tile_y, int w, int h, int mip);
4248 void invalidate_all(int texture_id);
……@@ -47,6 +53,7 @@ public:
4753
4854 // ── Material factory ────────────────────────────────────────────────
4955 Ref<ShaderMaterial> create_vt_material(const Color &fallback_albedo, float roughness, float metallic);
56 Ref<Shader> get_vt_shader() const;
5057
5158 // ── Configuration ───────────────────────────────────────────────────
5259 void set_tile_size(int size);
……@@ -97,6 +104,9 @@ private:
97104 int id;
98105 String name;
99106 bool active;
107 int width = 0; // Source image width in pixels
108 int height = 0; // Source image height in pixels
109 Ref<Image> cpu_image; // CPU-side reconstructed image
100110 };
101111 std::vector<RegisteredTexture> _textures;
102112 int _next_texture_id = 1;