Outline render order, stencil protection, JFA sentinel fix, diagnostics

Render order: 3-way M2 batch partition — game objects + local player
render first (write depth), then outline targets (stencil marks), then
other players/gear/NPCs. Outlines show through other players but are
occluded by world/WMO/game objects/local player.

Stencil protection: set STENCILWRITEMASK=0 after outline target DIPs
to prevent subsequent renders from overwriting stencil marks.

JFA sentinel: changed from (1,1) to (-1,-1) to move it outside UV
space. Did not fix banding but is correct regardless.

Debug: added DEBUG_SHOW_SILHOUETTE comptime flag to bypass JFA and
composite raw silhouette RT. Confirmed silhouette is clean — banding
is in the JFA pipeline, not stale vertex buffers.

Game object + local player tracking in tracker.zig for batch ordering.

Added project README and outline subsystem README documenting render
pipeline, architecture, known issues, and planned features.
This commit is contained in:
MarcelineVQ
2026-02-25 18:32:49 -08:00
parent 661e1562d9
commit fbfe1cb545
6 changed files with 656 additions and 33 deletions
+56
View File
@@ -45,6 +45,21 @@ pub var tracked_obj_count: usize = 0;
var frame_outlines: [MAX_OUTLINE_MODELS]types.OutlineEntry = undefined;
var frame_outline_count: usize = 0;
// =============================================================================
// Per-frame game object tracking (for render ordering — game objects first)
// =============================================================================
// Game object M2 models need to render before outline targets so their depth
// is in the buffer when stencil marks are written. Tracked separately from
// outline entries since game objects don't get outlines.
const MAX_GAME_OBJ_MODELS = 256;
var game_obj_ptrs: [MAX_TRACKED_OBJS]u32 = undefined;
var game_obj_ptr_count: usize = 0;
var game_obj_models: [MAX_GAME_OBJ_MODELS]u32 = undefined;
var game_obj_model_count: usize = 0;
// =============================================================================
// Global enable flag
// =============================================================================
@@ -88,6 +103,14 @@ pub fn getModelCategory(model_ptr: u32) types.ModelCategory {
return entry.category;
}
/// Check if a model belongs to a game object (for render ordering).
pub fn isGameObjectModel(model_ptr: u32) bool {
for (game_obj_models[0..game_obj_model_count]) |m| {
if (m == model_ptr) return true;
}
return false;
}
/// Get screen-space outline thickness in pixels for a category.
pub fn getOutlinePixels(cat: types.ModelCategory) f32 {
return switch (cat) {
@@ -124,6 +147,24 @@ pub fn classifyModel(model_ptr: u32) void {
}
}
}
// Match against game object pointers (for render ordering).
if (game_obj_ptr_count > 0 and game_obj_model_count < MAX_GAME_OBJ_MODELS) {
for (game_obj_ptrs[0..game_obj_ptr_count]) |go_ptr| {
if (owner_callback == go_ptr or owner_direct == go_ptr) {
// Deduplicate
var found = false;
for (game_obj_models[0..game_obj_model_count]) |m| {
if (m == model_ptr) { found = true; break; }
}
if (!found) {
game_obj_models[game_obj_model_count] = model_ptr;
game_obj_model_count += 1;
}
break;
}
}
}
}
fn addOutlineEntry(model_ptr: u32, cat: types.ModelCategory, mark: u8) void {
@@ -155,12 +196,21 @@ pub fn scanObjects() void {
// Clear per-frame sets
frame_outline_count = 0;
tracked_obj_count = 0;
game_obj_ptr_count = 0;
game_obj_model_count = 0;
resetDiag();
if (!wow.isInGame()) return;
const local_player = wow.getLocalPlayer();
if (local_player == 0) return;
// Local player renders before outline targets (occludes outlines) unless
// the local player IS an outline target (partition logic checks outline first).
if (game_obj_ptr_count < MAX_TRACKED_OBJS) {
game_obj_ptrs[game_obj_ptr_count] = local_player;
game_obj_ptr_count += 1;
}
// Cache raid target GUIDs
wow.cacheRaidTargets();
@@ -197,6 +247,12 @@ pub fn scanObjects() void {
addTrackedObj(obj, .dead_player, 0);
}
},
.game_object => {
if (game_obj_ptr_count < MAX_TRACKED_OBJS) {
game_obj_ptrs[game_obj_ptr_count] = obj;
game_obj_ptr_count += 1;
}
},
else => {},
}
}