OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge / src / page_table.h

175 lines · c ref:
#pragma once

#include <cstdint>
#include <cstring>
#include <vector>

// ── Physical tile location in the cache atlas ─────────────────────────────────
// Packed as uint16: x in low byte, y in high byte (matches R16G16_UINT GPU format)
struct PhysicalLocation {
	uint8_t x = 0;
	uint8_t y = 0;
};

struct PageLookup {
	bool valid = false;
	PhysicalLocation location;
};

// ── Virtual page table ────────────────────────────────────────────────────────
// Multi-mip page table mapping virtual tile coordinates to physical cache locations.
// Mip 0 is the highest detail level with the most tiles.
// Each mip level halves in each dimension: mip N has (base_size >> N)^2 entries.
class PageTable {
public:
	PageTable() = default;

	// Initialize with number of mip levels. Mip 0 = base_tiles × base_tiles.
	void init(int mip_levels, int base_tiles = 128) {
		_mip_levels = mip_levels;
		_base_tiles = base_tiles;
		_entries.resize(mip_levels);
		_valid.resize(mip_levels);

		for (int m = 0; m < mip_levels; m++) {
			int size = tiles_at_mip(m);
			_entries[m].resize(size * size);
			_valid[m].resize(size * size, false);
			memset(_entries[m].data(), 0, _entries[m].size() * sizeof(PhysicalLocation));
		}
	}

	int mip_levels() const { return _mip_levels; }
	int base_tiles() const { return _base_tiles; }

	int tiles_at_mip(int mip) const {
		int s = _base_tiles >> mip;
		return s > 0 ? s : 1;
	}

	// ── Map / Evict / Lookup ────────────────────────────────────────────

	void map(int mip, int x, int y, PhysicalLocation loc) {
		int idx = _index(mip, x, y);
		if (idx < 0)
			return;
		_entries[mip][idx] = loc;
		_valid[mip][idx] = true;
	}

	void evict(int mip, int x, int y) {
		int idx = _index(mip, x, y);
		if (idx < 0)
			return;
		_valid[mip][idx] = false;
		_entries[mip][idx] = {};
	}

	PageLookup lookup(int mip, int x, int y) const {
		int idx = _index(mip, x, y);
		if (idx < 0)
			return {};
		if (_valid[mip][idx]) {
			return { true, _entries[mip][idx] };
		}
		return {};
	}

	// Map a tile and fill unmapped children with fallback to this parent.
	// This mirrors UE5/LibVT: if a high-detail tile isn't loaded, the page table
	// points to the parent's physical tile instead (bilinear filter hides seams).
	void map_with_fallback(int mip, int x, int y, PhysicalLocation loc) {
		map(mip, x, y, loc);
		_fill_children(mip, x, y, loc);
	}

	// Evict all entries across all mip levels (full flush).
	void clear() {
		for (int m = 0; m < _mip_levels; m++) {
			std::fill(_valid[m].begin(), _valid[m].end(), false);
			memset(_entries[m].data(), 0, _entries[m].size() * sizeof(PhysicalLocation));
		}
	}

	// Evict a region of tiles at a given mip and all finer mips below it.
	void invalidate_region(int mip, int x, int y, int w, int h) {
		// Invalidate at this mip level
		for (int ty = y; ty < y + h; ty++) {
			for (int tx = x; tx < x + w; tx++) {
				evict(mip, tx, ty);
			}
		}
		// Invalidate finer mips (children)
		if (mip > 0) {
			invalidate_region(mip - 1, x * 2, y * 2, w * 2, h * 2);
		}
	}

	// ── GPU page table data (for upload to R16G16_UINT texture) ─────────

	// Get raw page table data for a specific mip level.
	// Each entry is 4 bytes: [x, y, valid_flag, 0]
	// Returns total bytes written.
	int get_gpu_data(int mip, uint8_t *out, int max_bytes) const {
		int size = tiles_at_mip(mip);
		int needed = size * size * 4;
		if (needed > max_bytes)
			return 0;

		for (int i = 0; i < size * size; i++) {
			if (_valid[mip][i]) {
				out[i * 4 + 0] = _entries[mip][i].x;
				out[i * 4 + 1] = _entries[mip][i].y;
				out[i * 4 + 2] = 1; // valid
				out[i * 4 + 3] = 0;
			} else {
				out[i * 4 + 0] = 0;
				out[i * 4 + 1] = 0;
				out[i * 4 + 2] = 0; // invalid
				out[i * 4 + 3] = 0;
			}
		}
		return needed;
	}

private:
	int _mip_levels = 0;
	int _base_tiles = 0;
	std::vector<std::vector<PhysicalLocation>> _entries; // per-mip
	std::vector<std::vector<bool>> _valid; // per-mip validity

	int _index(int mip, int x, int y) const {
		if (mip < 0 || mip >= _mip_levels)
			return -1;
		int size = tiles_at_mip(mip);
		if (x < 0 || x >= size || y < 0 || y >= size)
			return -1;
		return y * size + x;
	}

	// Recursively fill unmapped children with parent's physical location.
	void _fill_children(int parent_mip, int px, int py, PhysicalLocation loc) {
		if (parent_mip <= 0)
			return;

		int child_mip = parent_mip - 1;
		// Each parent tile covers 2×2 children
		for (int dy = 0; dy < 2; dy++) {
			for (int dx = 0; dx < 2; dx++) {
				int cx = px * 2 + dx;
				int cy = py * 2 + dy;
				int idx = _index(child_mip, cx, cy);
				if (idx < 0)
					continue;
				if (!_valid[child_mip][idx]) {
					// Only fill if child is unmapped
					_entries[child_mip][idx] = loc;
					_valid[child_mip][idx] = true;
					// Continue recursively
					_fill_children(child_mip, cx, cy, loc);
				}
			}
		}
	}
};