game_toolkit_dosvideo/lib.rs
1//! DOS-era PC video for [game-toolkit](https://docs.rs/game-toolkit): VGA (mode 13h, Mode X,
2//! planar 16-color, text), EGA, CGA (with composite artifact colors and snow), Hercules,
3//! Tandy 1000 and VESA SVGA, emulated at the register level with a beam-accurate CRT.
4//!
5//! # The model
6//!
7//! A [`Dos`] is a machine with a clock. Everything a program does costs time: a port write
8//! ([`Dos::outb`]), a video memory write ([`Dos::poke`], priced by the configured
9//! [`BusTiming`]), computation you choose to charge ([`Dos::cpu`]), and waiting for the beam
10//! ([`Dos::wait_vretrace`], [`Dos::wait_line`]) or polling the status port like DOS programs
11//! did (`while dos.inb(0x3DA) & 8 == 0 {}` works).
12//!
13//! The CRT paints the picture as the beam sweeps, from the adapter's state *at that moment*.
14//! So all the tricks of the era fall out by themselves: palette changes between scanlines
15//! (copper bars), changing the start address or line compare for page flipping and split
16//! screens, pel panning for smooth scrolling, palette rotation, and the failure modes too:
17//! tearing when a copy is overtaken by the beam, a start address that only latches at
18//! vertical retrace, the attribute controller flip-flop, CRTC write protection, CGA snow.
19//!
20//! # Running a program
21//!
22//! With the default `toolkit` feature, implement [`DosGame`] and hand it to [`run`]. `step` is
23//! one pass of your main loop; the host calls it as often as emulated time allows and shows the
24//! frames the CRT produces.
25//!
26//! ```no_run
27//! use game_toolkit_dosvideo::prelude::*;
28//!
29//! struct Hello;
30//!
31//! impl DosGame for Hello {
32//! fn init(dos: &mut Dos) -> anyhow::Result<Self> {
33//! dos.set_mode(0x13);
34//! Ok(Self)
35//! }
36//!
37//! fn step(&mut self, dos: &mut Dos) {
38//! dos.wait_vretrace();
39//! let t = dos.frames() as u32;
40//! for y in 0..200u32 {
41//! let row: Vec<u8> = (0..320u32).map(|x| ((x ^ y) + t) as u8).collect();
42//! dos.poke_slice(0xA0000 + y * 320, &row);
43//! }
44//! if dos.key(sc::ESC) {
45//! dos.exit();
46//! }
47//! }
48//! }
49//!
50//! fn main() -> anyhow::Result<()> {
51//! run::<Hello>(DosConfig::new("hello", Adapter::Vga))
52//! }
53//! ```
54
55#![forbid(unsafe_code)]
56
57pub mod cga;
58mod crt;
59pub mod draw;
60pub mod font;
61pub mod hercules;
62pub mod input;
63mod machine;
64pub mod palette;
65mod png;
66pub mod speaker;
67#[cfg(feature = "synth")]
68pub mod speaker_synth;
69pub mod svga;
70pub mod text;
71#[cfg(feature = "ratatui")]
72pub mod tui;
73pub mod vga;
74
75#[cfg(feature = "toolkit")]
76mod crt_filter;
77#[cfg(feature = "toolkit")]
78mod host;
79
80pub use cga::CgaMonitor;
81pub use crt::{Beam, FrameView, Timing};
82pub use font::Font;
83pub use input::{KeyPress, Keyboard, Mouse, sc};
84pub use machine::{Adapter, BusTiming, Dos, MIN_IO_NS, MachineConfig, PIT_HZ, Video, cpu};
85pub use palette::Phosphor;
86pub use svga::VbeMode;
87
88#[cfg(feature = "toolkit")]
89pub use crt_filter::{CrtFilter, PhosphorMask};
90#[cfg(feature = "toolkit")]
91pub use host::{Aspect, DosConfig, DosGame, DosScreen, run};
92
93/// `use game_toolkit_dosvideo::prelude::*;`
94pub mod prelude {
95 pub use crate::draw::{Bitmap, Dither, ModeX};
96 pub use crate::palette::color;
97 pub use crate::speaker::Melody;
98 pub use crate::{
99 Adapter, Beam, BusTiming, CgaMonitor, Dos, Font, MachineConfig, Phosphor, Timing, cpu, sc,
100 };
101 #[cfg(feature = "toolkit")]
102 pub use crate::{Aspect, CrtFilter, DosConfig, DosGame, DosScreen, PhosphorMask, run};
103 #[cfg(feature = "toolkit")]
104 pub use game_toolkit::prelude::{Painter, anyhow};
105}
106
107#[cfg(feature = "toolkit")]
108pub use game_toolkit;
109/// The sound hardware behind [`speaker`]: prepared speaker music, the crystal clock.
110pub use game_toolkit_dosaudio;