OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge / src / virtual_texture.h

141 lines · c ref:
#pragma once

#include <godot_cpp/classes/image.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/rendering_device.hpp>
#include <godot_cpp/classes/rendering_server.hpp>
#include <godot_cpp/classes/shader.hpp>
#include <godot_cpp/classes/shader_material.hpp>
#include <godot_cpp/classes/texture2drd.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/color.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/rid.hpp>
#include <godot_cpp/variant/string.hpp>
#include <godot_cpp/variant/typed_array.hpp>

#include "page_table.h"
#include "tile_cache.h"
#include "tile_request_queue.h"

using namespace godot;

// ── SparseVirtualTexture node ────────────────────────────────────────────────
// Manages the SVT pipeline: page table, physical cache atlas, feedback analysis,
// and tile upload. Runs entirely on the global RenderingDevice.
class VirtualTexture : public Node {
	GDCLASS(VirtualTexture, Node)

protected:
	static void _bind_methods();

public:
	VirtualTexture();
	~VirtualTexture();

	// ── Texture registration ────────────────────────────────────────────
	int register_texture(const String &name, int width = 0, int height = 0);
	void unregister_texture(int vt_id);
	int get_texture_count() const;

	// ── CPU image reconstruction ────────────────────────────────────────
	Ref<Image> get_reconstructed_image(int vt_id) const;
	TypedArray<Dictionary> get_all_textures() const;

	// ── Invalidation (called by BlenderBridge on texture edits) ─────────
	void invalidate_region(int texture_id, int tile_x, int tile_y, int w, int h, int mip);
	void invalidate_all(int texture_id);

	// ── Atlas / page table access (for materials) ───────────────────────
	Ref<Texture2DRD> get_atlas_texture() const;
	Ref<Texture2DRD> get_page_table_texture() const;

	// ── Material factory ────────────────────────────────────────────────
	Ref<ShaderMaterial> create_svt_material(const Color &fallback_albedo, float roughness, float metallic);
	Ref<Shader> get_svt_shader() const;

	// ── Configuration ───────────────────────────────────────────────────
	void set_tile_size(int size);
	int get_tile_size() const;

	void set_tile_budget(int budget);
	int get_tile_budget() const;

	void set_cache_grid_size(int size);
	int get_cache_grid_size() const;

	void set_mip_levels(int levels);
	int get_mip_levels() const;

	// ── Feedback submission (called by FeedbackEffect or externally) ────
	void submit_feedback(const PackedByteArray &feedback_data);

	// ── Tile upload (raw RGBA8 pixel data for a single tile) ────────────
	// Called by BlenderBridge or Blender addon after tile production.
	Error upload_tile(int texture_id, int tile_x, int tile_y, int mip, const PackedByteArray &rgba_data);

	// ── Frame processing ────────────────────────────────────────────────
	void _ready() override;
	void _process(double delta) override;
	void _exit_tree() override;

	// ── Stats ───────────────────────────────────────────────────────────
	Dictionary get_stats() const;

	// ── Signals ─────────────────────────────────────────────────────────
	// tile_requested(texture_id: int, tile_x: int, tile_y: int, mip: int)
	// tile_loaded(texture_id: int, tile_x: int, tile_y: int, mip: int)

private:
	// ── Configuration ───────────────────────────────────────────────────
	int _tile_size = 128; // pixels per tile edge
	int _tile_budget = 16; // max tiles uploaded per frame
	int _cache_grid_size = 32; // 32×32 = 1024 cache slots
	int _mip_levels = 8;

	// ── Core data structures ────────────────────────────────────────────
	PageTable _page_table;
	TileCache _tile_cache;
	TileRequestQueue _request_queue;

	// ── Texture registry ────────────────────────────────────────────────
	struct RegisteredTexture {
		int id;
		String name;
		bool active;
		int width = 0;   // Source image width in pixels
		int height = 0;  // Source image height in pixels
		Ref<Image> cpu_image;  // CPU-side reconstructed image
	};
	std::vector<RegisteredTexture> _textures;
	int _next_texture_id = 1;

	// ── GPU resources ───────────────────────────────────────────────────
	RID _atlas_rid; // Physical cache atlas (RGBA8, tile_size * grid × tile_size * grid)
	RID _page_table_rid; // Page table texture (RGBA8, one texel per virtual tile)
	Ref<Texture2DRD> _atlas_tex;
	Ref<Texture2DRD> _page_table_tex;
	bool _gpu_initialized = false;

	// ── Shader (lazy-loaded) ────────────────────────────────────────────
	Ref<Shader> _svt_shader;
	bool _shader_loaded = false;

	// ── Frame tracking ──────────────────────────────────────────────────
	uint64_t _frame_count = 0;
	int _tiles_uploaded_this_frame = 0;

	// ── GPU setup/teardown ──────────────────────────────────────────────
	void _init_gpu_resources();
	void _free_gpu_resources();
	void _upload_page_table_to_gpu();

	// ── Tile upload to atlas ────────────────────────────────────────────
	void _upload_tile_to_atlas(PhysicalLocation loc, const PackedByteArray &rgba_data);

	// ── Per-frame pipeline ──────────────────────────────────────────────
	void _process_feedback();
	void _process_tile_requests();
};