Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 117 additions & 17 deletions src/dev/gr2/gl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,25 @@ pub enum PixZoom {
/// READ_DONE. Unimplemented, snoop spun on FIN3 for its whole time slice.
pub const T_READ_RECT: u32 = 0x0ad;
pub const T_IRIS_SBOXF: u32 = 0x053;
/// sbox / sboxi / sboxs (gl_i_sbox*): a screen-aligned box outline, the
/// corners as for 0x053 (f32; integers on ITOF|0x052). Blender draws its
/// interface lines as degenerate ones.
pub const T_IRIS_SBOX: u32 = 0x052;
pub const T_IRIS_SBOXI: u32 = 0x4052;
/// Aux (overlay / popup) plane write mask (__glExpSetPixWritemask; IRIS GL
/// drawmode): the aux bits drawing may change, 0 in normal drawing.
pub const T_AUX_WRITEMASK: u32 = 0x003;
/// swaptmesh (gl_i_swaptmesh): swap the two vertices a tmesh (bgntmesh =
/// 0x046, LOADV|0x047) keeps; the next vertex makes a triangle with them.
pub const T_IRIS_SWAPTMESH: u32 = 0x04b;
pub const T_IRIS_SBOXFI: u32 = 0x4053;
pub const T_IRIS_CHAR16: u32 = 0x069;
pub const T_IRIS_CHAR16_TALL: u32 = 0x06a;
pub const T_IRIS_CHAR32: u32 = 0x06d;
/// Frame setup: 0x008 = 0, back buffer (0/1); 0x005 = colour write mask
/// for it (0x000FFF / 0xFFF000 for 12-bit double buffering).
/// readsource (gl_setreadsource): 0x008 = plane (0 colour, 1 overlay /
/// popup, 2 and up other planes), then the absolute bank (0/1). 0x005 is
/// the colour write mask, also absolute (0x000FFF / 0xFFF000 for 12-bit
/// double buffering).
pub const T_IRIS_BUFFER: u32 = 0x008;
pub const T_IRIS_WRITEMASK: u32 = 0x005;
/// clear() with the current colour; zclear(value).
Expand Down Expand Up @@ -454,6 +464,12 @@ pub struct GlState {
st_bits: u32,
/// Pixel word packing (0x0A7): 0 = 32-bit, 1 = 2 x 16, 2 = 4 x 8.
pix_pack: u32,
/// Drawing into the overlay / popup planes (IRIS GL drawmode OVERDRAW /
/// PUPDRAW): the MakeCurrent mode (8 = 4-bit overlay, 11 = 2-bit
/// overlay or popup, as the X server's 2D MODE numbers them), else 0;
/// and the aux plane write mask (0x003).
aux_draw: u32,
aux_wmask: u32,
/// Spare (keeps the count of 4-byte fields even, see st_bits).
pad: u32,
/// Vertices drawn / primitives emitted since the last trace note.
Expand All @@ -462,6 +478,19 @@ pub struct GlState {

impl GlState {
/// Range of the R channel in GL colour units (see to_fixed).
/// UAUXDATA for colour index `idx` in overlay / popup drawing: 4-bit
/// overlay takes it as is; the 2-bit planes take it shifted up when the
/// mask names aux bits 3:2 (popup), as the 2D path's value2d does.
fn aux_value(&self, idx: u32) -> u32 {
if self.aux_draw == 8 {
idx & 0xf
} else if self.aux_wmask & 0xc != 0 {
(idx << 2) & 0xc
} else {
idx & 3
}
}

fn cmax(&self) -> f32 {
if self.pixfmt == re3::PIXFMT_CI12 { 4095.0 / 255.0 } else { 1.0 }
}
Expand Down Expand Up @@ -976,7 +1005,8 @@ impl Hq2Engine {
T_BITMAP => (BITMAP_HDR + 9) as u32,
T_BITMAP_TALL => (BITMAP_HDR + 17) as u32,
T_BITMAP_HUGE => (BITMAP_HDR + 33) as u32,
T_IRIS_SBOXF | T_IRIS_SBOXFI => 4,
T_IRIS_SBOXF | T_IRIS_SBOXFI | T_IRIS_SBOX | T_IRIS_SBOXI => 4,
T_AUX_WRITEMASK => 1,
T_IRIS_SWAPTMESH => 1,
T_CLIP_PLANE_ENABLE => 2,
T_CLIP_PLANE => 5,
Expand Down Expand Up @@ -1268,7 +1298,10 @@ impl Hq2Engine {
T_STIPPLE_OFF => g.stipple_on = 0,
T_BLEND_MODE => g.blend_fast = a[0] & 1,
T_IRIS_DB => g.lt.on = a[1] & 1,
T_IRIS_BUFFER | T_IRIS_BGNPOLYGON | T_IRIS_PMV => {}
// readsource (gl_setreadsource): plane, then the absolute bank
// (libgl tracks swaps itself and sends no 0x1E7, so swap stays 0).
T_IRIS_BUFFER => g.read_src = [a[0], a[1]],
T_IRIS_BGNPOLYGON | T_IRIS_PMV => {}
T_IRIS_WRITEMASK => g.masks = [a[0] & 0x00ff_ffff; 2],
T_IRIS_CLEAR => {
let c = g.color;
Expand Down Expand Up @@ -1311,6 +1344,8 @@ impl Hq2Engine {
g.pv.swap(0, 1);
g.tflip ^= 1;
}
T_AUX_WRITEMASK => g.aux_wmask = a[0] & 0xf,
T_IRIS_SBOX | T_IRIS_SBOXI => self.gl_sbox(cmd == T_IRIS_SBOXI, &a, out),
T_IRIS_SBOXF | T_IRIS_SBOXFI => {
let num = |w: u32| if cmd == T_IRIS_SBOXFI { w as i32 as f32 } else { f(w) };
let p = g.transform([num(a[0]), num(a[1]), 0.0, 1.0]);
Expand Down Expand Up @@ -1441,15 +1476,24 @@ impl Hq2Engine {
// Visual code from the kernel (inferred from 0, 1, 2, 4, 9, 10 in
// traces and the DDX's matching 2D MODE numbers): bit 3 = colour
// index, low bits = depth (0 = 4, 1 = 8, 2 = 12, 4 = 24 bits).
T_MAKECURRENT => match a[0] {
0 => g.pixfmt = re3::PIXFMT_RGB4,
4 => g.pixfmt = 0,
2 => g.pixfmt = re3::PIXFMT_RGB12,
1 => g.pixfmt = re3::PIXFMT_RGB8,
10 => g.pixfmt = re3::PIXFMT_CI12,
9 => g.pixfmt = re3::PIXFMT_CI8,
_ => {}
},
T_MAKECURRENT => {
g.aux_draw = 0;
match a[0] {
// Overlay / popup planes: the colour index goes to the aux
// bits (gl_setup), the colour planes are left alone.
8 | 11 => {
g.aux_draw = a[0];
g.pixfmt = re3::PIXFMT_CI8;
}
0 => g.pixfmt = re3::PIXFMT_RGB4,
4 => g.pixfmt = 0,
2 => g.pixfmt = re3::PIXFMT_RGB12,
1 => g.pixfmt = re3::PIXFMT_RGB8,
10 => g.pixfmt = re3::PIXFMT_CI12,
9 => g.pixfmt = re3::PIXFMT_CI8,
_ => {}
}
}
T_CLEAR_CI => self.gl_clear([f(a[0]) / 255.0, 0.0, 0.0, 1.0], out),
T_CLEAR_CI_DEPTH | T_CLEAR_CI_DEPTH_I => {
let i = if cmd == T_CLEAR_CI_DEPTH_I { a[0] as i32 as f32 } else { f(a[0]) };
Expand Down Expand Up @@ -1549,7 +1593,13 @@ impl Hq2Engine {
light::T_FOG_ON => format!("GL_FOG {}", if g.lt.fog_on != 0 { "on" } else { "off" }),
light::T_MATERIAL_COMMIT => "GL_MATERIAL_COMMIT".to_string(),
T_BLEND_MODE => format!("GL_BLEND_MODE {}", a[0]),
T_IRIS_BUFFER => format!("IRISGL_BUFFER {} {}", a[0], a[1]),
T_IRIS_BUFFER => format!("IRISGL_READSOURCE plane {} bank {}", a[0], a[1]),
T_AUX_WRITEMASK => format!("GL_AUX_WRITEMASK {:#x}", a[0]),
T_IRIS_SBOX | T_IRIS_SBOXI | T_IRIS_SBOXF | T_IRIS_SBOXFI => {
let num = |w: u32| if cmd & 0x4000 != 0 { w as i32 as f32 } else { f(w) };
format!("IRISGL_{} ({}, {})-({}, {})", if cmd & 0xff == T_IRIS_SBOX { "SBOX" } else { "SBOXF" },
num(a[0]), num(a[1]), num(a[2]), num(a[3]))
}
T_IRIS_WRITEMASK => format!("IRISGL_WRITEMASK {:#08x}", a[0]),
T_IRIS_CLEAR => format!("IRISGL_CLEAR rgb ({:.3}, {:.3}, {:.3}) rect {:?}", g.color[0], g.color[1], g.color[2], g.clip_rect()),
T_IRIS_ZCLEAR => format!("IRISGL_ZCLEAR {:#x}", a[0]),
Expand Down Expand Up @@ -1661,8 +1711,16 @@ impl Hq2Engine {
self.gl.blend_sent = 1;
}
out.reg(re3::REG_NOPUP, 1);
out.reg(re3::REG_UAUXDATA, 0);
out.reg(re3::REG_AUXMASK, 0);
if self.gl.aux_draw != 0 {
// Overlay / popup drawing: only the aux planes 0x003 names, the
// index as the X server's 2D path writes it (value2d).
out.reg(re3::REG_AUXMASK, self.gl.aux_wmask);
let idx = (self.gl.color[0] * 255.0).round() as u32;
out.reg(re3::REG_UAUXDATA, self.gl.aux_value(idx));
} else {
out.reg(re3::REG_UAUXDATA, 0);
out.reg(re3::REG_AUXMASK, 0);
}
if self.gl.wid_clip() {
// More visible pieces than 0x1E5 carries: draw only where the
// CID planes (painted by Xsgi, 2D_CID_WRITE) hold this window's
Expand All @@ -1675,7 +1733,7 @@ impl Hq2Engine {
}
// Stipple patterns are span-relative (see gl_stipple_pattern).
out.reg(re3::REG_ALIGNPAT, 0);
let mask = self.gl.masks[self.gl.swap as usize & 1] & self.gl.colormask;
let mask = if self.gl.aux_draw != 0 { 0 } else { self.gl.masks[self.gl.swap as usize & 1] & self.gl.colormask };
out.reg(re3::REG_PIXMASK, mask & 0x00ff_ffff);
// Always: a 2D 12-bit draw (MODE 2) may have left RGB12 behind.
out.op(re3::RE3_OP_PIXFMT, self.gl.pixfmt as u64);
Expand Down Expand Up @@ -1703,6 +1761,11 @@ impl Hq2Engine {

/// Undo GL-only RE3 state so 2D drawing sees what it expects.
fn gl_done(&mut self, out: &mut dyn Re3Sink) {
if self.gl.aux_draw != 0 {
// 2D drawing keeps AUXMASK only for its own aux modes.
out.reg(re3::REG_AUXMASK, 0);
out.reg(re3::REG_UAUXDATA, 0);
}
if self.gl.pixfmt != 0 {
out.op(re3::RE3_OP_PIXFMT, 0);
out.reg(re3::REG_ENABDITH, 0);
Expand Down Expand Up @@ -1774,6 +1837,9 @@ impl Hq2Engine {
}

fn gl_color_regs(&mut self, c: [u32; 3], out: &mut dyn Re3Sink) {
if self.gl.aux_draw != 0 {
out.reg(re3::REG_UAUXDATA, self.gl.aux_value(c[0] >> 11));
}
out.reg(re3::REG_R, c[0]);
out.reg(re3::REG_G, c[1]);
out.reg(re3::REG_B, c[2]);
Expand Down Expand Up @@ -1968,6 +2034,40 @@ impl Hq2Engine {
self.gl_done(out);
}

/// 0x052 sbox: the outline of the screen-aligned box between two
/// transformed corners, in the current colour, each pixel once (top
/// and bottom rows, then the sides between them).
fn gl_sbox(&mut self, ints: bool, a: &[u32], out: &mut dyn Re3Sink) {
let num = |w: u32| if ints { w as i32 as f32 } else { f(w) };
let p = self.gl.transform([num(a[0]), num(a[1]), 0.0, 1.0]);
let q = self.gl.transform([num(a[2]), num(a[3]), 0.0, 1.0]);
if p.ok == 0 || q.ok == 0 {
return;
}
let (x0, x1) = (p.x.min(q.x).floor() as i32, p.x.max(q.x).floor() as i32);
let (y0, y1) = (p.y.min(q.y).floor() as i32, p.y.max(q.y).floor() as i32);
self.gl_setup(out);
let c = self.gl.color;
self.gl_color_regs(to_fixed(c, self.gl.cmax()), out);
let mut run = |e: &mut Self, xa: i32, xb: i32, y: i32| {
let (pieces, np) = e.gl.span_pieces(y, xa, xb + 1);
for &(s, t) in &pieces[..np] {
e.span(s, y, (t - s) as u32, None, out);
}
};
run(self, x0, x1, y0);
if y1 > y0 {
run(self, x0, x1, y1);
}
for y in y0 + 1..y1 {
run(self, x0, x0, y);
if x1 > x0 {
run(self, x1, x1, y);
}
}
self.gl_done(out);
}

fn gl_point(&mut self, v: Wv, color: [f32; 4], out: &mut dyn Re3Sink) {
if v.ok == 0 || v.oc != 0 {
return;
Expand Down
16 changes: 13 additions & 3 deletions src/dev/gr2/hq2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ pub const HQ2_2D_POLYLINE: u32 = 302;
pub const HQ2_2D_POLYLINE_ALT: u32 = 357;
/// Segments: stream of (x1, y1, x2, y2), padded with (1280, 1024) pairs.
pub const HQ2_2D_SEGMENTS: u32 = 345;
/// Points (expPolyPoint, XDrawPoints): 0; stream of (x, y), padded with
/// (1280, 1024) pairs, clipped to LINE_CLIP.
pub const HQ2_2D_POLYPOINT: u32 = 353;
/// Stipple opaque flag (1 = zero bits get bg).
pub const HQ2_2D_STIPPLE_AUX: u32 = 313;
/// Stippled box: 0; DATA origin x, x1, origin y, y1, x2, y2.
Expand Down Expand Up @@ -392,7 +395,7 @@ pub fn token_name(index: u32) -> Option<&'static str> {
HQ2_2D_COLOR_AUX => "2D_COLOR_AUX", HQ2_2D_COLOR_OFF => "2D_COLOR_OFF", HQ2_2D_COLOR_ON => "2D_COLOR_ON",
313 => "2D_STIPPLE_AUX", HQ2_2D_TILE_SETUP => "2D_TILE_SETUP", 315..=318 => "2D_TILE_DATA",
HQ2_2D_STIPPLE_BOX_POW2 | HQ2_2D_STIPPLE_BOX => "2D_STIPPLE_BOX", HQ2_2D_TILE_RECT | HQ2_2D_TILE_RECT_ODD => "2D_TILE_RECT",
HQ2_2D_POLYLINE => "2D_POLYLINE", HQ2_2D_POLYLINE_ALT => "2D_POLYLINE", HQ2_2D_SEGMENTS => "2D_SEGMENTS",
HQ2_2D_POLYLINE => "2D_POLYLINE", HQ2_2D_POLYLINE_ALT => "2D_POLYLINE", HQ2_2D_SEGMENTS => "2D_SEGMENTS", HQ2_2D_POLYPOINT => "2D_POLYPOINT",
323 => "2D_GLYPH_8", 324 => "2D_GLYPH_16", 325 => "2D_GLYPH_32", 326 => "2D_GLYPH_64",
HQ2_2D_LINE_MODE => "2D_LINE_MODE", HQ2_2D_LINE_CLIP => "2D_LINE_CLIP", HQ2_2D_MODE => "2D_MODE", HQ2_2D_ROP => "2D_ROP",
HQ2_2D_CID_WRITE => "2D_CID_WRITE", HQ2_2D_BUF_SELECT => "2D_BUF_SELECT",
Expand Down Expand Up @@ -679,7 +682,8 @@ impl Hq2Engine {
HQ2_2D_LINE_CLIP => 4,
HQ2_2D_STIPPLE_RECT | HQ2_2D_STIPPLE_BOX_POW2 | HQ2_2D_STIPPLE_BOX => 7,
HQ2_2D_STIPPLED_SPAN_A | HQ2_2D_STIPPLED_SPAN_B => STREAM,
HQ2_2D_POLY_SPAN | HQ2_2D_POLYLINE | HQ2_2D_POLYLINE_ALT | HQ2_2D_SEGMENTS | HQ2_2D_LINE_SEG => STREAM,
HQ2_2D_POLY_SPAN | HQ2_2D_POLYLINE | HQ2_2D_POLYLINE_ALT | HQ2_2D_SEGMENTS | HQ2_2D_LINE_SEG
| HQ2_2D_POLYPOINT => STREAM,
HQ2_2D_SOLID_RECT => STREAM,
HQ2_2D_TILE_RECT | HQ2_2D_TILE_RECT_ODD => 7,
HQ2_2D_DRAW_IMAGE | HQ2_2D_DRAW_IMAGE_SMALL | HQ2_DMA_WRITE_PIXELS
Expand Down Expand Up @@ -868,7 +872,7 @@ impl Hq2Engine {
}
let group = match self.cmd {
HQ2_2D_POLY_SPAN => 3,
HQ2_2D_POLYLINE | HQ2_2D_POLYLINE_ALT => 2,
HQ2_2D_POLYLINE | HQ2_2D_POLYLINE_ALT | HQ2_2D_POLYPOINT => 2,
HQ2_2D_SEGMENTS | HQ2_2D_LINE_SEG | HQ2_2D_STIPPLED_SPAN_A | HQ2_2D_STIPPLED_SPAN_B => 4,
_ => 0,
};
Expand Down Expand Up @@ -902,6 +906,12 @@ impl Hq2Engine {
a[1] as i32, a[2] as i32, a[3] as i32, a[4], self.stipple_opaque, self.s2d.color_on, self.s2d.color_off));
}
}
HQ2_2D_POLYPOINT => {
if !(a[1] == PAD_X && a[2] == PAD_Y) {
let p = [a[1] as i32, a[2] as i32];
self.line2d(p, p, true, out);
}
}
HQ2_2D_SEGMENTS | HQ2_2D_LINE_SEG => {
// Padding: (1280, 1024) pairs (SEGMENTS) or x1 = x2
// = 1280 with y 0 (LINE_SEG, 4Dwm frames).
Expand Down
67 changes: 67 additions & 0 deletions src/dev/gr2/hq2_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,17 @@ fn ddx_glyphs_lines_spans_stipples() {
// Segments with sentinel padding.
cmd(g, HQ2_2D_SEGMENTS, 0);
for v in [0x100, 0x80, 0x110, 0x80, 1280, 1024, 1280, 1024] { data(g, v); }
// Points (xlock pop's 2x2 sparks), clipped to x <= 0x1ff, padded.
cmd(g, HQ2_2D_LINE_CLIP, 0);
for v in [0x1ff, 0, 0x3ff] { data(g, v); }
cmd(g, HQ2_2D_POLYPOINT, 0);
for v in [0x30, 0x40, 0x31, 0x40, 0x20c, 0x1a5, 1280, 1024] { data(g, v); }
cmd(g, HQ2_2D_LINE_CLIP, 0);
for v in [0x7ff, 0, 0x3ff] { data(g, v); }
cmd(g, HQ2_2D_END_PRIMITIVE, 0);
g.wait_idle();
assert_eq!((px(0x30, 0x40), px(0x31, 0x40), px(0x32, 0x40)), (7, 7, 0), "points");
assert_eq!(px(0x20c, 0x1a5), 0, "point outside the clip");
assert_eq!((px(0x15, 0x300), px(0x1d, 0x300), px(0x1e, 0x300)), (7, 7, 0));
assert_eq!(px(0x16, 0x301), 7);
assert!((0x3e0..=0x3ee).all(|y| px(0x11, y) == 7), "vertical run");
Expand Down Expand Up @@ -3031,3 +3040,61 @@ fn bench_gl_smooth_triangles() {
println!("{size:5} px legs: {:8.0} tris/s {:6.1} Mpx/s", n as f64 / s, n as f64 * size as f64 * size as f64 / 2.0 / s / 1e6);
}
}

/// IRIS GL drawmode(OVERDRAW) (Blender's toolbox, IRIX 5.3): MakeCurrent
/// 11, colour planes masked off (0x005 = 0), aux write mask 0x003 = 3 (the
/// 2-bit overlay, aux bits 1:0), then sboxfi / sboxi (0x4053 / 0x4052,
/// ITOF) in colour indices set by 0x7030. Only the overlay bits change;
/// sbox draws the outline alone.
#[test]
fn iris_overdraw_boxes_into_the_overlay_planes() {
let g = &live_gr2(Gr2Variant::Xz);
gl_setup_window(g);
let ints = |g: &Gr2, tok: u32, v: [u32; 4]| {
cmd(g, tok, v[0]);
for &w in &v[1..] { cmd(g, 0x41df, w); }
};
cmd(g, 0x004, 11);
cmd(g, 0x005, 0);
cmd(g, 0x003, 3);
cmd(g, 0x008, 1);
cmd(g, 0x008, 0);
cmd(g, 0x7030, 2);
ints(g, 0x4053, [10, 10, 20, 20]);
cmd(g, 0x7030, 1);
ints(g, 0x4052, [30, 10, 40, 20]);
// Back to normal drawing, as Blender does.
cmd(g, 0x004, 2);
cmd(g, 0x003, 0);
g.wait_idle();
let aux = |x: i32, y: i32| (g.vram()[((660 + y) as usize) * FB_W + (64 + x) as usize] >> 24) & 0xf;
assert_eq!(aux(15, 15), 2, "filled box in the overlay");
assert_eq!(gl_px(g, 15, 15), 0, "colour planes untouched");
assert_eq!(aux(30, 15), 1, "outline left side");
assert_eq!(aux(35, 20), 1, "outline top");
assert_eq!(aux(35, 15), 0, "outline only, not filled");
assert_eq!(aux(25, 15), 0, "between the boxes");
assert_eq!(g.re3_reg(super::re3::REG_AUXMASK), 0, "aux mask reset for 2D");
}

/// IRIS GL readsource (0x008 = plane, absolute bank) picks the bank a
/// rectcopy reads. Blender keeps its views in the back buffer and copies
/// them to the front with readsource bank 1 and write mask 0x000FFF; reading
/// bank 0 instead left those views black.
#[test]
fn iris_rectcopy_reads_the_readsource_bank() {
let g = &live_gr2(Gr2Variant::Xz);
gl_setup_window(g);
cmd(g, 0x004, 2); // MakeCurrent: 12-bit RGB
cmd(g, 0x005, 0xfff000); // draw into bank 1 only
cmd(g, 0x6913, 0x00ff_ffff); // cpack white
cmd(g, 0x09e, 0); // clear
cmd(g, 0x005, 0x000fff); // now bank 0 ...
cmd(g, 0x008, 0);
cmd(g, 0x008, 1); // ... reading bank 1
cmd(g, 0x0ba, 0);
for v in [20, 40, 1, 10, 50, 3] { data(g, v); }
g.wait_idle();
assert_eq!(gl_px(g, 50, 40), 0xfff_fff, "bank 1 copied into bank 0");
assert_eq!(gl_px(g, 53, 40), 0xfff_000, "outside the copy bank 0 is untouched");
}
Loading
Loading