VPX render: port the restoration gallery shading model; renderer collab doc
Device renderer (vpxlog.cpp, commit-copy of the fork source): - View-flush fog decode (enable/near/far/rgb at d+72.., mode 5 linear, garbage-guarded; VPX_NOFOG=1 diagnostic) applied per fragment. - Full dpl_MATERIAL parse: emissive/ambient/diffuse/opacity/specular/ shininess (opacity+specular parsed, not yet applied); fixed-offset texture/ramp refs with lazy resolution, rebake on material/ramp reflush. - Gallery shading model (restoration/gallery_template.html) as a GLSL 1.10 program over the existing immediate-mode path, fixed-function fallback: c = (matAmb*sceneAmb + matDiff*mix(ramp.lo,ramp.hi,acc))*tex + matEmis, linear fog with immunity knob. Textured polys use an identity light-ramp (texels already baked through the material ramp). Newell flat normals for polys without wire normals. Interim sun/ambient until the light node is decoded: VPX_AMBIENT / VPX_SUN env overrides. RENDERER-COLLAB.md: shared working doc with the external-renderer team -- verified FIFO action/node tables, struct offsets, scene assembly and coordinate conventions, available data taps, open questions (incl. the type-7 object-vs-light flush conflict). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
# Division Renderer Collaboration Log
|
||||
|
||||
Shared working doc for the two renderer efforts against the VPX/Division
|
||||
hardware interface:
|
||||
|
||||
- **In-process renderer** — GL HLE inside the DOSBox-X fork's VPX device
|
||||
(`src/src/hardware/vpxlog.cpp`, commit-copy `vpx-device/vpxlog.cpp`).
|
||||
Maintained by Cyd + Claude (this repo's sessions).
|
||||
- **External renderer** — separate approach by the graphics developer +
|
||||
their Claude instance. *(Fill in your section below.)*
|
||||
|
||||
**How to use this file:** each session (human or Claude) reads it at start,
|
||||
appends to its own section and the Log, and commits. Don't rewrite the other
|
||||
team's sections — append questions/answers instead. Commit messages prefixed
|
||||
`collab:` so both sides can `git log --grep=collab:` for changes.
|
||||
|
||||
---
|
||||
|
||||
## Ground-truth sources (in priority order)
|
||||
|
||||
1. `sda4/DPL3/LIBDPL/DPLTYPES.H` + `DPL.H` — the dVS/DPL node structs and API.
|
||||
2. `sda4/DPL3/VRENDER/` — Division's own i860 renderer source (rasterizer
|
||||
semantics; the ramp/texture/light questions below should be settled here).
|
||||
3. `ALPHA_1/REL410/BT/BTDPL.INI` — per-arena ground truth the game actually
|
||||
loads: `fog=start end r g b`, `light=r g b pitch yaw roll`, `ambient=`,
|
||||
`clip=`, `objectpath=` (arena content dirs), keyed by egg map/time/weather.
|
||||
4. `restoration/` — WebGL re-render of the on-disk scene formats
|
||||
(`divformats.py` parsers, `gallery_template.html` shading model). Proven
|
||||
against 26 recovered scenes; its mesh shader is the reference shading
|
||||
model (see below).
|
||||
5. Live wire captures: `VPXLOG=<file>` logs every port I/O; scratch tooling
|
||||
(`dump_nodes.py`-style parsers) has decoded node flushes from fifodumps.
|
||||
|
||||
## VPX board interface — verified on live captures
|
||||
|
||||
Register offsets (host side of the i860 board FIFO link):
|
||||
`0` inputData (board→host), `1` outputData (host→board), `2` inputStatus
|
||||
(bit0 = byte available), `3` outputStatus (always ready), `4/5` FIFO data
|
||||
port link B low/high bytes, `16` resetRoot, `17` analyseRoot.
|
||||
|
||||
### Action codes (FIFO message stream)
|
||||
|
||||
| action | meaning |
|
||||
|---|---|
|
||||
| 1 | create `[type][name]` |
|
||||
| 3 | flush `[name][type][struct]` — struct offsets below are into the payload `d`, where `d+8` = struct offset 0 |
|
||||
| 7 | dcs_link `[parent][child]` — articulation tree |
|
||||
| 9 | draw_scene — frame commit |
|
||||
| 11 | list_add `[parent][child]` |
|
||||
| 23 | set_geom_verts header `[name][0][n_verts][stride_floats]` + vertex bursts; stride 3..16 floats/vert: xyz, +uv at 3 (stride 5) or 6 (stride ≥8), +normal at 3 when stride ≥8 |
|
||||
| 25 | set_geom_conns header `[name][n_polys][loop_len][0]` + index words (closing duplicate dropped) |
|
||||
| 26 | texel upload header `[node][nbytes][w][h]` + texel bursts (4/8/16 bpp, bpp inferred from byte/texel ratio) |
|
||||
| 31 | per-frame camera `[?][view][3x3 rows][eye]` |
|
||||
|
||||
### Node types (create/flush `type` field)
|
||||
|
||||
| type | node | verified layout (flush payload `d`) |
|
||||
|---|---|---|
|
||||
| 2 | zone | raw-logged only |
|
||||
| 3 | view | win l/b/r/t/dist `d+24..40`, vw/vh `d+44/48`, near/far `d+52/56`, back_color `d+60..68`, **fog enable/mode `d+72`, near `d+76`, far `d+80`, rgb `d+84..92`** (mode 5 = linear; game ANIMATES fog and re-flushes ~27×; boot flushes carry heap garbage — sanity-check floats) |
|
||||
| 4 | instance | object ref scanned at `d+8..` (resolves to a type-7 node) |
|
||||
| 5 | dcs | 4×4 row-major at floats 4..19 (`d+16..`); row 3 = translation; world = local × parent (row-vector convention) |
|
||||
| 6 | lmodel | raw-logged only |
|
||||
| 7 | **object** (instance target in the scene-graph walker) — **CONFLICT:** the flush handler currently raw-dumps `t==7` as a *light* candidate ("t7 light=" lines in the tex log). Both can't be right; the real dpl_LIGHT wire type is still unidentified. Reconcile against DPLTYPES.H + a live dump. |
|
||||
| 9 | geogroup | material ref at `d+64` |
|
||||
| 11 | material | texture ref `d+8`, ramp ref `d+20`, emissive `d+24..32`, ambient `d+36..44`, diffuse `d+48..56`, opacity `d+60`, specular `d+72..80`, shininess `d+84` (84-byte struct; refs resolve lazily — a ref may precede its create) |
|
||||
| 12 | dpl_TEXTURE params | texmap ref `d+8`, then minify/magnify/alpha/wrap_u/wrap_v/detail (u32s) + u0/v0/du/dv/anim_time (floats) + anim_bhv — logged (`t12` lines), **semantics not yet applied** |
|
||||
| 13 | texmap header | texels ptr, u_size, v_size, bits_per_texel (+ hwareSize/hwareOffs/bilinear) |
|
||||
| 14 | ramp | lo RGB `d+8..16`, hi RGB `d+20..28`; texels are intensities colorized through the material's ramp |
|
||||
|
||||
### Scene assembly & conventions
|
||||
|
||||
- Draw hierarchy: DCS (type 5) children (via list_add) → instance (4) →
|
||||
object (7) → **first LOD child** (host keeps active LOD at list head; the
|
||||
wire carries no switch distances) → geogroups (9) → geometry.
|
||||
- World is **y-down** (DCS matrices carry reflections); Division screen x is
|
||||
mirrored vs GL eye x (flip x after projection).
|
||||
- Vertices reach the renderer already in world space; the modelview holds
|
||||
only the camera (action 31: row-major world→eye rotation + eye pos,
|
||||
`eye_p = R·(p − e)`).
|
||||
- Frustum from view win[]: `glFrustum(win[l/r/b/t] · near / win[4], …)`.
|
||||
- **The camera only goes live after the first analog (throttle/stick)
|
||||
input** — until then the game submits no worklist and the screen is the
|
||||
back_color clear. Hands-off runs look like "solid sky"; check the radar
|
||||
head (win0 BMP dump) to confirm the mission actually loaded.
|
||||
|
||||
## In-process renderer — status (2026-07-04)
|
||||
|
||||
The device now runs the restoration gallery's shading model verbatim as a
|
||||
GLSL 1.10 program (fixed-function fallback; stderr prints
|
||||
`VPX render: gallery shading model active (GLSL)`):
|
||||
|
||||
```
|
||||
acc = Σ_i lightCol_i · max(dot(N, −lightDir_i), 0) (world space, 2 dir lights)
|
||||
lit = mix(ramp.lo, ramp.hi, clamp(acc, 0, 1))
|
||||
c = (matAmb·sceneAmb + matDiff·lit) · tex + matEmis
|
||||
out = mix(fogColor, c, max(clamp((fogEnd−d)/(fogEnd−fogStart), 0, 1), immune))
|
||||
```
|
||||
|
||||
Interim until the light node is decoded: hardcoded sun + sceneAmb 0.45,
|
||||
overridable via `VPX_AMBIENT="r,g,b"` / `VPX_SUN="r,g,b,pitch,yaw"` (deg,
|
||||
y-down). Fog comes from the type-3 view flush. Opacity/specular parsed but
|
||||
not applied. Polys without wire normals get Newell flat face normals.
|
||||
|
||||
**Ramp interpretation (open):** wire textures are intensity texels baked
|
||||
through the material ramp at upload; the gallery applies the ramp to *light*
|
||||
(disk textures are RGB). The device currently avoids double-application by
|
||||
using an identity light-ramp on textured polys. VRENDER source should settle
|
||||
whether Division ramps texels, light, or both.
|
||||
|
||||
## Data taps available to an external renderer (today)
|
||||
|
||||
- `VPXLOG=<path>` (host env): full port-I/O log incl. every FIFO byte —
|
||||
replayable; parse with the action/type tables above.
|
||||
- `VPX_DUMPDIR=<dir>` + touch `<dir>/DUMP`: per-head BMP dumps (`win<g>.bmp`,
|
||||
g=0 radar, 3/4 MFD heads, 5-9 exploded singles).
|
||||
- Device tex log: `t7`/`t12`/`t13` raw flush lines for undecoded nodes.
|
||||
- `RIO_TAP=<path>`: serial wire tap (not renderer-relevant, but proves rig
|
||||
liveness).
|
||||
- **Offer:** if a structured per-frame scene export (JSON/binary worklist
|
||||
after graph assembly, i.e. post-DCS world-space polys + materials) would
|
||||
feed your renderer better than raw FIFO, ask here — straightforward to add
|
||||
behind an env var.
|
||||
|
||||
## Open questions (either side, answer in place with date)
|
||||
|
||||
1. Real dpl_LIGHT wire type + layout (type 7 conflict above). BTDPL.INI
|
||||
`light=` lines give expected values to match against.
|
||||
2. Type-12 texture param semantics (wrap/clamp/uv-window/animate) — cause of
|
||||
smeared wall textures?
|
||||
3. Ramp on texel vs ramp on light (VRENDER).
|
||||
4. Terrain + sky in arena missions: what content/nodes carry them? (Live
|
||||
arena1 renders structures but almost no ground; sky is back_color only.)
|
||||
5. Opacity blending order/mode (glass cockpits, effects).
|
||||
6. Scene ambient: which node carries it at runtime (lmodel type 6?).
|
||||
|
||||
## External renderer — approach & status
|
||||
|
||||
*(Yours — please fill in: approach, inputs consumed, what you need from the
|
||||
device side, decisions taken. Append, date-stamp.)*
|
||||
|
||||
## Log
|
||||
|
||||
- **2026-07-04 (Claude, in-process side):** doc created. Gallery shading
|
||||
model ported into the device and verified live in arena1 (lit faces,
|
||||
plausible wire fog). Full material parse landed. Next in-process steps:
|
||||
light node decode, type-12 params, restoration-toolkit reference render of
|
||||
arena1 content.
|
||||
+491
-43
@@ -434,6 +434,7 @@ static VDBPalette vdb_pal[3];
|
||||
#include <GL/gl.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <math.h>
|
||||
|
||||
static unsigned rd_u32(const unsigned char *p) {
|
||||
return (unsigned)p[0] | ((unsigned)p[1] << 8) |
|
||||
@@ -444,13 +445,34 @@ static float rd_f32(const unsigned char *p) {
|
||||
}
|
||||
|
||||
struct VCol { float c[3]; };
|
||||
/* Full dpl_MATERIAL shading params. The shading model is ported from the
|
||||
* restoration viewer (restoration/gallery_template.html meshShader), proven
|
||||
* against the recovered .SCN scenes:
|
||||
* acc = sum_i lightCol_i * max(dot(N, -lightDir_i), 0)
|
||||
* c = (amb*sceneAmb + diff*mix(ramp.lo, ramp.hi, acc)) * tex + emis */
|
||||
struct VMatG {
|
||||
float amb[3], diff[3], emis[3];
|
||||
float opacity, spec[3], shin;
|
||||
VMatG() : opacity(1), shin(0) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
amb[i] = 0.6f; diff[i] = 0.6f; emis[i] = 0.0f; spec[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
};
|
||||
struct VPoly {
|
||||
float rgb[3];
|
||||
float rgb[3]; /* material diffuse */
|
||||
float amb[3], emis[3]; /* material ambient / emissive */
|
||||
float ramp0[3], ramp1[3]; /* shading ramp lo/hi (identity when the
|
||||
* ramp is already baked into the texture) */
|
||||
std::vector<float> xyz; /* x,y,z triples */
|
||||
std::vector<float> uv; /* u,v pairs (empty = untextured) */
|
||||
std::vector<float> nrm; /* nx,ny,nz triples (empty = unlit) */
|
||||
std::vector<float> nrm; /* nx,ny,nz triples (empty = flat normal) */
|
||||
unsigned matkey; /* material name for texture lookup, 0 = none */
|
||||
VPoly() : matkey(0) {}
|
||||
VPoly() : matkey(0) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
amb[i] = 0.6f; emis[i] = 0.0f; ramp0[i] = 0.0f; ramp1[i] = 1.0f;
|
||||
}
|
||||
}
|
||||
};
|
||||
/* Baked RGBA texture (texture intensity x material ramp), keyed by material.
|
||||
* Shared with the GL thread under rt_lock; ver bumps force re-upload. */
|
||||
@@ -460,14 +482,18 @@ struct VFrame {
|
||||
float bg[3];
|
||||
float win[5]; /* l, b, r, t, window-plane distance */
|
||||
float nearp, farp;
|
||||
bool fog; /* dpl_SetViewFog: linear haze near..far */
|
||||
float fogn, fogf, fogc[3];
|
||||
int vw, vh;
|
||||
bool has_cam;
|
||||
bool ydown; /* game world is y-down (DCS reflections) */
|
||||
float rot[9], eye[3]; /* row-major rotation; eye = R*(p - e) */
|
||||
std::vector<VPoly> polys;
|
||||
VFrame() : valid(false), nearp(2), farp(12000), vw(832), vh(512),
|
||||
VFrame() : valid(false), nearp(2), farp(12000), fog(false),
|
||||
fogn(0), fogf(0), vw(832), vh(512),
|
||||
has_cam(false), ydown(false) {
|
||||
bg[0] = bg[1] = bg[2] = 0;
|
||||
fogc[0] = fogc[1] = fogc[2] = 0;
|
||||
win[0] = -1; win[1] = -0.6153846f; win[2] = 1; win[3] = 0.6153846f;
|
||||
win[4] = 1.3f;
|
||||
}
|
||||
@@ -484,7 +510,7 @@ static struct VScene {
|
||||
std::map<unsigned, std::vector<float> > uvs; /* geometry -> u,v */
|
||||
std::map<unsigned, std::vector<float> > nrms; /* geometry -> nx,ny,nz */
|
||||
std::map<unsigned, std::vector<std::vector<int> > > polys;
|
||||
std::map<unsigned, VCol> mat; /* material -> RGB */
|
||||
std::map<unsigned, VMatG> mat; /* material -> shading */
|
||||
std::map<unsigned, unsigned> ggmat; /* geogroup -> material */
|
||||
std::map<unsigned, std::vector<unsigned> > children;
|
||||
/* game (full DPL) hierarchy: instance placement + articulation */
|
||||
@@ -556,6 +582,183 @@ static bool rt_new = false;
|
||||
static unsigned long rt_frames = 0;
|
||||
static std::map<unsigned, TexImg> rt_texs; /* material -> baked RGBA (rt_lock) */
|
||||
|
||||
/* ---- GLSL port of the restoration viewer's shading model ----------------
|
||||
* Source: restoration/gallery_template.html meshShader (proven against the
|
||||
* recovered .SCN scenes). Per fragment:
|
||||
* acc = sum_i lightCol_i * max(dot(N, -lightDir_i), 0) (world space)
|
||||
* lit = mix(ramp.lo, ramp.hi, clamp(acc, 0, 1))
|
||||
* c = (matAmb*sceneAmb + matDiff*lit) * tex + matEmis
|
||||
* out = mix(fogColor, c, max(clamp((end-d)/(end-start),0,1), immune))
|
||||
* Geometry reaches GL already in world space (modelview = camera only), so
|
||||
* normals and light directions stay in world coordinates like the gallery.
|
||||
* Falls back to the old fixed-function path when GLSL is unavailable. */
|
||||
#ifndef GL_FRAGMENT_SHADER
|
||||
#define GL_FRAGMENT_SHADER 0x8B30
|
||||
#endif
|
||||
#ifndef GL_VERTEX_SHADER
|
||||
#define GL_VERTEX_SHADER 0x8B31
|
||||
#endif
|
||||
#ifndef GL_COMPILE_STATUS
|
||||
#define GL_COMPILE_STATUS 0x8B81
|
||||
#endif
|
||||
#ifndef GL_LINK_STATUS
|
||||
#define GL_LINK_STATUS 0x8B82
|
||||
#endif
|
||||
typedef GLuint (APIENTRY *pfnCreateShader)(GLenum);
|
||||
typedef void (APIENTRY *pfnShaderSource)(GLuint, GLsizei, const char **,
|
||||
const GLint *);
|
||||
typedef void (APIENTRY *pfnCompileShader)(GLuint);
|
||||
typedef void (APIENTRY *pfnGetShaderiv)(GLuint, GLenum, GLint *);
|
||||
typedef void (APIENTRY *pfnGetShaderInfoLog)(GLuint, GLsizei, GLsizei *,
|
||||
char *);
|
||||
typedef GLuint (APIENTRY *pfnCreateProgram)(void);
|
||||
typedef void (APIENTRY *pfnAttachShader)(GLuint, GLuint);
|
||||
typedef void (APIENTRY *pfnLinkProgram)(GLuint);
|
||||
typedef void (APIENTRY *pfnGetProgramiv)(GLuint, GLenum, GLint *);
|
||||
typedef void (APIENTRY *pfnUseProgram)(GLuint);
|
||||
typedef GLint (APIENTRY *pfnGetUniformLocation)(GLuint, const char *);
|
||||
typedef void (APIENTRY *pfnUniform3f)(GLint, GLfloat, GLfloat, GLfloat);
|
||||
typedef void (APIENTRY *pfnUniform1f)(GLint, GLfloat);
|
||||
typedef void (APIENTRY *pfnUniform1i)(GLint, GLint);
|
||||
|
||||
static struct RShade {
|
||||
bool tried; GLuint prog;
|
||||
pfnUseProgram use; pfnUniform3f u3f; pfnUniform1f u1f;
|
||||
GLint uAmbScene, uMatAmb, uMatDiff, uMatEmis, uFogColor, uViewFwd;
|
||||
GLint uLightDir0, uLightCol0, uLightDir1, uLightCol1;
|
||||
GLint uRamp0, uRamp1, uFog, uHasTex;
|
||||
} rsh = { false, 0, NULL, NULL, NULL,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
|
||||
|
||||
static const char *rsh_vs_src =
|
||||
"varying vec3 vN; varying vec2 vUV; varying float vDist;\n"
|
||||
"void main() {\n"
|
||||
" vec4 v = gl_ModelViewMatrix * gl_Vertex;\n"
|
||||
" vDist = length(v.xyz);\n"
|
||||
" vN = gl_Normal;\n"
|
||||
" vUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;\n"
|
||||
" gl_Position = gl_ProjectionMatrix * v;\n"
|
||||
"}\n";
|
||||
static const char *rsh_fs_src =
|
||||
"varying vec3 vN; varying vec2 vUV; varying float vDist;\n"
|
||||
"uniform vec3 uAmbScene, uMatAmb, uMatDiff, uMatEmis, uFogColor, uViewFwd;\n"
|
||||
"uniform vec3 uLightDir0, uLightCol0, uLightDir1, uLightCol1;\n"
|
||||
"uniform vec3 uRamp0, uRamp1, uFog;\n" /* uFog: start, end, immune */
|
||||
"uniform float uHasTex; uniform sampler2D uTex;\n"
|
||||
"void main() {\n"
|
||||
" vec3 N = normalize(vN);\n"
|
||||
" if (dot(N, uViewFwd) > 0.0) N = -N;\n"
|
||||
" vec3 acc = uLightCol0 * max(dot(N, -uLightDir0), 0.0)\n"
|
||||
" + uLightCol1 * max(dot(N, -uLightDir1), 0.0);\n"
|
||||
" vec3 lit = mix(uRamp0, uRamp1, clamp(acc, 0.0, 1.0));\n"
|
||||
" vec3 tex = mix(vec3(1.0), texture2D(uTex, vUV).rgb, uHasTex);\n"
|
||||
" vec3 c = (uMatAmb * uAmbScene + uMatDiff * lit) * tex + uMatEmis;\n"
|
||||
" float fr = clamp((uFog.y - vDist) / max(uFog.y - uFog.x, 0.001),\n"
|
||||
" 0.0, 1.0);\n"
|
||||
" fr = max(fr, uFog.z);\n"
|
||||
" gl_FragColor = vec4(mix(uFogColor, c, fr), 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
static GLuint rsh_compile(GLenum kind, const char *src,
|
||||
pfnCreateShader cs, pfnShaderSource ss,
|
||||
pfnCompileShader cc, pfnGetShaderiv gi,
|
||||
pfnGetShaderInfoLog il) {
|
||||
GLuint s = cs(kind);
|
||||
ss(s, 1, &src, NULL);
|
||||
cc(s);
|
||||
GLint ok = 0;
|
||||
gi(s, GL_COMPILE_STATUS, &ok);
|
||||
if (!ok) {
|
||||
char log[512]; GLsizei n = 0;
|
||||
il(s, sizeof log, &n, log);
|
||||
fprintf(stderr, "VPX render: shader compile failed: %.*s\n",
|
||||
(int)n, log);
|
||||
return 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static void rsh_init(void) {
|
||||
rsh.tried = true;
|
||||
pfnCreateShader cs = (pfnCreateShader)wglGetProcAddress("glCreateShader");
|
||||
pfnShaderSource ss = (pfnShaderSource)wglGetProcAddress("glShaderSource");
|
||||
pfnCompileShader cc = (pfnCompileShader)wglGetProcAddress("glCompileShader");
|
||||
pfnGetShaderiv gi = (pfnGetShaderiv)wglGetProcAddress("glGetShaderiv");
|
||||
pfnGetShaderInfoLog il = (pfnGetShaderInfoLog)wglGetProcAddress("glGetShaderInfoLog");
|
||||
pfnCreateProgram cp = (pfnCreateProgram)wglGetProcAddress("glCreateProgram");
|
||||
pfnAttachShader as = (pfnAttachShader)wglGetProcAddress("glAttachShader");
|
||||
pfnLinkProgram lp = (pfnLinkProgram)wglGetProcAddress("glLinkProgram");
|
||||
pfnGetProgramiv gp = (pfnGetProgramiv)wglGetProcAddress("glGetProgramiv");
|
||||
pfnGetUniformLocation gu = (pfnGetUniformLocation)wglGetProcAddress("glGetUniformLocation");
|
||||
pfnUniform1i u1i = (pfnUniform1i)wglGetProcAddress("glUniform1i");
|
||||
rsh.use = (pfnUseProgram)wglGetProcAddress("glUseProgram");
|
||||
rsh.u3f = (pfnUniform3f)wglGetProcAddress("glUniform3f");
|
||||
rsh.u1f = (pfnUniform1f)wglGetProcAddress("glUniform1f");
|
||||
if (!cs || !ss || !cc || !gi || !il || !cp || !as || !lp || !gp || !gu ||
|
||||
!u1i || !rsh.use || !rsh.u3f || !rsh.u1f) {
|
||||
fprintf(stderr, "VPX render: GLSL entry points unavailable, "
|
||||
"using fixed-function fallback\n");
|
||||
return;
|
||||
}
|
||||
GLuint vs = rsh_compile(GL_VERTEX_SHADER, rsh_vs_src, cs, ss, cc, gi, il);
|
||||
GLuint fs = rsh_compile(GL_FRAGMENT_SHADER, rsh_fs_src, cs, ss, cc, gi, il);
|
||||
if (!vs || !fs) return;
|
||||
GLuint pr = cp();
|
||||
as(pr, vs); as(pr, fs);
|
||||
lp(pr);
|
||||
GLint ok = 0;
|
||||
gp(pr, GL_LINK_STATUS, &ok);
|
||||
if (!ok) {
|
||||
fprintf(stderr, "VPX render: shader link failed, "
|
||||
"using fixed-function fallback\n");
|
||||
return;
|
||||
}
|
||||
rsh.uAmbScene = gu(pr, "uAmbScene");
|
||||
rsh.uMatAmb = gu(pr, "uMatAmb");
|
||||
rsh.uMatDiff = gu(pr, "uMatDiff");
|
||||
rsh.uMatEmis = gu(pr, "uMatEmis");
|
||||
rsh.uFogColor = gu(pr, "uFogColor");
|
||||
rsh.uViewFwd = gu(pr, "uViewFwd");
|
||||
rsh.uLightDir0 = gu(pr, "uLightDir0");
|
||||
rsh.uLightCol0 = gu(pr, "uLightCol0");
|
||||
rsh.uLightDir1 = gu(pr, "uLightDir1");
|
||||
rsh.uLightCol1 = gu(pr, "uLightCol1");
|
||||
rsh.uRamp0 = gu(pr, "uRamp0");
|
||||
rsh.uRamp1 = gu(pr, "uRamp1");
|
||||
rsh.uFog = gu(pr, "uFog");
|
||||
rsh.uHasTex = gu(pr, "uHasTex");
|
||||
rsh.use(pr);
|
||||
u1i(gu(pr, "uTex"), 0);
|
||||
rsh.use(0);
|
||||
rsh.prog = pr;
|
||||
fprintf(stderr, "VPX render: gallery shading model active (GLSL)\n");
|
||||
}
|
||||
|
||||
/* scene lighting until the type-7 light node decode lands (step 2):
|
||||
* defaults keep the previous hardcoded sun; VPX_AMBIENT="r,g,b" and
|
||||
* VPX_SUN="r,g,b,pitch,yaw" (degrees, y-down world) override for tuning */
|
||||
static float rsh_amb[3] = { 0.45f, 0.45f, 0.45f };
|
||||
static float rsh_sundir[3] = { -0.3412f, 0.8286f, -0.3899f }; /* travel dir */
|
||||
static float rsh_suncol[3] = { 0.80f, 0.80f, 0.78f };
|
||||
static void rsh_env(void) {
|
||||
static bool done = false;
|
||||
if (done) return;
|
||||
done = true;
|
||||
const char *a = getenv("VPX_AMBIENT");
|
||||
if (a) sscanf(a, "%f,%f,%f", &rsh_amb[0], &rsh_amb[1], &rsh_amb[2]);
|
||||
const char *s = getenv("VPX_SUN");
|
||||
if (s) {
|
||||
float p = -50.0f, y = 10.0f;
|
||||
if (sscanf(s, "%f,%f,%f,%f,%f", &rsh_suncol[0], &rsh_suncol[1],
|
||||
&rsh_suncol[2], &p, &y) >= 3) {
|
||||
float pr = p * 3.14159265f / 180.0f, yr = y * 3.14159265f / 180.0f;
|
||||
rsh_sundir[0] = -sinf(yr) * cosf(pr);
|
||||
rsh_sundir[1] = -sinf(pr); /* world is y-down */
|
||||
rsh_sundir[2] = -cosf(yr) * cosf(pr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
glViewport(0, 0, cw, ch);
|
||||
glClearColor(f.bg[0], f.bg[1], f.bg[2], 1.0f);
|
||||
@@ -583,9 +786,50 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glShadeModel(GL_SMOOTH);
|
||||
/* directional sun (world coords; modelview is loaded, so GL maps it
|
||||
* into eye space). World is y-down: up = -y. */
|
||||
{
|
||||
/* distance haze (dpl_SetViewFog): linear, eye-distance based.
|
||||
* VPX_NOFOG=1 disables (diagnostic). */
|
||||
static int nofog = -1;
|
||||
if (nofog < 0) {
|
||||
const char *nf = getenv("VPX_NOFOG");
|
||||
nofog = (nf && nf[0] && nf[0] != '0') ? 1 : 0;
|
||||
}
|
||||
bool fog_on = !nofog && f.fog && f.fogf > f.fogn;
|
||||
if (!rsh.tried) { rsh_env(); rsh_init(); }
|
||||
bool shade = rsh.prog != 0;
|
||||
if (shade) {
|
||||
/* gallery shading model: frame-level uniforms (world space) */
|
||||
glDisable(GL_FOG);
|
||||
glDisable(GL_LIGHTING);
|
||||
rsh.use(rsh.prog);
|
||||
rsh.u3f(rsh.uAmbScene, rsh_amb[0], rsh_amb[1], rsh_amb[2]);
|
||||
rsh.u3f(rsh.uLightDir0, rsh_sundir[0], rsh_sundir[1],
|
||||
rsh_sundir[2]);
|
||||
rsh.u3f(rsh.uLightCol0, rsh_suncol[0], rsh_suncol[1],
|
||||
rsh_suncol[2]);
|
||||
rsh.u3f(rsh.uLightDir1, 0.0f, 1.0f, 0.0f);
|
||||
rsh.u3f(rsh.uLightCol1, 0.0f, 0.0f, 0.0f);
|
||||
/* camera forward in world coords: -row2 of the world->eye
|
||||
* rotation (rows = eye axes in world; eye looks down -z) */
|
||||
rsh.u3f(rsh.uViewFwd, -f.rot[6], -f.rot[7], -f.rot[8]);
|
||||
rsh.u3f(rsh.uFogColor, f.fogc[0], f.fogc[1], f.fogc[2]);
|
||||
/* uFog = start, end, immune; immune=1 kills fog entirely.
|
||||
* Per-material immunity has no wire source yet -- 0 for all. */
|
||||
if (fog_on) rsh.u3f(rsh.uFog, f.fogn, f.fogf, 0.0f);
|
||||
else rsh.u3f(rsh.uFog, 1.0f, 2.0f, 1.0f);
|
||||
} else {
|
||||
if (fog_on) {
|
||||
GLfloat fc[4] = { f.fogc[0], f.fogc[1], f.fogc[2], 1.0f };
|
||||
glFogi(GL_FOG_MODE, GL_LINEAR);
|
||||
glFogf(GL_FOG_START, f.fogn);
|
||||
glFogf(GL_FOG_END, f.fogf);
|
||||
glFogfv(GL_FOG_COLOR, fc);
|
||||
glHint(GL_FOG_HINT, GL_NICEST);
|
||||
glEnable(GL_FOG);
|
||||
} else {
|
||||
glDisable(GL_FOG);
|
||||
}
|
||||
/* directional sun (world coords; modelview is loaded, so GL maps
|
||||
* it into eye space). World is y-down: up = -y. */
|
||||
GLfloat lpos[4] = { 0.35f, -0.85f, 0.40f, 0.0f };
|
||||
GLfloat lamb[4] = { 0.45f, 0.45f, 0.45f, 1.0f };
|
||||
GLfloat ldif[4] = { 0.80f, 0.80f, 0.78f, 1.0f };
|
||||
@@ -601,6 +845,14 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
/* upload any new/changed baked material textures */
|
||||
static std::map<unsigned, GLuint> gltex; /* matkey -> GL name */
|
||||
static std::map<unsigned, unsigned> gltex_ver;
|
||||
static std::map<unsigned, std::pair<int,int> > gltex_dim;
|
||||
/* VPX_UVNORM=1: treat wire UVs as texel-space and rescale by the
|
||||
* texture dimensions (pxpl5-era convention test) */
|
||||
static int uvnorm = -1;
|
||||
if (uvnorm < 0) {
|
||||
const char *un = getenv("VPX_UVNORM");
|
||||
uvnorm = (un && un[0] && un[0] != '0') ? 1 : 0;
|
||||
}
|
||||
EnterCriticalSection(&rt_lock);
|
||||
std::map<unsigned, TexImg> texs = rt_texs; /* small; copy out */
|
||||
LeaveCriticalSection(&rt_lock);
|
||||
@@ -621,6 +873,7 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, it->second.w, it->second.h,
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, &it->second.rgba[0]);
|
||||
gltex_ver[it->first] = it->second.ver;
|
||||
gltex_dim[it->first] = std::make_pair(it->second.w, it->second.h);
|
||||
}
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
||||
unsigned bound = 0;
|
||||
@@ -632,15 +885,52 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
if (bound != p.matkey) {
|
||||
glBindTexture(GL_TEXTURE_2D, gltex[p.matkey]);
|
||||
bound = p.matkey;
|
||||
if (uvnorm) {
|
||||
std::map<unsigned, std::pair<int,int> >::const_iterator
|
||||
di = gltex_dim.find(p.matkey);
|
||||
glMatrixMode(GL_TEXTURE);
|
||||
glLoadIdentity();
|
||||
if (di != gltex_dim.end() && di->second.first > 0 &&
|
||||
di->second.second > 0)
|
||||
glScalef(1.0f / di->second.first,
|
||||
1.0f / di->second.second, 1.0f);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
}
|
||||
}
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
if (!shade) glColor3f(1.0f, 1.0f, 1.0f);
|
||||
} else {
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor3f(p.rgb[0], p.rgb[1], p.rgb[2]);
|
||||
if (!shade) glColor3f(p.rgb[0], p.rgb[1], p.rgb[2]);
|
||||
}
|
||||
bool lit = !p.nrm.empty();
|
||||
if (lit) glEnable(GL_LIGHTING); else glDisable(GL_LIGHTING);
|
||||
if (shade) {
|
||||
rsh.u3f(rsh.uMatAmb, p.amb[0], p.amb[1], p.amb[2]);
|
||||
rsh.u3f(rsh.uMatDiff, p.rgb[0], p.rgb[1], p.rgb[2]);
|
||||
rsh.u3f(rsh.uMatEmis, p.emis[0], p.emis[1], p.emis[2]);
|
||||
rsh.u3f(rsh.uRamp0, p.ramp0[0], p.ramp0[1], p.ramp0[2]);
|
||||
rsh.u3f(rsh.uRamp1, p.ramp1[0], p.ramp1[1], p.ramp1[2]);
|
||||
rsh.u1f(rsh.uHasTex, tex ? 1.0f : 0.0f);
|
||||
if (!lit) {
|
||||
/* no wire normals: flat world-space face normal
|
||||
* (Newell); the gallery model lights everything and
|
||||
* the shader flips it toward the viewer */
|
||||
float nx = 0, ny = 0, nz = 0;
|
||||
size_t n = p.xyz.size() / 3;
|
||||
for (size_t v = 0; v < n; v++) {
|
||||
const float *a = &p.xyz[v * 3];
|
||||
const float *b = &p.xyz[((v + 1) % n) * 3];
|
||||
nx += (a[1] - b[1]) * (a[2] + b[2]);
|
||||
ny += (a[2] - b[2]) * (a[0] + b[0]);
|
||||
nz += (a[0] - b[0]) * (a[1] + b[1]);
|
||||
}
|
||||
float l = sqrtf(nx * nx + ny * ny + nz * nz);
|
||||
if (l < 1e-20f) { nx = 0; ny = -1; nz = 0; l = 1; }
|
||||
glNormal3f(nx / l, ny / l, nz / l);
|
||||
}
|
||||
} else {
|
||||
if (lit) glEnable(GL_LIGHTING); else glDisable(GL_LIGHTING);
|
||||
}
|
||||
glBegin(GL_POLYGON);
|
||||
for (size_t v = 0; v + 2 < p.xyz.size(); v += 3) {
|
||||
if (tex) glTexCoord2f(p.uv[v / 3 * 2], p.uv[v / 3 * 2 + 1]);
|
||||
@@ -649,6 +939,7 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
if (shade) rsh.use(0);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glDisable(GL_LIGHTING);
|
||||
}
|
||||
@@ -958,6 +1249,19 @@ static DWORD WINAPI rt_main(LPVOID) {
|
||||
|
||||
/* ---- scene-graph message decode ----------------------------------------- */
|
||||
|
||||
/* VPX_TEXLOG=<path>: texture-pipeline ground-truth log (type-12/13 flushes,
|
||||
* texel-upload headers, bake decisions) for decoding texture params. */
|
||||
static FILE *tex_log_fp(void) {
|
||||
static FILE *fp = NULL;
|
||||
static bool tried = false;
|
||||
if (!tried) {
|
||||
tried = true;
|
||||
const char *p = getenv("VPX_TEXLOG");
|
||||
if (p && p[0]) fp = fopen(p, "w");
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
/* Bake material's texture (intensity) through its ramp into RGBA and stash it
|
||||
* for the GL thread. Returns the material key if textured, else 0. */
|
||||
static std::map<unsigned, unsigned> baked_ver; /* material -> baked tex ver */
|
||||
@@ -969,9 +1273,24 @@ static unsigned bake_material_tex(unsigned matname) {
|
||||
std::map<unsigned, VTex>::const_iterator ti = S.tex.find(txi->second);
|
||||
if (ti == S.tex.end() || ti->second.px.empty()) return 0;
|
||||
const VTex &t = ti->second;
|
||||
if ((size_t)t.w * t.h > t.px.size()) return 0;
|
||||
size_t npx = (size_t)t.w * t.h;
|
||||
/* effective bpp from the upload-size ratio: dpl_TEXMAP has a
|
||||
* bits_per_texel field but its wire semantics are unverified; the
|
||||
* byte count is arithmetic ground truth. 16bpp = LE u16 intensity
|
||||
* (hi byte), 4bpp = two texels/byte low-nibble-first. */
|
||||
int bpp = t.px.size() >= npx * 2 ? 16 :
|
||||
t.px.size() >= npx ? 8 :
|
||||
t.px.size() * 2 >= npx ? 4 : 0;
|
||||
if (!bpp) return 0;
|
||||
std::map<unsigned, unsigned>::const_iterator bi = baked_ver.find(matname);
|
||||
if (bi != baked_ver.end() && bi->second == t.ver) return matname;
|
||||
FILE *tl = tex_log_fp();
|
||||
if (tl) {
|
||||
fprintf(tl, "bake mat=%08x texmap=%08x %dx%d px=%lu bpp=%d\n",
|
||||
matname, txi->second, t.w, t.h,
|
||||
(unsigned long)t.px.size(), bpp);
|
||||
fflush(tl);
|
||||
}
|
||||
float lo[3] = { 0, 0, 0 }, hi[3] = { 1, 1, 1 };
|
||||
std::map<unsigned, unsigned>::const_iterator ri = S.mat_ramp.find(matname);
|
||||
if (ri != S.mat_ramp.end()) {
|
||||
@@ -982,10 +1301,15 @@ static unsigned bake_material_tex(unsigned matname) {
|
||||
}
|
||||
}
|
||||
TexImg img;
|
||||
img.w = t.w; img.h = t.h; img.ver = t.ver;
|
||||
static unsigned bake_gen = 0; /* unique per bake so GL re-uploads */
|
||||
img.w = t.w; img.h = t.h; img.ver = ++bake_gen;
|
||||
img.rgba.resize((size_t)t.w * t.h * 4);
|
||||
for (size_t i = 0; i < (size_t)t.w * t.h; i++) {
|
||||
float a = t.px[i] / 255.0f;
|
||||
for (size_t i = 0; i < npx; i++) {
|
||||
unsigned char raw =
|
||||
bpp == 16 ? t.px[i * 2 + 1] :
|
||||
bpp == 8 ? t.px[i] :
|
||||
(unsigned char)(((t.px[i / 2] >> ((i & 1) * 4)) & 0xF) * 17);
|
||||
float a = raw / 255.0f;
|
||||
for (int c = 0; c < 3; c++) {
|
||||
float v = lo[c] + (hi[c] - lo[c]) * a;
|
||||
img.rgba[i * 4 + c] =
|
||||
@@ -1001,13 +1325,23 @@ static unsigned bake_material_tex(unsigned matname) {
|
||||
}
|
||||
|
||||
static void emit_geogroup(VFrame &f, unsigned gg, const M16 *world) {
|
||||
VCol col = { { 1.0f, 0.0f, 1.0f } }; /* missing = magenta */
|
||||
VMatG gmat;
|
||||
gmat.diff[0] = 1.0f; gmat.diff[1] = 0.0f; gmat.diff[2] = 1.0f; /* magenta */
|
||||
gmat.amb[0] = 1.0f; gmat.amb[1] = 0.0f; gmat.amb[2] = 1.0f;
|
||||
VRamp rp = { { 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f } }; /* identity */
|
||||
unsigned matname = 0;
|
||||
std::map<unsigned, unsigned>::const_iterator gmi = S.ggmat.find(gg);
|
||||
if (gmi != S.ggmat.end()) {
|
||||
matname = gmi->second;
|
||||
std::map<unsigned, VCol>::const_iterator mi = S.mat.find(matname);
|
||||
if (mi != S.mat.end()) col = mi->second;
|
||||
std::map<unsigned, VMatG>::const_iterator mi = S.mat.find(matname);
|
||||
if (mi != S.mat.end()) gmat = mi->second;
|
||||
std::map<unsigned, unsigned>::const_iterator rmi =
|
||||
S.mat_ramp.find(matname);
|
||||
if (rmi != S.mat_ramp.end()) {
|
||||
std::map<unsigned, VRamp>::const_iterator ri =
|
||||
S.ramp.find(rmi->second);
|
||||
if (ri != S.ramp.end()) rp = ri->second;
|
||||
}
|
||||
}
|
||||
unsigned texkey = matname ? bake_material_tex(matname) : 0;
|
||||
std::map<unsigned, std::vector<unsigned> >::const_iterator ci =
|
||||
@@ -1037,8 +1371,17 @@ static void emit_geogroup(VFrame &f, unsigned gg, const M16 *world) {
|
||||
for (size_t q = 0; q < pi->second.size(); q++) {
|
||||
const std::vector<int> &idx = pi->second[q];
|
||||
VPoly poly;
|
||||
memcpy(poly.rgb, col.c, sizeof poly.rgb);
|
||||
memcpy(poly.rgb, gmat.diff, sizeof poly.rgb);
|
||||
memcpy(poly.amb, gmat.amb, sizeof poly.amb);
|
||||
memcpy(poly.emis, gmat.emis, sizeof poly.emis);
|
||||
poly.matkey = (texkey && uv) ? texkey : 0;
|
||||
if (!poly.matkey) {
|
||||
/* untextured: the shading ramp applies to the light; for
|
||||
* textured polys the bake already ran texels through the
|
||||
* ramp, so keep the constructor's identity light-ramp */
|
||||
memcpy(poly.ramp0, rp.lo, sizeof poly.ramp0);
|
||||
memcpy(poly.ramp1, rp.hi, sizeof poly.ramp1);
|
||||
}
|
||||
for (size_t j = 0; j < idx.size(); j++) {
|
||||
size_t o = (size_t)idx[j] * 3;
|
||||
if (o + 2 >= vv.size()) continue;
|
||||
@@ -1149,6 +1492,21 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
nl.push_back(S.geom_acc[i + 5]);
|
||||
} else { nl.push_back(0); nl.push_back(0); nl.push_back(0); }
|
||||
}
|
||||
FILE *flog = tex_log_fp();
|
||||
if (flog && uvo && !tl.empty()) {
|
||||
float ulo = tl[0], uhi = tl[0], vlo = tl[1], vhi = tl[1];
|
||||
for (size_t k = 0; k + 1 < tl.size(); k += 2) {
|
||||
if (tl[k] < ulo) ulo = tl[k];
|
||||
if (tl[k] > uhi) uhi = tl[k];
|
||||
if (tl[k + 1] < vlo) vlo = tl[k + 1];
|
||||
if (tl[k + 1] > vhi) vhi = tl[k + 1];
|
||||
}
|
||||
fprintf(flog, "geomuv node=%08x verts=%lu stride=%lu "
|
||||
"u=[%g,%g] v=[%g,%g]\n", S.geom_node,
|
||||
(unsigned long)(vl.size() / 3),
|
||||
(unsigned long)S.geom_stride, ulo, uhi, vlo, vhi);
|
||||
fflush(flog);
|
||||
}
|
||||
S.geom_acc.clear();
|
||||
S.geom_active = false;
|
||||
}
|
||||
@@ -1185,29 +1543,90 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
case 3: { /* flush [name][type][struct] */
|
||||
if (nb < 8) break;
|
||||
unsigned name = rd_u32(d), t = rd_u32(d + 4);
|
||||
if (t == 11 && nb >= 92) { /* material diffuse */
|
||||
VCol c = { { rd_f32(d + 48), rd_f32(d + 52), rd_f32(d + 56) } };
|
||||
S.mat[name] = c;
|
||||
/* texmap / ramp refs: identify fields by node type */
|
||||
for (size_t o = 8; o + 3 < nb; o += 4) {
|
||||
unsigned val = rd_u32(d + o);
|
||||
if (!val || val == 0xFFFFFFFFu) continue;
|
||||
std::map<unsigned, unsigned>::const_iterator ti =
|
||||
S.type.find(val);
|
||||
if (ti == S.type.end()) continue;
|
||||
if (ti->second == 12) S.mat_texmap[name] = val;
|
||||
else if (ti->second == 14) S.mat_ramp[name] = val;
|
||||
if (t == 11 && nb >= 92) { /* material */
|
||||
/* dpl_MATERIAL wire layout (verified on a live capture):
|
||||
* texture ref d+8, ramp ref d+20, emissive d+24, ambient
|
||||
* d+36, diffuse d+48, opacity d+60, specular+shin d+72.
|
||||
* Fixed offsets: the old scan-by-node-type missed refs
|
||||
* whose create hadn't arrived yet (=> default gray ramp).
|
||||
* Refs resolve lazily at bake time, so order is safe.
|
||||
* Boot flushes can carry heap garbage -- clamp (NaN fails
|
||||
* the range test and lands on the default). */
|
||||
VMatG m;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float e = rd_f32(d + 24 + i * 4), a = rd_f32(d + 36 + i * 4);
|
||||
float df = rd_f32(d + 48 + i * 4), sp = rd_f32(d + 72 + i * 4);
|
||||
m.emis[i] = (e >= 0.0f && e <= 8.0f) ? e : 0.0f;
|
||||
m.amb[i] = (a >= 0.0f && a <= 8.0f) ? a : 0.6f;
|
||||
m.diff[i] = (df >= 0.0f && df <= 8.0f) ? df : 0.6f;
|
||||
m.spec[i] = (sp >= 0.0f && sp <= 8.0f) ? sp : 0.0f;
|
||||
}
|
||||
} else if (t == 12 && nb >= 12) { /* texmap -> texture */
|
||||
for (size_t o = 8; o + 3 < nb; o += 4) {
|
||||
unsigned val = rd_u32(d + o);
|
||||
if (!val || val == 0xFFFFFFFFu) continue;
|
||||
std::map<unsigned, unsigned>::const_iterator ti =
|
||||
S.type.find(val);
|
||||
if (ti != S.type.end() && ti->second == 13) {
|
||||
S.texmap_tex[name] = val;
|
||||
break;
|
||||
}
|
||||
float op = rd_f32(d + 60), sh = rd_f32(d + 84);
|
||||
m.opacity = (op >= 0.0f && op <= 1.0f) ? op : 1.0f;
|
||||
m.shin = (sh >= 0.0f && sh <= 1024.0f) ? sh : 0.0f;
|
||||
S.mat[name] = m;
|
||||
unsigned tref = rd_u32(d + 8), rref = rd_u32(d + 20);
|
||||
if (tref && tref != 0xFFFFFFFFu) S.mat_texmap[name] = tref;
|
||||
else S.mat_texmap.erase(name);
|
||||
if (rref && rref != 0xFFFFFFFFu) S.mat_ramp[name] = rref;
|
||||
else S.mat_ramp.erase(name);
|
||||
baked_ver.erase(name); /* rebake with fresh bindings */
|
||||
} else if (t == 12 && nb >= 12) { /* texture params */
|
||||
/* dpl_TEXTURE: texmap ref at d+8 (then minify/magnify/
|
||||
* alpha/wrap/detail/u0/v0/du/dv -- not yet used) */
|
||||
unsigned mref = rd_u32(d + 8);
|
||||
if (mref && mref != 0xFFFFFFFFu) S.texmap_tex[name] = mref;
|
||||
FILE *tl = tex_log_fp();
|
||||
if (tl && nb >= 60) {
|
||||
fprintf(tl, "t12 tex=%08x texmap=%08x min=%d mag=%d "
|
||||
"alpha=%d wrapu=%d wrapv=%d detail=%d u0=%g "
|
||||
"v0=%g du=%g dv=%g atime=%g abhv=%d\n",
|
||||
name, mref, (int)rd_u32(d + 12),
|
||||
(int)rd_u32(d + 16), (int)rd_u32(d + 20),
|
||||
(int)rd_u32(d + 24), (int)rd_u32(d + 28),
|
||||
(int)rd_u32(d + 32), rd_f32(d + 36),
|
||||
rd_f32(d + 40), rd_f32(d + 44), rd_f32(d + 48),
|
||||
rd_f32(d + 52), (int)rd_u32(d + 56));
|
||||
fflush(tl);
|
||||
}
|
||||
} else if (t == 7 && nb >= 12) { /* light */
|
||||
/* dpl_LIGHT wire layout still unknown (field-by-field
|
||||
* serializer, not a struct dump) -- raw dump each u32 as
|
||||
* hex + float until the offsets are pinned */
|
||||
FILE *tl = tex_log_fp();
|
||||
if (tl) {
|
||||
fprintf(tl, "t7 light=%08x nb=%lu raw=", name,
|
||||
(unsigned long)nb);
|
||||
for (size_t o = 8; o + 3 < nb; o += 4)
|
||||
fprintf(tl, " %08x/%g", rd_u32(d + o), rd_f32(d + o));
|
||||
fprintf(tl, "\n");
|
||||
fflush(tl);
|
||||
}
|
||||
} else if ((t == 6 || t == 2) && nb >= 8) { /* lmodel / zone */
|
||||
FILE *tl = tex_log_fp();
|
||||
if (tl) {
|
||||
fprintf(tl, "t%d %s=%08x raw=", (int)t,
|
||||
t == 6 ? "lmodel" : "zone", name);
|
||||
for (size_t o = 8; o + 3 < nb && o < 40; o += 4)
|
||||
fprintf(tl, " %08x", rd_u32(d + o));
|
||||
fprintf(tl, "\n");
|
||||
fflush(tl);
|
||||
}
|
||||
} else if (t == 13 && nb >= 24) { /* texmap header */
|
||||
/* dpl_TEXMAP: texels ptr, u_size, v_size, bits_per_texel
|
||||
* (+ REMOTE hwareSize/hwareOffs/bilinear) -- log only, the
|
||||
* bake infers bpp from the upload byte/texel ratio */
|
||||
FILE *tl = tex_log_fp();
|
||||
if (tl) {
|
||||
fprintf(tl, "t13 texmap=%08x texels=%08x u=%u v=%u bpt=%u",
|
||||
name, rd_u32(d + 8), rd_u32(d + 12),
|
||||
rd_u32(d + 16), rd_u32(d + 20));
|
||||
if (nb >= 36)
|
||||
fprintf(tl, " hwsz=%u hwoff=%u bilin=%u",
|
||||
rd_u32(d + 24), rd_u32(d + 28),
|
||||
rd_u32(d + 32));
|
||||
fprintf(tl, "\n");
|
||||
fflush(tl);
|
||||
}
|
||||
} else if (t == 14 && nb >= 36) { /* ramp lo/hi RGB */
|
||||
VRamp &r = S.ramp[name];
|
||||
@@ -1215,6 +1634,10 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
r.lo[i] = rd_f32(d + 8 + i * 4);
|
||||
r.hi[i] = rd_f32(d + 20 + i * 4);
|
||||
}
|
||||
/* retint materials baked through this ramp */
|
||||
for (std::map<unsigned, unsigned>::const_iterator mi =
|
||||
S.mat_ramp.begin(); mi != S.mat_ramp.end(); ++mi)
|
||||
if (mi->second == name) baked_ver.erase(mi->first);
|
||||
} else if (t == 9 && nb >= 80) { /* geogroup material */
|
||||
S.ggmat[name] = rd_u32(d + 64);
|
||||
} else if (t == 3 && nb >= 104) { /* view */
|
||||
@@ -1225,6 +1648,22 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
S.view.nearp = rd_f32(d + 52); S.view.farp = rd_f32(d + 56);
|
||||
S.view.bg[0] = rd_f32(d + 60); S.view.bg[1] = rd_f32(d + 64);
|
||||
S.view.bg[2] = rd_f32(d + 68);
|
||||
/* dpl_VIEW fog follows back_color: enable/mode, near, far,
|
||||
* r, g, b (game sends mode 5 and animates near/far/color;
|
||||
* verified against a live capture). Boot-time flushes carry
|
||||
* heap garbage here, so sanity-check the range. */
|
||||
float fn = rd_f32(d + 76), ff = rd_f32(d + 80);
|
||||
if (rd_u32(d + 72) != 0 && ff > fn && ff > 0 &&
|
||||
ff < 1e6f && fn >= 0) {
|
||||
S.view.fog = true;
|
||||
S.view.fogn = fn; S.view.fogf = ff;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float c = rd_f32(d + 84 + i * 4);
|
||||
S.view.fogc[i] = (c >= 0 && c <= 1) ? c : 0;
|
||||
}
|
||||
} else {
|
||||
S.view.fog = false;
|
||||
}
|
||||
} else if (t == 5 && nb >= 132) { /* dcs: 4x4 at f[4..19] */
|
||||
M16 &mm = S.dcs_mat[name];
|
||||
for (int i = 0; i < 16; i++) mm.m[i] = rd_f32(d + 16 + i * 4);
|
||||
@@ -1270,12 +1709,21 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
S.polys[S.conn_node].clear();
|
||||
}
|
||||
break;
|
||||
case 26: /* texel upload header [node][nbytes][w][h]... (8bpp) */
|
||||
case 26: /* texel upload header [node][nbytes][w][h]... (4/8/16bpp) */
|
||||
if (nb >= 16) {
|
||||
unsigned node = rd_u32(d), nbytes = rd_u32(d + 4);
|
||||
unsigned tw = rd_u32(d + 8), th = rd_u32(d + 12);
|
||||
FILE *tl = tex_log_fp();
|
||||
if (tl) {
|
||||
fprintf(tl, "up26 texmap=%08x nbytes=%u w=%u h=%u\n",
|
||||
node, nbytes, tw, th);
|
||||
fflush(tl);
|
||||
}
|
||||
/* w*h <= 2*nbytes admits 4bpp uploads; bake derives the
|
||||
* effective bpp from the byte/texel ratio */
|
||||
if (nbytes && nbytes <= (1u << 20) && tw && th &&
|
||||
tw <= 1024 && th <= 1024 && (size_t)tw * th <= nbytes) {
|
||||
tw <= 1024 && th <= 1024 &&
|
||||
(size_t)tw * th <= (size_t)nbytes * 2) {
|
||||
S.tex_node = node;
|
||||
S.tex_need = nbytes;
|
||||
S.tex_active = true;
|
||||
|
||||
Reference in New Issue
Block a user