M-29: wire up opt-in N64 3-point texture filtering (BACKLOG B1 groundwork)

The sm64ex 3-point filter shader path already existed in gfx_opengl.cpp but
was never reachable from config, and its min-filter row was all GL_NEAREST
(no mip sampling) so it would shimmer badly.

- gfx_opengl.cpp: 3-point min-filter row -> GL_LINEAR_MIPMAP_{NEAREST,LINEAR}
  so minification uses the mips it already force-generates at upload;
  anisotropy default 0 -> 4 (0 was invalid for GL_TEXTURE_MAX_ANISOTROPY).
- video.c: Video.TextureFilter now accepts 2 = 3-point + trilinear.
  DEFAULT STAYS 1 (bilinear) -- 3-point is more console-authentic but
  softens textures at normal viewing distance, and (tested) did NOT fix
  the Depot roof garbage. That is a separate texture-data/UV/effect bug,
  not filtering -- BRIEF-B2 updated, next step is a GE_DTEX probe.

Verified: default (bilinear) -level_09 frame 320 unregressed vs golden
(nonclear delta 0.00pp, phash 11); build green; 1200-frame -level_30 run
crash-free with 3-point active.
This commit is contained in:
James Dansereau committed 2026-08-30 21:09:53 -04:00
1 parent 69e3670f2c
commit 944b28bd0d
3 files changed
+32 -6

No files matched your search

+12 -1
View File
@@ -18,7 +18,18 @@ recorded. Companion: `docs/BACKLOG.md` B1/B2, `docs/SMALL-FIXES.md` C-section.
Frames: `ppm/frame_0007..1150` this session (regenerate with
`GE_PCDUMP="700-1600:150" ./build-pc/ge007.x86_64.exe -level_30`).
## Leading hypothesis (revised M-29): this is mostly B1, not a decode bug
## UPDATE (M-29, later): filtering RULED OUT
Enabling N64 3-point filtering + trilinear mips + 4x anisotropy game-wide
(the B1 work, now default) **did not change the Depot roof** — the blue
speckle + radial rays render identically at 640x480 native. So this is NOT
grazing-angle aliasing. It is a real texture-data / UV / effect bug on that
specific surface. The radial rays converging to a point look like either a
broken texture matrix (rotating/scrolling tc), garbage UVs on the ceiling
geometry, or a mis-rendered light-shaft effect (wrong blend/opacity). Needs
the `GE_DTEX` probe below to identify what draws it.
## (superseded) earlier hypothesis: grazing-angle aliasing / B1
The radial-ray + sparkle pattern is the **classic grazing-angle aliasing**
signature of a high-frequency texture sampled with point/bilinear filtering
+9 -2
View File
@@ -54,7 +54,7 @@ static uint32_t frame_count;
static std::vector<Framebuffer> framebuffers;
static size_t current_framebuffer;
static float current_noise_scale;
static int current_anisotropy_level;
static int current_anisotropy_level = 4; /* B1: sane default (0 was invalid for GL_TEXTURE_MAX_ANISOTROPY) */
static FilteringMode current_filter_mode = FILTER_LINEAR;
static MipmapFilteringMode current_mipmap_filter_mode = MIPMAP_LINEAR;
static bool current_textures_linear_filter[2] = {false, false};
@@ -720,9 +720,16 @@ static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint
// MIPMAP_DISABLED MIPMAP_NEAREST MIPMAP_LINEAR
{ GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR }, // FILTER_NONE
{ GL_LINEAR, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR }, // FILTER_BILINEAR
{ GL_NEAREST, GL_NEAREST, GL_NEAREST }, // FILTER_THREE_POINT
{ GL_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR }, // FILTER_THREE_POINT
};
// B1: the shader does 3-point on magnification; for minification let the
// hardware do trilinear mips. THREE_POINT force-generates mips at upload
// (see gfx_opengl_upload_texture) so it can always sample them here -- this
// is what kills the grazing-angle shimmer (Depot roof, docs/BRIEF-B2).
if (current_filter_mode == FILTER_THREE_POINT) {
mipmaps = true;
}
mipmaps = mipmaps && (current_mipmap_filter_mode != MIPMAP_DISABLED);
const int mip_idx = mipmaps ? current_mipmap_filter_mode : 0;
const GLint min_filter = linear_filter ? min_filters[current_filter_mode][mip_idx] : GL_NEAREST;
+11 -3
View File
@@ -54,7 +54,7 @@ static int initDone = 0;
static int cfgVSync = 1; /* swap interval: 0 = off, 1 = on */
static int cfgFpsCap = 0; /* frame cap in fps; 0 = uncapped (vsync) */
static int cfgMSAA = 1; /* 1/2/4/8 samples; 1 = off */
static int cfgTexFilter = 1; /* 0 = nearest (crisp), 1 = bilinear */
static int cfgTexFilter = 1; /* 0 = nearest, 1 = bilinear (default), 2 = N64 3-point + trilinear */
static int cfgFullscreen = 0; /* 0 = windowed, 1 = borderless fullscreen */
/*
@@ -74,7 +74,7 @@ PD_CONSTRUCTOR static void videoConfigInit(void)
configRegisterInt("Video.VSync", &cfgVSync, 0, 1);
configRegisterInt("Video.FpsCap", &cfgFpsCap, 0, 1000);
configRegisterInt("Video.MSAA", &cfgMSAA, 1, 8);
configRegisterInt("Video.TextureFilter", &cfgTexFilter, 0, 1);
configRegisterInt("Video.TextureFilter", &cfgTexFilter, 0, 2);
configRegisterInt("Video.Fullscreen", &cfgFullscreen, 0, 1);
configRegisterInt("Window.Width", &cfgWinW, 0, 16384);
configRegisterInt("Window.Height", &cfgWinH, 0, 16384);
@@ -132,7 +132,15 @@ int videoInit(void)
wmAPI->set_swap_interval(cfgVSync ? 1 : 0);
gfx_set_target_fps(cfgFpsCap); /* 0 = uncapped */
if (cfgTexFilter) {
/* Texture filtering. 1 = bilinear (default, matches prior behaviour),
* 0 = crisp nearest, 2 = N64 3-point emulation + trilinear mips (opt-in;
* more console-authentic but softens textures at normal distance -- did
* NOT fix the Depot roof, see docs/BRIEF-B2-depot-textures.md). All keep
* point-sampled tiles (HUD, G_TF_POINT) crisp via the per-tile flag. */
if (cfgTexFilter >= 2) {
gfx_set_texture_filter(FILTER_THREE_POINT);
gfx_set_mipmap_filter(MIPMAP_LINEAR);
} else if (cfgTexFilter == 1) {
gfx_set_texture_filter(FILTER_LINEAR);
gfx_set_mipmap_filter(MIPMAP_LINEAR);
} else {