Phase 3e: live texturing + serial receive-latency fixes for the RIO
Texturing (validated live -- game-live-textured.png, the ravine's brown dirt terrain): the wire model is Division's intensity+ramp scheme. action 26 uploads 8-bit intensity texels ([node][nbytes][w][h] + rows; type 13 = texture); texmap (12) references the texture; material (11) references its texmap and a ramp (14: lo/hi RGB); texel color = lerp(lo, hi, i/255). The backend bakes RGBA per material, uploads to GL, maps with wire UVs (stride-5: floats 3-4; stride-8/9: floats 6-7). Serial (directserial fork options, tracked in vpx-device/serialport/): - rxpollus:<us> -- receive poll tick (stock 1ms); 100us discovers inbound bytes ~10x sooner. Validated: sim advanced, camera moved with real RIO. - rxburst:<n> -- stock DOSBox re-serializes received bytes at emulated wire speed (~1ms/byte at 9600) though they already paid wire time on the real cable; a 15-byte analog reply gained ~14ms, blowing the RIO's few-ms ACK window and dropping the game into its 15-second retry fallback (L4CTRL.CPP limit=15.0 //0.2). rxburst:16 delivers buffered bytes 16x faster. game_rio.conf: realport:COM1 rxpollus:100 rxburst:16. Crash-on-advance fixed: *_TSHD.BGF terrain shadows live only in arena subdirs the search path misses; null shadow renderable was dereferenced once the sim moved. All 11 copied into VIDEO/GEO in the working image; game now runs sustained (560+ frames, textured, no crash). Details in RIO-NOTES.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -435,7 +435,16 @@ static float rd_f32(const unsigned char *p) {
|
||||
}
|
||||
|
||||
struct VCol { float c[3]; };
|
||||
struct VPoly { float rgb[3]; std::vector<float> xyz; }; /* x,y,z triples */
|
||||
struct VPoly {
|
||||
float rgb[3];
|
||||
std::vector<float> xyz; /* x,y,z triples */
|
||||
std::vector<float> uv; /* u,v pairs (empty = untextured) */
|
||||
unsigned matkey; /* material name for texture lookup, 0 = none */
|
||||
VPoly() : matkey(0) {}
|
||||
};
|
||||
/* Baked RGBA texture (texture intensity x material ramp), keyed by material.
|
||||
* Shared with the GL thread under rt_lock; ver bumps force re-upload. */
|
||||
struct TexImg { int w, h; unsigned ver; std::vector<unsigned char> rgba; };
|
||||
struct VFrame {
|
||||
bool valid;
|
||||
float bg[3];
|
||||
@@ -456,9 +465,13 @@ struct VFrame {
|
||||
|
||||
struct M16 { float m[16]; }; /* row-major 4x4, row 3 = translation */
|
||||
|
||||
struct VRamp { float lo[3], hi[3]; };
|
||||
struct VTex { int w, h; unsigned ver; std::vector<unsigned char> px; };
|
||||
|
||||
static struct VScene {
|
||||
std::map<unsigned, unsigned> type; /* name -> node type */
|
||||
std::map<unsigned, std::vector<float> > verts; /* geometry -> xyz */
|
||||
std::map<unsigned, std::vector<float> > uvs; /* geometry -> u,v */
|
||||
std::map<unsigned, std::vector<std::vector<int> > > polys;
|
||||
std::map<unsigned, VCol> mat; /* material -> RGB */
|
||||
std::map<unsigned, unsigned> ggmat; /* geogroup -> material */
|
||||
@@ -467,11 +480,21 @@ static struct VScene {
|
||||
std::map<unsigned, unsigned> inst_object; /* instance -> object */
|
||||
std::map<unsigned, M16> dcs_mat; /* dcs -> local matrix */
|
||||
std::map<unsigned, unsigned> dcs_parent; /* dcs_link child->parent */
|
||||
/* texture chain: material -> texmap -> texture, colorized by the
|
||||
* material's ramp (type 14: lo/hi RGB; texels are intensities) */
|
||||
std::map<unsigned, VTex> tex; /* texture node -> texels */
|
||||
std::map<unsigned, unsigned> texmap_tex; /* texmap -> texture */
|
||||
std::map<unsigned, unsigned> mat_texmap; /* material -> texmap */
|
||||
std::map<unsigned, unsigned> mat_ramp; /* material -> ramp */
|
||||
std::map<unsigned, VRamp> ramp; /* ramp -> lo/hi */
|
||||
VFrame view; /* view/bg/camera state */
|
||||
/* multi-burst assembly (stride-aware: game verts are 3..9 floats each) */
|
||||
unsigned geom_node; size_t geom_need, geom_stride; bool geom_active;
|
||||
std::vector<float> geom_acc;
|
||||
unsigned conn_node, conn_npolys, conn_loop; bool conn_active;
|
||||
/* action-26 texel upload assembly */
|
||||
unsigned tex_node; size_t tex_need; bool tex_active;
|
||||
std::vector<unsigned char> tex_acc;
|
||||
} S;
|
||||
|
||||
static void m16_id(M16 &o) {
|
||||
@@ -516,6 +539,7 @@ static CRITICAL_SECTION rt_lock;
|
||||
static VFrame rt_pending;
|
||||
static bool rt_new = false;
|
||||
static unsigned long rt_frames = 0;
|
||||
static std::map<unsigned, TexImg> rt_texs; /* material -> baked RGBA (rt_lock) */
|
||||
|
||||
static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
glViewport(0, 0, cw, ch);
|
||||
@@ -545,14 +569,55 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_LIGHTING);
|
||||
glShadeModel(GL_FLAT);
|
||||
/* upload any new/changed baked material textures */
|
||||
static std::map<unsigned, GLuint> gltex; /* matkey -> GL name */
|
||||
static std::map<unsigned, unsigned> gltex_ver;
|
||||
EnterCriticalSection(&rt_lock);
|
||||
std::map<unsigned, TexImg> texs = rt_texs; /* small; copy out */
|
||||
LeaveCriticalSection(&rt_lock);
|
||||
for (std::map<unsigned, TexImg>::const_iterator it = texs.begin();
|
||||
it != texs.end(); ++it) {
|
||||
std::map<unsigned, unsigned>::const_iterator vi =
|
||||
gltex_ver.find(it->first);
|
||||
if (vi != gltex_ver.end() && vi->second == it->second.ver) continue;
|
||||
GLuint id;
|
||||
std::map<unsigned, GLuint>::const_iterator gi = gltex.find(it->first);
|
||||
if (gi == gltex.end()) { glGenTextures(1, &id); gltex[it->first] = id; }
|
||||
else id = gi->second;
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
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;
|
||||
}
|
||||
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
||||
unsigned bound = 0;
|
||||
for (size_t i = 0; i < f.polys.size(); i++) {
|
||||
const VPoly &p = f.polys[i];
|
||||
glColor3f(p.rgb[0], p.rgb[1], p.rgb[2]);
|
||||
bool tex = p.matkey && gltex.count(p.matkey) &&
|
||||
p.uv.size() * 3 == p.xyz.size() * 2;
|
||||
if (tex) {
|
||||
if (bound != p.matkey) {
|
||||
glBindTexture(GL_TEXTURE_2D, gltex[p.matkey]);
|
||||
bound = p.matkey;
|
||||
}
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
} else {
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
glColor3f(p.rgb[0], p.rgb[1], p.rgb[2]);
|
||||
}
|
||||
glBegin(GL_POLYGON);
|
||||
for (size_t v = 0; v + 2 < p.xyz.size(); v += 3)
|
||||
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]);
|
||||
glVertex3f(p.xyz[v], p.xyz[v + 1], p.xyz[v + 2]);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
SwapBuffers(dc);
|
||||
}
|
||||
@@ -610,13 +675,59 @@ static DWORD WINAPI rt_main(LPVOID) {
|
||||
}
|
||||
|
||||
/* ---- scene-graph message decode ----------------------------------------- */
|
||||
|
||||
/* 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 */
|
||||
static unsigned bake_material_tex(unsigned matname) {
|
||||
std::map<unsigned, unsigned>::const_iterator tmi = S.mat_texmap.find(matname);
|
||||
if (tmi == S.mat_texmap.end()) return 0;
|
||||
std::map<unsigned, unsigned>::const_iterator txi = S.texmap_tex.find(tmi->second);
|
||||
if (txi == S.texmap_tex.end()) return 0;
|
||||
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;
|
||||
std::map<unsigned, unsigned>::const_iterator bi = baked_ver.find(matname);
|
||||
if (bi != baked_ver.end() && bi->second == t.ver) return matname;
|
||||
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()) {
|
||||
std::map<unsigned, VRamp>::const_iterator rr = S.ramp.find(ri->second);
|
||||
if (rr != S.ramp.end()) {
|
||||
memcpy(lo, rr->second.lo, sizeof lo);
|
||||
memcpy(hi, rr->second.hi, sizeof hi);
|
||||
}
|
||||
}
|
||||
TexImg img;
|
||||
img.w = t.w; img.h = t.h; img.ver = t.ver;
|
||||
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 (int c = 0; c < 3; c++) {
|
||||
float v = lo[c] + (hi[c] - lo[c]) * a;
|
||||
img.rgba[i * 4 + c] =
|
||||
(unsigned char)(v < 0 ? 0 : v > 1 ? 255 : v * 255.0f + 0.5f);
|
||||
}
|
||||
img.rgba[i * 4 + 3] = 255;
|
||||
}
|
||||
EnterCriticalSection(&rt_lock);
|
||||
rt_texs[matname] = img;
|
||||
LeaveCriticalSection(&rt_lock);
|
||||
baked_ver[matname] = t.ver;
|
||||
return matname;
|
||||
}
|
||||
|
||||
static void emit_geogroup(VFrame &f, unsigned gg, const M16 *world) {
|
||||
VCol col = { { 1.0f, 0.0f, 1.0f } }; /* missing = magenta */
|
||||
unsigned matname = 0;
|
||||
std::map<unsigned, unsigned>::const_iterator gmi = S.ggmat.find(gg);
|
||||
if (gmi != S.ggmat.end()) {
|
||||
std::map<unsigned, VCol>::const_iterator mi = S.mat.find(gmi->second);
|
||||
matname = gmi->second;
|
||||
std::map<unsigned, VCol>::const_iterator mi = S.mat.find(matname);
|
||||
if (mi != S.mat.end()) col = mi->second;
|
||||
}
|
||||
unsigned texkey = matname ? bake_material_tex(matname) : 0;
|
||||
std::map<unsigned, std::vector<unsigned> >::const_iterator ci =
|
||||
S.children.find(gg);
|
||||
if (ci == S.children.end()) return;
|
||||
@@ -628,10 +739,16 @@ static void emit_geogroup(VFrame &f, unsigned gg, const M16 *world) {
|
||||
pi = S.polys.find(geo);
|
||||
if (vi == S.verts.end() || pi == S.polys.end()) continue;
|
||||
const std::vector<float> &vv = vi->second;
|
||||
const std::vector<float> *uv = NULL;
|
||||
std::map<unsigned, std::vector<float> >::const_iterator ui =
|
||||
S.uvs.find(geo);
|
||||
if (ui != S.uvs.end() && ui->second.size() * 3 == vv.size() * 2)
|
||||
uv = &ui->second;
|
||||
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);
|
||||
poly.matkey = (texkey && uv) ? texkey : 0;
|
||||
for (size_t j = 0; j < idx.size(); j++) {
|
||||
size_t o = (size_t)idx[j] * 3;
|
||||
if (o + 2 >= vv.size()) continue;
|
||||
@@ -641,6 +758,10 @@ static void emit_geogroup(VFrame &f, unsigned gg, const M16 *world) {
|
||||
poly.xyz.push_back(out[0]);
|
||||
poly.xyz.push_back(out[1]);
|
||||
poly.xyz.push_back(out[2]);
|
||||
if (poly.matkey) {
|
||||
poly.uv.push_back((*uv)[(size_t)idx[j] * 2]);
|
||||
poly.uv.push_back((*uv)[(size_t)idx[j] * 2 + 1]);
|
||||
}
|
||||
}
|
||||
if (poly.xyz.size() >= 9) f.polys.push_back(poly);
|
||||
}
|
||||
@@ -702,18 +823,38 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
S.geom_acc.push_back(rd_f32(d + o));
|
||||
if (S.geom_acc.size() >= S.geom_need * S.geom_stride) {
|
||||
std::vector<float> &vl = S.verts[S.geom_node];
|
||||
vl.clear();
|
||||
std::vector<float> &tl = S.uvs[S.geom_node];
|
||||
vl.clear(); tl.clear();
|
||||
/* uv offset within a vertex record by stride: 5 = xyz+uv,
|
||||
* 8/9 = xyz+normal+uv(+extra) */
|
||||
size_t uvo = (S.geom_stride >= 8) ? 6 :
|
||||
(S.geom_stride == 5) ? 3 : 0;
|
||||
for (size_t i = 0; i + 2 < S.geom_need * S.geom_stride;
|
||||
i += S.geom_stride) {
|
||||
vl.push_back(S.geom_acc[i]);
|
||||
vl.push_back(S.geom_acc[i + 1]);
|
||||
vl.push_back(S.geom_acc[i + 2]);
|
||||
if (uvo && i + uvo + 1 < S.geom_acc.size()) {
|
||||
tl.push_back(S.geom_acc[i + uvo]);
|
||||
tl.push_back(S.geom_acc[i + uvo + 1]);
|
||||
} else { tl.push_back(0); tl.push_back(0); }
|
||||
}
|
||||
S.geom_acc.clear();
|
||||
S.geom_active = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (S.tex_active && action == 26) {
|
||||
for (size_t o = 0; o < nb; o++) S.tex_acc.push_back(d[o]);
|
||||
if (S.tex_acc.size() >= S.tex_need) {
|
||||
VTex &t = S.tex[S.tex_node];
|
||||
t.px.assign(S.tex_acc.begin(), S.tex_acc.begin() + S.tex_need);
|
||||
t.ver++;
|
||||
S.tex_acc.clear();
|
||||
S.tex_active = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (S.conn_active && action == 25) {
|
||||
std::vector<std::vector<int> > &pl = S.polys[S.conn_node];
|
||||
size_t nw = nb / 4;
|
||||
@@ -737,6 +878,33 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
} else if (t == 14 && nb >= 36) { /* ramp lo/hi RGB */
|
||||
VRamp &r = S.ramp[name];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
r.lo[i] = rd_f32(d + 8 + i * 4);
|
||||
r.hi[i] = rd_f32(d + 20 + i * 4);
|
||||
}
|
||||
} else if (t == 9 && nb >= 80) { /* geogroup material */
|
||||
S.ggmat[name] = rd_u32(d + 64);
|
||||
} else if (t == 3 && nb >= 104) { /* view */
|
||||
@@ -792,6 +960,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) */
|
||||
if (nb >= 16) {
|
||||
unsigned node = rd_u32(d), nbytes = rd_u32(d + 4);
|
||||
unsigned tw = rd_u32(d + 8), th = rd_u32(d + 12);
|
||||
if (nbytes && nbytes <= (1u << 20) && tw && th &&
|
||||
tw <= 1024 && th <= 1024 && (size_t)tw * th <= nbytes) {
|
||||
S.tex_node = node;
|
||||
S.tex_need = nbytes;
|
||||
S.tex_active = true;
|
||||
S.tex_acc.clear();
|
||||
VTex &t = S.tex[node];
|
||||
t.w = (int)tw; t.h = (int)th;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 31: /* per-frame camera [?][view][3x3 rows][eye] */
|
||||
if (nb >= 56) {
|
||||
for (int i = 0; i < 9; i++)
|
||||
|
||||
Reference in New Issue
Block a user