The texture sampler was Repeat while its comment claimed it clamped
Wiring the first gs rule into the renderer, and it turned up a bug that had been
sitting in plain sight:
// Only mip level 0 is decoded, so minification must clamp rather than
// invent a level the disc does not have here.
ImageSamplerDescriptor {
mag_filter: ImageFilterMode::Linear,
..default() // <-- address_mode is Repeat
}
The comment describes clamping. The code was doing the opposite: `..default()`
supplies bevy's default address mode, which is ImageAddressMode::Repeat. Nothing
here ever set an address mode, so every texture was sampling with Repeat while a
comment three lines up claimed it was not.
That is the GS rule I wrote in 81ec178 saying out loud -- CLAMP, with 1.0 held at
the far edge -- and the renderer was doing the other thing. The seam shows up as a
patch running off its texture wrapping onto the opposite edge instead of repeating
the border texel, which is exactly the kind of thing that reads as "the texture
decoded wrong" and sends you looking in the format parser instead of at one line
of sampler setup.
Also set mipmap_filter to Nearest. Only level 0 is decoded, so there is no chain
to interpolate across and the filter cannot matter; Nearest is the honest setting
until levels exist, and the comment now says that gs::mip_level returns 0 for every
patch until they are decoded, rather than implying a selection is happening.
Worth noting how this was found. I went looking for where clamp01 should live and
found the answer was "somewhere that already claims to do it, and doesn't". A
comment asserting behaviour is not evidence of behaviour -- the comment here was
specific and plausible and wrong, which is worse than no comment, because it stops
anyone checking.
Two API corrections along the way, both mine: ImageSamplerDescriptor has no
`address_mode` field -- it is address_mode_u and address_mode_v separately -- and
the engine is on bevy 0.19 rather than raw wgpu, so the GS rules reach the GPU
through bevy's sampler. The roadmap now says so instead of implying a wgpu
integration that does not exist.
278 tests, clippy clean.
This commit is contained in:
@@ -19,7 +19,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use bevy::asset::RenderAssetUsages;
|
use bevy::asset::RenderAssetUsages;
|
||||||
use bevy::image::{ImageFilterMode, ImageSampler, ImageSamplerDescriptor};
|
use bevy::image::{ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::mesh::{Indices, PrimitiveTopology};
|
use bevy::render::mesh::{Indices, PrimitiveTopology};
|
||||||
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
|
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
|
||||||
@@ -386,11 +386,20 @@ fn image_of(t: &gc_formats::texture::Texture) -> Image {
|
|||||||
TextureFormat::Rgba8UnormSrgb,
|
TextureFormat::Rgba8UnormSrgb,
|
||||||
RenderAssetUsages::default(),
|
RenderAssetUsages::default(),
|
||||||
);
|
);
|
||||||
// Only mip level 0 is decoded, so minification must clamp rather than
|
// The GS clamps; bevy's default is Repeat. This was `..default()` with a
|
||||||
// invent a level the disc does not have here.
|
// comment claiming the sampler clamped, which it did not -- the default is
|
||||||
|
// ImageAddressMode::Repeat. A tile edge therefore wrapped onto its opposite
|
||||||
|
// edge instead of repeating the border texel, which is the seam you see when
|
||||||
|
// a patch runs off its texture. See gc_formats::gs::clamp01.
|
||||||
|
//
|
||||||
|
// Only mip level 0 is decoded, so there is no chain to interpolate: the
|
||||||
|
// mipmap filter is Nearest because it samples that single level either way,
|
||||||
|
// and gs::mip_level would return 0 for every patch until levels are decoded.
|
||||||
image.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
|
image.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
|
||||||
label: Some("tfrag texture".into()),
|
label: Some("tfrag texture".into()),
|
||||||
mipmap_filter: ImageFilterMode::Linear,
|
address_mode_u: ImageAddressMode::ClampToEdge,
|
||||||
|
address_mode_v: ImageAddressMode::ClampToEdge,
|
||||||
|
mipmap_filter: ImageFilterMode::Nearest,
|
||||||
mag_filter: ImageFilterMode::Linear,
|
mag_filter: ImageFilterMode::Linear,
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-3
@@ -717,9 +717,11 @@ lookup, and it is the main thing inherited from ReRAC.
|
|||||||
the GS's `AFAIL` bit reads as "fail if above the reference" and on the
|
the GS's `AFAIL` bit reads as "fail if above the reference" and on the
|
||||||
hardware means the opposite -- it discards where alpha is **below**; and the
|
hardware means the opposite -- it discards where alpha is **below**; and the
|
||||||
opaque bit must be kept out of the comparison, or `0x80` satisfies every
|
opaque bit must be kept out of the comparison, or `0x80` satisfies every
|
||||||
threshold and nothing is ever discarded. **No wgpu work has started** --
|
threshold and nothing is ever discarded. **The engine is on bevy 0.19, not raw wgpu**, so these rules reach the GPU
|
||||||
this is the rule layer the renderer will feed it, done first because it is
|
through bevy's sampler rather than wgpu directly. One is now wired: the
|
||||||
checkable without a GPU.
|
texture sampler was `..default()` with a comment claiming it clamped, and
|
||||||
|
bevy's default is `Repeat` -- see `image_of` in `crates/gc-engine`.
|
||||||
|
**No draw-path or shader work has started.**
|
||||||
- [ ] VU0/VU1 skinning and lighting on the PS2 float model
|
- [ ] VU0/VU1 skinning and lighting on the PS2 float model
|
||||||
- [ ] LOD/morph/cull per `TfragProc` and `TieProc`
|
- [ ] LOD/morph/cull per `TfragProc` and `TieProc`
|
||||||
- [ ] Occlusion culling, fog, shadows
|
- [ ] Occlusion culling, fog, shadows
|
||||||
|
|||||||
Reference in New Issue
Block a user