#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;
}
};