These changes were made and verified earlier this session (the all-cyan bug
fix is already recorded as SOLVED in project memory) but never got committed --
render_final.py has referenced dpl_sampler as a module in its docstring since
ac4142e7, yet dpl_sampler.py itself was never added, leaving the repo unable
to run the M4b/M4c/M4d renderer scripts that all import it.
texstore.py: fix the texel word format. SVT texels are [pad,B,G,R] (ground
truth: dpl3-revive patha/vrboard.py do_texels + stage_assets.py); reading RGB
as bytes (0,1,2)=(pad,B,G) zeroed red, producing the all-cyan render. Correct
read is bytes (3,2,1).
dpl_sampler.py: the authentic IG-board texture-value model (wrap repeat/clamp,
near-black CUT keying) factored into its own tested module, distilled from the
shipped libDPL headers -- see IG-SHADING-MODEL.md (also added, referenced by
render_final.py's docstring but likewise never committed).
igc_exec.py: OP48/OP49 candidate opcodes (pixel global-X/Y seed into mem) added
to the CPU golden-model executor alongside the existing SCMEMA seed handling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
131 lines
6.1 KiB
Python
131 lines
6.1 KiB
Python
"""dpl_sampler.py -- the authentic IG-board texture-value model, distilled from
|
|
the shipped Division library headers (BT411 engine/MUNGA_L4/libDPL/dpl/dpltypes.h
|
|
`dpl_TEX_VALUE` + dpl.h `dpl_SetTextureProperty`) cross-checked against the real
|
|
renderer (dpl3-revive patha/vrview_gl.py, stage_assets.py, vrboard.py).
|
|
|
|
The board applies a texmap over a polygon under per-texture PROPERTY values
|
|
(`dpl_TEX_PROP` set to a `dpl_TEX_VALUE`). Two affect the rasterizer's *output*:
|
|
|
|
* WRAP (dpl_tex_prop_wrap / wrapu / wrapv):
|
|
dpl_tex_value_repeat -> texel index wraps modulo the map size (default)
|
|
dpl_tex_value_clamp -> texel index clamps to the edge
|
|
A tiling terrain uses REPEAT; a one-shot decal / HUD label / emblem CLAMPs
|
|
(tiling a "PLAYER 1" label is the tell-tale wrong-wrap artefact).
|
|
|
|
* ALPHA / cutout (dpl_tex_prop_alpha, dpl_tex_value_cut):
|
|
the texel word is [pad, B, G, R] (SVT xbgr) -- there is NO stored alpha
|
|
channel. The board realises `dpl_tex_value_cut` (PUNCH: the cockpit /
|
|
effect-card cutout that retires the "opaque rectangle" bug) by NEAR-BLACK
|
|
KEYING at draw time: a texel whose R+G+B is <= a small threshold is a
|
|
HOLE (the poly is not drawn there, and does not claim z). Ground truth:
|
|
patha/vrview_gl.py ("alpha cutout (texel sum <= 24/255)").
|
|
|
|
Filtering (point/bilinear/...) is a quality axis; pvision runs texmode 0 = point
|
|
(nearest), which is what the rasterizer models.
|
|
|
|
Pure + numpy-vectorized so the frame renderer applies it over a whole tile at
|
|
once; independent of the wire capture so it is conformance-tested on synthetic
|
|
input (see __main__).
|
|
"""
|
|
import numpy as np
|
|
|
|
# dpl_TEX_VALUE tokens we act on (names mirror dpltypes.h)
|
|
WRAP_REPEAT = 'repeat'
|
|
WRAP_CLAMP = 'clamp'
|
|
CUT_OFF = 'off' # dpl_tex_value off -> opaque
|
|
CUT_KEY = 'cut' # dpl_tex_value_cut -> near-black keying (PUNCH)
|
|
|
|
# Near-black cutout threshold (R+G+B), from patha/vrview_gl.py (sum <= 24/255).
|
|
CUT_THRESHOLD = 24
|
|
|
|
|
|
# The wire's per-texmap `mode` word (0x1a upload header word[4]) selects the
|
|
# board's internal storage path (mode 1 = 8-bit). It is NOT reliably a cutout
|
|
# flag -- cutout is a per-MATERIAL property (dpl_SetTextureProperty), which the
|
|
# wire capture does not yet expose per drawn surface. So wrap defaults to REPEAT
|
|
# (the board default) and cutout defaults to OFF until a SetTextureProperty
|
|
# stream is decoded; callers can override per texmap.
|
|
def mode_to_texflags(mode):
|
|
"""Decode a wire texmap `mode` word into (wrap_u, wrap_v, cut_mode)."""
|
|
return WRAP_REPEAT, WRAP_REPEAT, CUT_OFF
|
|
|
|
|
|
def wrap_index(coord, size, wrap):
|
|
"""Vectorized texel index for integer texel coords under a wrap mode."""
|
|
c = np.asarray(coord, dtype=np.int64)
|
|
if wrap == WRAP_CLAMP:
|
|
return np.clip(c, 0, size - 1)
|
|
return np.mod(c, size) # REPEAT (default)
|
|
|
|
|
|
def composite(dst_rgb, inside, src_rgb, cut_mode, cut_thresh=CUT_THRESHOLD):
|
|
"""Apply a textured polygon's texels to the framebuffer under the board
|
|
texture-value model. Returns the boolean DRAW mask (pixels the poly actually
|
|
owns -- the caller gates the z-buffer write with it so CUT holes don't claim
|
|
z).
|
|
|
|
dst_rgb : HxWx3 uint8 framebuffer (modified in place on the draw mask)
|
|
inside : HxW bool -- candidate pixels (edge AND z test already done)
|
|
src_rgb : HxWx3 -- sampled texel colour (RGB)
|
|
cut_mode : CUT_OFF (opaque) or CUT_KEY (near-black keying)
|
|
"""
|
|
if cut_mode == CUT_KEY:
|
|
lum = src_rgb.astype(np.int32).sum(axis=2)
|
|
draw = inside & (lum > cut_thresh) # near-black texels are holes
|
|
else:
|
|
draw = inside
|
|
dst_rgb[draw] = src_rgb[draw]
|
|
return draw
|
|
|
|
|
|
def composite_masked(dst_rgb, sel, src_rgb_compact, cut_mode, cut_thresh=CUT_THRESHOLD):
|
|
"""Like composite(), but src_rgb_compact holds only the SEL-true samples
|
|
(shape (K, C), K = sel.sum()), in the order sel's True entries appear under
|
|
numpy boolean indexing. Lets a caller sample/composite only the pixels a
|
|
texture actually covers instead of the whole frame -- the same board
|
|
texture-value model, just without doing (frame_size / coverage) redundant
|
|
work when many textures each cover a small fraction of the frame (see
|
|
gpu_raster.Renderer.frame, where this was the dominant per-frame cost).
|
|
"""
|
|
if cut_mode == CUT_KEY:
|
|
lum = src_rgb_compact.astype(np.int32).sum(axis=1)
|
|
keep = lum > cut_thresh
|
|
idx = np.flatnonzero(sel)[keep]
|
|
dst_rgb.reshape(-1, dst_rgb.shape[-1])[idx] = src_rgb_compact[keep]
|
|
else:
|
|
dst_rgb[sel] = src_rgb_compact
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# -- wrap conformance --
|
|
assert list(wrap_index([-1, 0, 3, 4], 4, WRAP_REPEAT)) == [3, 0, 3, 0]
|
|
assert list(wrap_index([-1, 0, 3, 4], 4, WRAP_CLAMP)) == [0, 0, 3, 3]
|
|
|
|
# -- cutout conformance on a 2x2 scene --
|
|
dst = np.zeros((2, 2, 3), np.uint8)
|
|
inside = np.ones((2, 2), bool)
|
|
# (0,0) near-black (hole under CUT), others bright
|
|
src = np.array([[[2, 3, 4], [200, 10, 10]],
|
|
[[10, 200, 10], [10, 10, 200]]], np.uint8)
|
|
|
|
# OFF: everything drawn, keying ignored
|
|
d = dst.copy(); m = composite(d, inside, src, CUT_OFF)
|
|
assert m.all() and (d[0, 0] == [2, 3, 4]).all()
|
|
|
|
# CUT: the near-black texel (sum 9 <= 24) is a hole (not drawn, no z claim)
|
|
d = dst.copy(); m = composite(d, inside, src, CUT_KEY)
|
|
assert not m[0, 0] and m[0, 1] and m[1, 0] and m[1, 1]
|
|
assert (d[0, 0] == 0).all() and (d[0, 1] == [200, 10, 10]).all()
|
|
|
|
# -- composite_masked must match composite() exactly, on a partial `sel`
|
|
# (a subset of `inside`, as gpu_raster.Renderer.frame uses it per texture) --
|
|
sel = np.array([[True, False], [True, True]]) # skip (0,1) this time
|
|
for cm in (CUT_OFF, CUT_KEY):
|
|
d_full = dst.copy(); composite(d_full, sel, src, cm)
|
|
d_masked = dst.copy(); composite_masked(d_masked, sel, src[sel], cm)
|
|
assert (d_full == d_masked).all(), (cm, d_full, d_masked)
|
|
print("composite_masked matches composite() on a partial mask, both cut modes: PASS")
|
|
|
|
print("dpl_sampler conformance PASS "
|
|
"(repeat/clamp wrap + off/cut near-black keying)")
|