OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge / src / tile_request_queue.h

160 lines · c ref:
#pragma once

#include "tile_cache.h"

#include <algorithm>
#include <cstdint>
#include <unordered_map>
#include <vector>

// ── Feedback buffer format ────────────────────────────────────────────────────
// Each pixel in the feedback buffer is a packed uint32:
//   bits [31:20] = texture_id (12 bits, 0-4095)
//   bits [19:12] = tile_x     (8 bits, 0-255)
//   bits [11:4]  = tile_y     (8 bits, 0-255)
//   bits [3:0]   = mip        (4 bits, 0-15)
// Value 0 = no request (background/sky)

static inline uint32_t pack_feedback(int tex_id, int tile_x, int tile_y, int mip) {
	return ((uint32_t)tex_id << 20) | ((uint32_t)tile_x << 12) |
			((uint32_t)tile_y << 4) | (uint32_t)mip;
}

static inline void unpack_feedback(uint32_t packed, int &tex_id, int &tile_x, int &tile_y, int &mip) {
	tex_id = (packed >> 20) & 0xFFF;
	tile_x = (packed >> 12) & 0xFF;
	tile_y = (packed >> 4) & 0xFF;
	mip = packed & 0xF;
}

// ── Tile request ──────────────────────────────────────────────────────────────
enum RequestPriority : uint8_t {
	PRIORITY_INVALIDATED = 0, // Highest — cache was explicitly invalidated
	PRIORITY_HIGH_COVERAGE = 1, // Many pixels requested this tile
	PRIORITY_LOW_COVERAGE = 2, // Few pixels requested this tile
};

struct TileRequest {
	TileId tile;
	RequestPriority priority = PRIORITY_LOW_COVERAGE;
	int pixel_coverage = 0; // Number of feedback pixels that requested this tile

	// Sort: lower priority value = higher urgency, then higher coverage first
	bool operator<(const TileRequest &o) const {
		if (priority != o.priority)
			return priority < o.priority;
		return pixel_coverage > o.pixel_coverage;
	}
};

// ── Request queue ─────────────────────────────────────────────────────────────
// Collects tile requests from feedback buffer analysis and invalidation events,
// deduplicates, sorts by priority, and yields batches within a per-frame budget.
// Mirrors UE5's GatherRequests + SortLocalTileRequests.
class TileRequestQueue {
public:
	TileRequestQueue() = default;

	// Coverage threshold: tiles with fewer pixels than this are LOW priority
	void set_coverage_threshold(int threshold) { _coverage_threshold = threshold; }
	int coverage_threshold() const { return _coverage_threshold; }

	// ── Add requests ────────────────────────────────────────────────────

	// Parse feedback buffer and add requests (with dedup + coverage counting).
	void add_from_feedback(const uint32_t *data, int count) {
		for (int i = 0; i < count; i++) {
			if (data[i] == 0)
				continue; // Skip empty pixels

			int tex_id, tile_x, tile_y, mip;
			unpack_feedback(data[i], tex_id, tile_x, tile_y, mip);

			TileId tile = { tex_id, tile_x, tile_y, mip };
			uint64_t key = _tile_key(tile);

			auto it = _request_map.find(key);
			if (it != _request_map.end()) {
				// Already seen — increment coverage
				_requests[it->second].pixel_coverage++;
			} else {
				// New request
				TileRequest req;
				req.tile = tile;
				req.priority = PRIORITY_LOW_COVERAGE; // Updated in finalize
				req.pixel_coverage = 1;
				_request_map[key] = (int)_requests.size();
				_requests.push_back(req);
			}
		}
	}

	// Add a high-priority invalidation request.
	void add_invalidated(int texture_id, int tile_x, int tile_y, int mip) {
		TileId tile = { texture_id, tile_x, tile_y, mip };
		uint64_t key = _tile_key(tile);

		auto it = _request_map.find(key);
		if (it != _request_map.end()) {
			// Upgrade priority
			_requests[it->second].priority = PRIORITY_INVALIDATED;
		} else {
			TileRequest req;
			req.tile = tile;
			req.priority = PRIORITY_INVALIDATED;
			req.pixel_coverage = 0;
			_request_map[key] = (int)_requests.size();
			_requests.push_back(req);
		}
	}

	// ── Retrieve sorted batch ───────────────────────────────────────────

	// Finalize priorities based on coverage threshold, sort, and return
	// up to `budget` requests. Clears the queue afterward.
	std::vector<TileRequest> get_next_batch(int budget) {
		// Update coverage-based priorities
		for (auto &req : _requests) {
			if (req.priority != PRIORITY_INVALIDATED) {
				req.priority = (req.pixel_coverage >= _coverage_threshold)
						? PRIORITY_HIGH_COVERAGE
						: PRIORITY_LOW_COVERAGE;
			}
		}

		// Sort by priority (invalidated first, then high coverage, then low)
		std::sort(_requests.begin(), _requests.end());

		// Take up to budget
		int take = std::min(budget, (int)_requests.size());
		std::vector<TileRequest> batch(_requests.begin(), _requests.begin() + take);

		// Clear
		_requests.clear();
		_request_map.clear();

		return batch;
	}

	// ── Stats ───────────────────────────────────────────────────────────

	int pending_count() const { return (int)_requests.size(); }

	void clear() {
		_requests.clear();
		_request_map.clear();
	}

private:
	int _coverage_threshold = 4; // Pixels needed for HIGH priority
	std::vector<TileRequest> _requests;
	std::unordered_map<uint64_t, int> _request_map; // tile_key → index

	static uint64_t _tile_key(const TileId &tile) {
		return ((uint64_t)tile.texture_id << 32) |
				((uint64_t)tile.tile_x << 16) |
				((uint64_t)tile.tile_y << 4) |
				(uint64_t)tile.mip;
	}
};