1use crate::font::Font;
9use crate::machine::{Dos, Video};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct Bitmap {
17 pub width: u32,
18 pub height: u32,
19 pub data: Vec<u8>,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
25pub enum Dither {
26 #[default]
28 None,
29 Bayer2,
31 Bayer4,
33 Bayer8,
35 FloydSteinberg,
37}
38
39const BAYER8: [[u8; 8]; 8] = [
40 [0, 32, 8, 40, 2, 34, 10, 42],
41 [48, 16, 56, 24, 50, 18, 58, 26],
42 [12, 44, 4, 36, 14, 46, 6, 38],
43 [60, 28, 52, 20, 62, 30, 54, 22],
44 [3, 35, 11, 43, 1, 33, 9, 41],
45 [51, 19, 59, 27, 49, 17, 57, 25],
46 [15, 47, 7, 39, 13, 45, 5, 37],
47 [63, 31, 55, 23, 61, 29, 53, 21],
48];
49
50pub fn nearest_color(palette: &[[u8; 3]], c: [f32; 3]) -> u8 {
52 let mut best = 0;
53 let mut best_d = f32::MAX;
54 for (i, p) in palette.iter().enumerate() {
55 let dr = c[0] - p[0] as f32;
56 let dg = c[1] - p[1] as f32;
57 let db = c[2] - p[2] as f32;
58 let d = 2.0 * dr * dr + 4.0 * dg * dg + 3.0 * db * db;
59 if d < best_d {
60 best_d = d;
61 best = i;
62 }
63 }
64 best as u8
65}
66
67impl Bitmap {
68 pub fn from_rgba(
73 width: u32,
74 height: u32,
75 rgba: &[u8],
76 palette: &[[u8; 3]],
77 dither: Dither,
78 ) -> Self {
79 let mut b = Self::new(width, height);
80 if palette.is_empty() {
81 return b;
82 }
83 let px = |x: u32, y: u32| {
84 let i = ((y * width + x) * 4) as usize;
85 [rgba[i] as f32, rgba[i + 1] as f32, rgba[i + 2] as f32]
86 };
87 let spread = 0.8 * 255.0 / (palette.len() as f32).cbrt();
89 match dither {
90 Dither::None | Dither::Bayer2 | Dither::Bayer4 | Dither::Bayer8 => {
91 let n = match dither {
92 Dither::Bayer2 => 2,
93 Dither::Bayer4 => 4,
94 Dither::Bayer8 => 8,
95 _ => 0,
96 };
97 for y in 0..height {
98 for x in 0..width {
99 let mut c = px(x, y);
100 if let Some(s) = 8usize.checked_div(n) {
101 let m = BAYER8[(y as usize % n) * s][(x as usize % n) * s] as f32;
103 let t = ((m + 0.5) / (n * n) as f32 - 0.5) * spread;
104 for ch in &mut c {
105 *ch += t;
106 }
107 }
108 b.data[(y * width + x) as usize] = nearest_color(palette, c);
109 }
110 }
111 }
112 Dither::FloydSteinberg => {
113 let w = width as usize;
114 let mut err = vec![[0.0f32; 3]; w * 2];
115 for y in 0..height {
116 let (cur, next) = err.split_at_mut(w);
117 for x in 0..width as usize {
118 let o = px(x as u32, y);
119 let c = [o[0] + cur[x][0], o[1] + cur[x][1], o[2] + cur[x][2]];
120 let i = nearest_color(palette, c);
121 b.data[y as usize * w + x] = i;
122 let p = palette[i as usize];
123 for ch in 0..3 {
124 let e = c[ch] - p[ch] as f32;
125 if x + 1 < w {
126 cur[x + 1][ch] += e * 7.0 / 16.0;
127 next[x + 1][ch] += e * 1.0 / 16.0;
128 }
129 if x > 0 {
130 next[x - 1][ch] += e * 3.0 / 16.0;
131 }
132 next[x][ch] += e * 5.0 / 16.0;
133 }
134 }
135 let (cur, next) = err.split_at_mut(w);
136 cur.copy_from_slice(next);
137 next.fill([0.0; 3]);
138 }
139 }
140 }
141 b
142 }
143
144 pub fn new(width: u32, height: u32) -> Self {
145 Self {
146 width,
147 height,
148 data: vec![0; (width * height) as usize],
149 }
150 }
151
152 pub fn from_data(width: u32, height: u32, data: Vec<u8>) -> Option<Self> {
153 (data.len() == (width * height) as usize).then_some(Self {
154 width,
155 height,
156 data,
157 })
158 }
159
160 pub fn from_art(rows: &[&str], palette: &[(char, u8)]) -> Self {
163 let h = rows.len() as u32;
164 let w = rows.iter().map(|r| r.chars().count()).max().unwrap_or(0) as u32;
165 let mut b = Self::new(w, h);
166 for (y, r) in rows.iter().enumerate() {
167 for (x, ch) in r.chars().enumerate() {
168 let c = palette.iter().find(|p| p.0 == ch).map_or(0, |p| p.1);
169 b.data[y * w as usize + x] = c;
170 }
171 }
172 b
173 }
174
175 #[inline]
176 pub fn get(&self, x: i32, y: i32) -> u8 {
177 if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 {
178 return 0;
179 }
180 self.data[(y as u32 * self.width + x as u32) as usize]
181 }
182
183 #[inline]
184 pub fn pset(&mut self, x: i32, y: i32, c: u8) {
185 if x < 0 || y < 0 || x >= self.width as i32 || y >= self.height as i32 {
186 return;
187 }
188 self.data[(y as u32 * self.width + x as u32) as usize] = c;
189 }
190
191 pub fn clear(&mut self, c: u8) {
192 self.data.fill(c);
193 }
194
195 pub fn row(&self, y: u32) -> &[u8] {
196 let w = self.width as usize;
197 &self.data[y as usize * w..(y as usize + 1) * w]
198 }
199
200 pub fn row_mut(&mut self, y: u32) -> &mut [u8] {
201 let w = self.width as usize;
202 &mut self.data[y as usize * w..(y as usize + 1) * w]
203 }
204
205 pub fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, c: u8) {
206 let x0 = x.max(0);
207 let y0 = y.max(0);
208 let x1 = (x + w).min(self.width as i32);
209 let y1 = (y + h).min(self.height as i32);
210 if x0 >= x1 || y0 >= y1 {
211 return;
212 }
213 for yy in y0..y1 {
214 let o = (yy as u32 * self.width) as usize;
215 self.data[o + x0 as usize..o + x1 as usize].fill(c);
216 }
217 }
218
219 pub fn rect(&mut self, x: i32, y: i32, w: i32, h: i32, c: u8) {
220 self.hline(x, x + w - 1, y, c);
221 self.hline(x, x + w - 1, y + h - 1, c);
222 self.vline(x, y, y + h - 1, c);
223 self.vline(x + w - 1, y, y + h - 1, c);
224 }
225
226 pub fn hline(&mut self, x0: i32, x1: i32, y: i32, c: u8) {
227 let (a, b) = (x0.min(x1), x0.max(x1));
228 self.fill_rect(a, y, b - a + 1, 1, c);
229 }
230
231 pub fn vline(&mut self, x: i32, y0: i32, y1: i32, c: u8) {
232 let (a, b) = (y0.min(y1), y0.max(y1));
233 self.fill_rect(x, a, 1, b - a + 1, c);
234 }
235
236 pub fn line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, c: u8) {
238 let (mut x, mut y) = (x0, y0);
239 let dx = (x1 - x0).abs();
240 let dy = -(y1 - y0).abs();
241 let sx = if x0 < x1 { 1 } else { -1 };
242 let sy = if y0 < y1 { 1 } else { -1 };
243 let mut err = dx + dy;
244 loop {
245 self.pset(x, y, c);
246 if x == x1 && y == y1 {
247 break;
248 }
249 let e2 = 2 * err;
250 if e2 >= dy {
251 err += dy;
252 x += sx;
253 }
254 if e2 <= dx {
255 err += dx;
256 y += sy;
257 }
258 }
259 }
260
261 pub fn circle(&mut self, cx: i32, cy: i32, r: i32, c: u8, fill: bool) {
263 let (mut x, mut y, mut d) = (r, 0, 1 - r);
264 while x >= y {
265 if fill {
266 self.hline(cx - x, cx + x, cy + y, c);
267 self.hline(cx - x, cx + x, cy - y, c);
268 self.hline(cx - y, cx + y, cy + x, c);
269 self.hline(cx - y, cx + y, cy - x, c);
270 } else {
271 for (px, py) in [
272 (x, y),
273 (y, x),
274 (-y, x),
275 (-x, y),
276 (-x, -y),
277 (-y, -x),
278 (y, -x),
279 (x, -y),
280 ] {
281 self.pset(cx + px, cy + py, c);
282 }
283 }
284 y += 1;
285 if d < 0 {
286 d += 2 * y + 1;
287 } else {
288 x -= 1;
289 d += 2 * (y - x) + 1;
290 }
291 }
292 }
293
294 pub fn blit(&mut self, src: &Bitmap, x: i32, y: i32, transparent: Option<u8>) {
296 for sy in 0..src.height as i32 {
297 let dy = y + sy;
298 if dy < 0 || dy >= self.height as i32 {
299 continue;
300 }
301 for sx in 0..src.width as i32 {
302 let dx = x + sx;
303 if dx < 0 || dx >= self.width as i32 {
304 continue;
305 }
306 let c = src.data[(sy as u32 * src.width + sx as u32) as usize];
307 if Some(c) != transparent {
308 self.data[(dy as u32 * self.width + dx as u32) as usize] = c;
309 }
310 }
311 }
312 }
313
314 #[allow(clippy::too_many_arguments)]
316 pub fn blit_region(
317 &mut self,
318 src: &Bitmap,
319 sx: i32,
320 sy: i32,
321 w: i32,
322 h: i32,
323 x: i32,
324 y: i32,
325 transparent: Option<u8>,
326 ) {
327 for yy in 0..h {
328 for xx in 0..w {
329 let c = src.get(sx + xx, sy + yy);
330 if Some(c) != transparent {
331 self.pset(x + xx, y + yy, c);
332 }
333 }
334 }
335 }
336
337 pub fn text(&mut self, x: i32, y: i32, s: &str, fg: u8, bg: Option<u8>, font: &Font) {
339 let h = font.height() as i32;
340 for (i, ch) in crate::font::cp437(s).into_iter().enumerate() {
341 let g = font.glyph(ch);
342 let cx = x + i as i32 * 8;
343 for r in 0..h {
344 for b in 0..8 {
345 if (g[r as usize] >> (7 - b)) & 1 != 0 {
346 self.pset(cx + b, y + r, fg);
347 } else if let Some(bg) = bg {
348 self.pset(cx + b, y + r, bg);
349 }
350 }
351 }
352 }
353 }
354}
355
356fn crtc_port(dos: &Dos) -> u16 {
358 match dos.video() {
359 Video::Hercules(_) => 0x3B4,
360 Video::Vga(v) if v.misc & 1 == 0 => 0x3B4,
361 Video::Svga(s) if s.vga.misc & 1 == 0 => 0x3B4,
362 _ => 0x3D4,
363 }
364}
365
366fn status_port(dos: &Dos) -> u16 {
367 crtc_port(dos) + 6
368}
369
370fn vga_ref(dos: &Dos) -> Option<&crate::vga::Vga> {
371 dos.video().as_vga()
372}
373
374impl Dos {
375 pub fn set_palette(&mut self, i: u8, rgb: [u8; 3]) {
380 self.outb(0x3C8, i);
381 self.outb(0x3C9, rgb[0]);
382 self.outb(0x3C9, rgb[1]);
383 self.outb(0x3C9, rgb[2]);
384 }
385
386 pub fn set_palette_range(&mut self, start: u8, colors: &[[u8; 3]]) {
388 self.outb(0x3C8, start);
389 for c in colors {
390 self.outb(0x3C9, c[0]);
391 self.outb(0x3C9, c[1]);
392 self.outb(0x3C9, c[2]);
393 }
394 }
395
396 pub fn set_palette8(&mut self, i: u8, rgb: [u8; 3]) {
398 let eight = vga_ref(self).is_some_and(|v| v.dac_8bit);
399 let c = if eight {
400 rgb
401 } else {
402 [rgb[0] >> 2, rgb[1] >> 2, rgb[2] >> 2]
403 };
404 self.set_palette(i, c);
405 }
406
407 pub fn palette(&mut self, i: u8) -> [u8; 3] {
409 self.outb(0x3C7, i);
410 [self.inb(0x3C9), self.inb(0x3C9), self.inb(0x3C9)]
411 }
412
413 pub fn dac(&self) -> Option<[[u8; 3]; 256]> {
415 vga_ref(self).map(|v| v.dac)
416 }
417
418 pub fn cycle_palette(&mut self, first: u8, last: u8) {
421 let Some(dac) = self.dac() else { return };
422 let (a, b) = (first as usize, last as usize);
423 if b <= a {
424 return;
425 }
426 let mut seg: Vec<[u8; 3]> = dac[a..=b].to_vec();
427 seg.rotate_right(1);
428 self.set_palette_range(first, &seg);
429 }
430
431 pub fn fade_palette(&mut self, base: &[[u8; 3]; 256], level: u8) {
433 let l = level.min(64) as u16;
434 let faded: Vec<[u8; 3]> = base
435 .iter()
436 .map(|c| {
437 [
438 (c[0] as u16 * l / 64) as u8,
439 (c[1] as u16 * l / 64) as u8,
440 (c[2] as u16 * l / 64) as u8,
441 ]
442 })
443 .collect();
444 self.set_palette_range(0, &faded);
445 }
446
447 pub fn seq(&mut self, index: u8, v: u8) {
452 self.outb(0x3C4, index);
453 self.outb(0x3C5, v);
454 }
455
456 pub fn gc(&mut self, index: u8, v: u8) {
458 self.outb(0x3CE, index);
459 self.outb(0x3CF, v);
460 }
461
462 pub fn crtc(&mut self, index: u8, v: u8) {
464 let p = crtc_port(self);
465 self.outb(p, index);
466 self.outb(p + 1, v);
467 }
468
469 pub fn crtc_value(&self, index: u8) -> u8 {
472 match self.video() {
473 Video::Vga(v) => v.crtc.get(index as usize).copied().unwrap_or(0),
474 Video::Svga(s) => s.vga.crtc.get(index as usize).copied().unwrap_or(0),
475 Video::Cga(c) => c.crtc.r.get(index as usize).copied().unwrap_or(0),
476 Video::Hercules(h) => h.crtc.r.get(index as usize).copied().unwrap_or(0),
477 }
478 }
479
480 pub fn attr(&mut self, index: u8, v: u8) {
484 let s = status_port(self);
485 self.inb(s);
486 self.outb(0x3C0, index & 0x1F);
487 self.outb(0x3C0, v);
488 self.outb(0x3C0, 0x20);
489 }
490
491 pub fn map_mask(&mut self, planes: u8) {
493 self.seq(2, planes & 0x0F);
494 }
495
496 pub fn read_map(&mut self, plane: u8) {
498 self.gc(4, plane & 3);
499 }
500
501 pub fn write_mode(&mut self, mode: u8) {
503 let cur = vga_ref(self).map_or(0, |v| v.gc[5]);
504 self.gc(5, (cur & !3) | (mode & 3));
505 }
506
507 pub fn bit_mask(&mut self, mask: u8) {
509 self.gc(8, mask);
510 }
511
512 pub fn set_start_address(&mut self, addr: u16) {
516 self.crtc(0x0C, (addr >> 8) as u8);
517 self.crtc(0x0D, addr as u8);
518 }
519
520 pub fn start_address(&self) -> u16 {
522 ((self.crtc_value(0x0C) as u16) << 8) | self.crtc_value(0x0D) as u16
523 }
524
525 pub fn set_offset(&mut self, offset: u8) {
528 self.crtc(0x13, offset);
529 }
530
531 pub fn set_line_compare(&mut self, line: u16) {
535 let ov = self.crtc_value(7);
536 let msl = self.crtc_value(9);
537 self.crtc(0x18, line as u8);
538 self.crtc(7, (ov & !0x10) | (((line >> 8) & 1) as u8) << 4);
539 self.crtc(9, (msl & !0x40) | (((line >> 9) & 1) as u8) << 6);
540 }
541
542 pub fn set_pel_pan(&mut self, pan: u8) {
545 self.attr(0x13 | 0x20, pan & 0x0F);
546 }
547
548 pub fn set_border(&mut self, c: u8) {
551 match self.video() {
552 Video::Cga(cga) => {
553 let col = cga.color;
554 self.outb(0x3D9, (col & 0xF0) | (c & 0x0F));
555 }
556 Video::Hercules(_) => {}
557 _ => self.attr(0x11 | 0x20, c),
558 }
559 }
560
561 pub fn set_attr_palette(&mut self, i: u8, v: u8) {
563 self.attr((i & 0x0F) | 0x20, v & 0x3F);
564 }
565
566 pub fn screen_on(&mut self, on: bool) {
568 let cur = vga_ref(self).map_or(0, |v| v.seq[1]);
569 self.seq(1, if on { cur & !0x20 } else { cur | 0x20 });
570 }
571
572 pub fn pset13(&mut self, x: u32, y: u32, c: u8) {
577 if x < 320 && y < 200 {
578 self.poke(0xA0000 + y * 320 + x, c);
579 }
580 }
581
582 pub fn blit13(&mut self, bmp: &Bitmap) {
585 let n = (bmp.width * bmp.height).min(64000) as usize;
586 self.poke_slice(0xA0000, &bmp.data[..n]);
587 }
588
589 pub fn pset(&mut self, x: u32, y: u32, c: u8) {
596 let (w, h) = self.resolution();
597 if x >= w || y >= h {
598 return;
599 }
600 let mode = self.mode();
601 let chained = self.video().as_vga().is_some_and(|v| v.seq[4] & 0x08 != 0);
602 if self.vbe_mode().is_some() {
603 let addr = y * self.vbe_pitch() + x;
604 self.poke(crate::svga::LFB_BASE + addr, c);
605 return;
606 }
607 match self.video() {
608 Video::Vga(_) | Video::Svga(_) if mode == 0x13 => {
609 if chained {
610 self.pset13(x, y, c);
611 } else {
612 self.map_mask(1 << (x & 3));
613 self.poke(0xA0000 + y * (w / 4) + x / 4, c);
614 }
615 }
616 Video::Vga(_) | Video::Svga(_) => self.planar_pset(x, y, c),
617 Video::Cga(cga) => {
618 let tandy = cga.kind == crate::cga::CgaKind::Tandy;
619 let (addr, shift, bits) = match mode {
620 4 | 5 => (0x2000 * (y & 1) + (y >> 1) * 80 + x / 4, 6 - 2 * (x & 3), 2),
621 6 => (0x2000 * (y & 1) + (y >> 1) * 80 + x / 8, 7 - (x & 7), 1),
622 8 if tandy => (0x2000 * (y & 3) + (y >> 2) * 80 + x / 2, 4 - 4 * (x & 1), 4),
623 9 if tandy => (
624 0x2000 * (y & 3) + (y >> 2) * 160 + x / 2,
625 4 - 4 * (x & 1),
626 4,
627 ),
628 0x0A if tandy => {
629 let a = 0xB8000 + 0x2000 * (y & 3) + (y >> 2) * 160 + (x / 8) * 2;
630 let bit = 7 - (x & 7);
631 for plane in 0..2 {
632 let old = self.peek(a + plane);
633 let v = (old & !(1 << bit)) | (((c >> plane) & 1) << bit);
634 self.poke(a + plane, v);
635 }
636 return;
637 }
638 _ => return,
639 };
640 let a = 0xB8000 + addr;
641 let mask = (((1u16 << bits) - 1) as u8) << shift;
642 let old = self.peek(a);
643 self.poke(a, (old & !mask) | ((c << shift) & mask));
644 }
645 Video::Hercules(_) => self.herc_pset(x, y, c != 0, 0),
646 }
647 }
648
649 pub fn planar_pset(&mut self, x: u32, y: u32, c: u8) {
653 let (w, _) = self.resolution();
654 let addr = 0xA0000 + y * (w / 8) + x / 8;
655 self.gc(0, c & 0x0F);
656 self.gc(1, 0x0F);
657 self.gc(8, 0x80 >> (x & 7));
658 self.peek(addr);
659 self.poke(addr, 0xFF);
660 self.gc(1, 0x00);
661 self.gc(8, 0xFF);
662 }
663
664 pub fn planar_fill_rect(&mut self, x: u32, y: u32, w: u32, h: u32, c: u8) {
666 let (sw, sh) = self.resolution();
667 let x1 = (x + w).min(sw);
668 let y1 = (y + h).min(sh);
669 if x >= x1 || y >= y1 {
670 return;
671 }
672 let pitch = sw / 8;
673 self.gc(0, c & 0x0F);
674 self.gc(1, 0x0F);
675 let (bx0, bx1) = (x / 8, (x1 - 1) / 8);
676 for yy in y..y1 {
677 for bx in bx0..=bx1 {
678 let mut m = 0xFFu8;
679 if bx == bx0 {
680 m &= 0xFF >> (x & 7);
681 }
682 if bx == bx1 {
683 m &= 0xFF << (7 - ((x1 - 1) & 7));
684 }
685 let addr = 0xA0000 + yy * pitch + bx;
686 if m != 0xFF {
687 self.gc(8, m);
688 self.peek(addr);
689 } else {
690 self.gc(8, 0xFF);
691 }
692 self.poke(addr, 0xFF);
693 }
694 }
695 self.gc(1, 0x00);
696 self.gc(8, 0xFF);
697 }
698
699 pub fn planar_blit(&mut self, bmp: &Bitmap, x: u32, y: u32) {
703 let pitch = self.resolution().0 / 8;
704 self.planar_blit_at(bmp, 0xA0000 + y * pitch + x / 8, pitch);
705 }
706
707 pub fn planar_blit_at(&mut self, bmp: &Bitmap, addr: u32, pitch: u32) {
711 for plane in 0..4u8 {
712 self.map_mask(1 << plane);
713 for row in 0..bmp.height {
714 let bytes: Vec<u8> = bmp
715 .row(row)
716 .chunks(8)
717 .map(|c| {
718 c.iter()
719 .enumerate()
720 .fold(0u8, |acc, (i, &p)| acc | (((p >> plane) & 1) << (7 - i)))
721 })
722 .collect();
723 self.poke_slice(addr + row * pitch, &bytes);
724 }
725 }
726 self.map_mask(0x0F);
727 }
728
729 pub fn herc_pset(&mut self, x: u32, y: u32, on: bool, page: u32) {
731 if x >= 720 || y >= 348 {
732 return;
733 }
734 let a = 0xB0000 + (page & 1) * 0x8000 + (y & 3) * 0x2000 + (y >> 2) * 90 + x / 8;
735 let bit = 0x80 >> (x & 7);
736 let old = self.peek(a);
737 self.poke(a, if on { old | bit } else { old & !bit });
738 }
739
740 pub fn herc_blit(&mut self, packed: &[u8], page: u32) {
743 for y in 0..348u32 {
744 let row = &packed[(y * 90) as usize..((y + 1) * 90) as usize];
745 let a = 0xB0000 + (page & 1) * 0x8000 + (y & 3) * 0x2000 + (y >> 2) * 90;
746 self.poke_slice(a, row);
747 }
748 }
749
750 pub fn cga_blit(&mut self, bmp: &Bitmap) {
753 for y in 0..200u32.min(bmp.height) {
754 let row = bmp.row(y);
755 let bytes: Vec<u8> = row
756 .chunks(4)
757 .take(80)
758 .map(|c| {
759 c.iter()
760 .enumerate()
761 .fold(0, |a, (i, &p)| a | ((p & 3) << (6 - 2 * i)))
762 })
763 .collect();
764 self.poke_slice(0xB8000 + 0x2000 * (y & 1) + (y >> 1) * 80, &bytes);
765 }
766 }
767
768 pub fn tandy_blit(&mut self, bmp: &Bitmap) {
770 for y in 0..200u32.min(bmp.height) {
771 let row = bmp.row(y);
772 let bytes: Vec<u8> = row
773 .chunks(2)
774 .take(160)
775 .map(|c| (c[0] << 4) | (c.get(1).copied().unwrap_or(0) & 0x0F))
776 .collect();
777 self.poke_slice(0xB8000 + 0x2000 * (y & 3) + (y >> 2) * 160, &bytes);
778 }
779 }
780
781 pub fn vbe_pset_banked(&mut self, x: u32, y: u32, color: u32) {
788 let Some(m) = self.vbe_mode() else { return };
789 if x >= m.width || y >= m.height {
790 return;
791 }
792 let bpp = m.bytes_per_pixel();
793 let off = y * self.vbe_pitch() + x * bpp;
794 let bank = off / crate::svga::BANK_SIZE;
795 if bank != self.vbe_bank() {
796 self.vbe_set_bank(bank);
797 }
798 let a = 0xA0000 + off % crate::svga::BANK_SIZE;
799 match bpp {
800 1 => self.poke(a, color as u8),
801 2 => self.pokew(a, color as u16),
802 _ => self.poke32(a, color),
803 }
804 }
805
806 pub fn vbe_pset_lfb(&mut self, x: u32, y: u32, color: u32) {
808 let Some(m) = self.vbe_mode() else { return };
809 if x >= m.width || y >= m.height {
810 return;
811 }
812 let bpp = m.bytes_per_pixel();
813 let a = crate::svga::LFB_BASE + y * self.vbe_pitch() + x * bpp;
814 match bpp {
815 1 => self.poke(a, color as u8),
816 2 => self.pokew(a, color as u16),
817 _ => self.poke32(a, color),
818 }
819 }
820
821 pub fn vbe_write_banked(&mut self, off: u32, data: &[u8]) {
824 let mut done = 0usize;
825 while done < data.len() {
826 let o = off + done as u32;
827 let bank = o / crate::svga::BANK_SIZE;
828 if bank != self.vbe_bank() {
829 self.vbe_set_bank(bank);
830 }
831 let in_bank = o % crate::svga::BANK_SIZE;
832 let n = ((crate::svga::BANK_SIZE - in_bank) as usize).min(data.len() - done);
833 self.poke_slice(0xA0000 + in_bank, &data[done..done + n]);
834 done += n;
835 }
836 }
837
838 pub fn vbe_write_lfb(&mut self, off: u32, data: &[u8]) {
840 self.poke_slice(crate::svga::LFB_BASE + off, data);
841 }
842}
843
844#[derive(Clone, Copy, Debug)]
851pub struct ModeX {
852 pub width: u32,
853 pub height: u32,
854 pub page_size: u32,
856 pub visible: u32,
858 pub active: u32,
860}
861
862impl ModeX {
863 pub fn set(dos: &mut Dos, height: u32) -> Option<Self> {
867 if !dos.set_mode(0x13) {
868 return None;
869 }
870 dos.seq(4, 0x06); if height == 240 {
872 dos.seq(0, 0x01); dos.outb(0x3C2, 0xE3); dos.seq(0, 0x03);
875 dos.outb(0x3D4, 0x11);
876 let v = dos.inb(0x3D5);
877 dos.outb(0x3D5, v & 0x7F); for (i, v) in [
879 (0x06u8, 0x0Du8),
880 (0x07, 0x3E),
881 (0x09, 0x41),
882 (0x10, 0xEA),
883 (0x11, 0xAC),
884 (0x12, 0xDF),
885 (0x15, 0xE7),
886 (0x16, 0x06),
887 ] {
888 dos.crtc(i, v);
889 }
890 }
891 dos.crtc(0x14, 0x00); dos.crtc(0x17, 0xE3); dos.map_mask(0x0F);
894 dos.fill(0xA0000, 0x10000, 0);
895 if height == 240 {
896 dos.mouse_mut().set_range(0, 639, 0, 239);
897 }
898 let h = if height == 240 { 240 } else { 200 };
899 let page_size = 320 / 4 * h;
900 Some(Self {
901 width: 320,
902 height: h,
903 page_size,
904 visible: 0,
905 active: 1,
906 })
907 }
908
909 pub fn pages(&self) -> u32 {
911 0x10000 / self.page_size
912 }
913
914 pub fn page_offset(&self, page: u32) -> u32 {
916 page * self.page_size
917 }
918
919 fn pitch(&self) -> u32 {
920 self.width / 4
921 }
922
923 pub fn pset(&self, dos: &mut Dos, x: u32, y: u32, c: u8) {
925 if x >= self.width || y >= self.height {
926 return;
927 }
928 dos.map_mask(1 << (x & 3));
929 dos.poke(
930 0xA0000 + self.page_offset(self.active) + y * self.pitch() + x / 4,
931 c,
932 );
933 }
934
935 pub fn fill_rect(&self, dos: &mut Dos, x: i32, y: i32, w: i32, h: i32, c: u8) {
937 let x0 = x.max(0) as u32;
938 let y0 = y.max(0) as u32;
939 let x1 = ((x + w).max(0) as u32).min(self.width);
940 let y1 = ((y + h).max(0) as u32).min(self.height);
941 if x0 >= x1 || y0 >= y1 {
942 return;
943 }
944 let base = 0xA0000 + self.page_offset(self.active);
945 let (b0, b1) = (x0 / 4, (x1 - 1) / 4);
946 let lm = 0x0Fu8 << (x0 & 3) & 0x0F;
947 let rm = 0x0Fu8 >> (3 - ((x1 - 1) & 3));
948 for yy in y0..y1 {
949 let row = base + yy * self.pitch();
950 if b0 == b1 {
951 dos.map_mask(lm & rm);
952 dos.poke(row + b0, c);
953 continue;
954 }
955 dos.map_mask(lm);
956 dos.poke(row + b0, c);
957 if b1 > b0 + 1 {
958 dos.map_mask(0x0F);
959 dos.fill(row + b0 + 1, (b1 - b0 - 1) as usize, c);
960 }
961 dos.map_mask(rm);
962 dos.poke(row + b1, c);
963 }
964 }
965
966 pub fn clear(&self, dos: &mut Dos, c: u8) {
968 dos.map_mask(0x0F);
969 dos.fill(
970 0xA0000 + self.page_offset(self.active),
971 self.page_size as usize,
972 c,
973 );
974 }
975
976 pub fn blit(&self, dos: &mut Dos, bmp: &Bitmap, x: i32, y: i32, transparent: Option<u8>) {
979 let base = 0xA0000 + self.page_offset(self.active);
980 for plane in 0..4i32 {
981 dos.map_mask(1 << plane);
982 for row in 0..bmp.height as i32 {
983 let dy = y + row;
984 if dy < 0 || dy >= self.height as i32 {
985 continue;
986 }
987 let first = (plane - x).rem_euclid(4);
989 let mut run: Vec<u8> = Vec::with_capacity(bmp.width as usize / 4 + 1);
990 let mut run_start = None;
991 let mut sx = first;
992 while sx < bmp.width as i32 {
993 let dx = x + sx;
994 let c = bmp.get(sx, row);
995 if dx >= 0 && dx < self.width as i32 && Some(c) != transparent {
996 if run_start.is_none() {
997 run_start = Some(dx as u32 / 4);
998 }
999 run.push(c);
1000 } else if let Some(s) = run_start.take() {
1001 dos.poke_slice(base + dy as u32 * self.pitch() + s, &run);
1002 run.clear();
1003 }
1004 sx += 4;
1005 }
1006 if let Some(s) = run_start {
1007 dos.poke_slice(base + dy as u32 * self.pitch() + s, &run);
1008 }
1009 }
1010 }
1011 dos.map_mask(0x0F);
1012 }
1013
1014 pub fn copy_vram(&self, dos: &mut Dos, src: u32, dst: u32, len: u32) {
1018 dos.map_mask(0x0F);
1019 dos.write_mode(1);
1020 dos.copy_vram(0xA0000 + src, 0xA0000 + dst, len as usize);
1021 dos.write_mode(0);
1022 }
1023
1024 pub fn flip(&mut self, dos: &mut Dos) {
1028 self.visible = self.active;
1029 dos.set_start_address(self.page_offset(self.visible) as u16);
1030 dos.wait_vretrace();
1031 self.active = (self.active + 1) % 2;
1032 if self.active == self.visible {
1033 self.active = (self.active + 1) % 2;
1034 }
1035 }
1036
1037 pub fn flip_n(&mut self, dos: &mut Dos, n: u32) {
1039 self.visible = self.active;
1040 dos.set_start_address(self.page_offset(self.visible) as u16);
1041 dos.wait_vretrace();
1042 self.active = (self.active + 1) % n.clamp(2, self.pages());
1043 }
1044}