1#![feature(let_chains)]
2
3use std::collections::{BTreeMap, HashMap};
4use std::ops::{Deref, DerefMut};
5
6use bon::Builder;
7use cpclib_asm::{
8 IfBuilder, Listing, ListingBuilder, ListingExt, ListingFromStr, ListingSelector, Register8,
9 Register16, TestKind, dec_e, dec_l, inc_e, inc_l
10};
11use cpclib_image::convert::{SpriteEncoding, SpriteOutput};
12use itertools::Itertools;
13use smol_str::SmolStr;
14
15#[derive(Default)]
17pub enum RoutineAction {
18 #[default]
19 DrawPixelMaskedSprite,
20 SaveBackgroundAndDrawPixelMaskedSprite,
21 RestoreBackground
22}
23
24impl RoutineAction {
25 pub fn save_background(&self) -> bool {
26 matches!(self, RoutineAction::SaveBackgroundAndDrawPixelMaskedSprite)
27 }
28
29 pub fn build_register_store(&self) -> RegistersStore {
30 let regs: &[Register8] = match self {
31 RoutineAction::DrawPixelMaskedSprite => {
32 &[Register8::D, Register8::E, Register8::B, Register8::C]
33 },
34 RoutineAction::SaveBackgroundAndDrawPixelMaskedSprite => &[Register8::B, Register8::C],
35 RoutineAction::RestoreBackground => &[]
36 };
37 RegistersStore::new(regs)
38 }
39}
40
41#[derive(Default)]
42pub struct RegistersStore {
43 regs: HashMap<Register8, u8>,
44 available_regs: Vec<Register8>
45}
46
47impl RegistersStore {
48 pub fn new(regs: &[Register8]) -> Self {
49 Self {
50 regs: Default::default(),
51 available_regs: regs.to_vec()
52 }
53 }
54
55 pub fn listing(&self) -> Listing {
56 let mut lst = ListingBuilder::default();
57
58 let mut available_registers = self.regs.keys().collect_vec();
59 while let Some(first) = available_registers.pop() {
60 let second = first.neighbourg().unwrap();
62 if let Some(idx) = available_registers.iter().position(|v| **v == second) {
63 let second = available_registers.swap_remove(idx);
64 let complete = first.complete();
65 let value = self.value_for_r16(complete).unwrap();
66 lst = lst.ld_r16_expr(complete, value);
67 }
68 else {
69 let value = self.value_for_r8(*first).unwrap();
71 lst = lst.ld_r8_expr(*first, value);
72 }
73 }
74
75 lst.build()
76 }
77
78 pub fn register_for(&self, val: u8) -> Option<Register8> {
79 self.regs.iter().find(|(r, v)| **v == val).map(|(r, v)| *r)
80 }
81
82 pub fn value_for_r8(&self, r: Register8) -> Option<u8> {
83 self.regs.get(&r).cloned()
84 }
85
86 pub fn value_for_r16(&self, r: Register16) -> Option<u16> {
87 let low = self.value_for_r8(r.low().unwrap());
88 let high = self.value_for_r8(r.high().unwrap());
89 if let (Some(low), Some(high)) = (low, high) {
90 Some(low as u16 + (high as u16) * 256)
91 }
92 else {
93 None
94 }
95 }
96
97 pub fn set(&mut self, r: Register8, v: u8) -> &mut Self {
98 assert!(!self.available_regs.contains(&r));
99 self.regs.insert(r, v);
100 self
101 }
102
103 pub fn next_available_regs(&mut self) -> Option<Register8> {
104 self.available_regs.pop()
105 }
106
107 pub fn has_available_regs(&self) -> bool {
108 !self.available_regs.is_empty()
109 }
110}
111
112#[derive(Builder)]
113#[builder(on(String, into))]
115#[builder(on(SmolStr, into))]
116pub struct Compiler {
117 header_comment: Option<String>,
119 header_label: Option<SmolStr>,
121
122 #[builder(default)]
124 display_lst: Listing,
125
126 #[builder(default)]
127 restore_lst: Listing,
128
129 bc26: Bc26,
131
132 #[builder(default)]
133 regs: RegistersStore,
134
135 #[builder(default)]
136 action: RoutineAction,
137
138 #[builder(skip)]
139 nb_lines_to_pass: usize
140}
141
142impl Deref for Compiler {
143 type Target = Listing;
144
145 fn deref(&self) -> &Self::Target {
146 &self.display_lst
147 }
148}
149
150impl DerefMut for Compiler {
151 fn deref_mut(&mut self) -> &mut Self::Target {
152 &mut self.display_lst
153 }
154}
155
156impl Compiler {
157 pub fn build_stats(spr: &SpriteOutput, msk: &SpriteOutput) -> BTreeMap<u8, usize> {
158 let mut set: BTreeMap<u8, usize> = Default::default();
159 for b in spr
160 .data()
161 .iter()
162 .zip(msk.data().iter())
163 .filter_map(|(&b, &m)| {
164 if m != 0xFF && m != 0x00 {
165 Some([b, m])
166 }
167 else {
168 None
169 }
170 })
171 .flatten()
172 {
173 if let std::collections::btree_map::Entry::Vacant(e) = set.entry(b) {
174 e.insert(1);
175 }
176 else {
177 *set.get_mut(&b).unwrap() += 1;
178 }
179 }
180
181 set
182 }
183
184 pub fn compile(mut self, spr: &SpriteOutput, msk: &SpriteOutput) -> Listing {
185 assert_eq!(spr.bytes_width(), msk.bytes_width());
186 assert_eq!(spr.height(), msk.height());
187 assert_eq!(spr.encoding(), msk.encoding());
188
189 let stats = Self::build_stats(spr, msk);
190 let mut retained = stats
191 .into_iter()
192 .filter(|(k, v)| *v > 3)
193 .sorted_by_key(|(k, v)| *v)
194 .collect_vec();
195
196 self.regs = self.action.build_register_store();
198 while let Some((v, _)) = retained.pop()
199 && self.regs.has_available_regs()
200 {
201 let r = self.regs.next_available_regs().unwrap();
202 self.regs.set(r, v);
203 }
204
205 let width = spr.bytes_width();
206
207 self.emit_header();
208 let mut write_cursor = 0;
209 for (idx, line) in spr
210 .data()
211 .iter()
212 .cloned()
213 .zip(msk.data().iter().cloned())
214 .chunks(width)
215 .into_iter()
216 .enumerate()
217 {
218 write_cursor = self.emit_line(idx, line, write_cursor)
219 }
220
221 self.emit_footer();
222 self.display_lst
223 }
224
225 fn emit_line(
226 &mut self,
227 line_idx: usize,
228 line: impl Iterator<Item = (u8, u8)>,
229 mut write_cursor: u16
230 ) -> u16 {
231 let nb_moves = 0;
235 self.emit_line_header(line_idx);
236
237 let mut lst = ListingBuilder::default();
238
239 let line = line.collect_vec();
240 let nb_steps = line.len() as u16;
241 for (read_idx, (pixs, mask)) in line.into_iter().enumerate() {
242 let read_cursor = if line_idx.is_multiple_of(2) {
244 read_idx as u16
245 }
246 else {
247 nb_steps - read_idx as u16 - 1
248 };
249 let will_write_on_screen = mask != 0xFF;
250
251 if will_write_on_screen {
253 assert!(self.use_8bits_addresses_handling());
254
255 self.flushing_pending_next_line_computations();
256
257 let local_moves = (read_cursor as i32) - (write_cursor as i32);
259
260 let chosen: Listing = match local_moves {
262 0 => Listing::new(),
263 1 => inc_l().into(),
264 -1 => dec_l().into(),
265 local_moves => {
266 let choice1: Listing = ListingBuilder::default()
267 .repeat(
268 local_moves.abs(),
269 if local_moves > 0 { inc_l() } else { dec_l() }
270 )
271 .build();
272 let choice2: Listing = ListingBuilder::default()
273 .ld_a_expr(local_moves)
274 .add_l()
275 .ld_l_a()
276 .build();
277
278 ListingSelector::default()
279 .add(choice1)
280 .add(choice2)
281 .select()
282 }
283 };
284
285 lst = lst
287 .comment(format!("Move of {local_moves} bytes"))
288 .extend(chosen); write_cursor = read_cursor;
291
292 if self.action.save_background() {
293 match local_moves {
296 0 => {},
297 1 => {
298 self.restore_lst.add(inc_e());
300 },
301 -1 => {
302 self.restore_lst.add(dec_e());
303 },
304 _ => {
305 lst = lst
306 .comment("Save screen address")
307 .ex_hl_de()
308 .ld_mem_hl_e()
309 .inc_hl() .ld_mem_hl_d()
311 .inc_hl() .ex_hl_de();
313
314 self.restore_lst.inject_listing(
315 &ListingBuilder::default()
316 .comment("Retreive screen address")
317 .ld_e_mem_hl()
318 .inc_hl()
319 .ld_d_mem_hl()
320 .inc_hl()
321 .build()
322 );
323 }
324 }
325 }
326 }
327
328 match mask {
330 0x00 => {
331 if self.action.save_background() {
332 lst = lst
333 .comment("No masking, but need to save the background")
334 .ld_a_mem_hl()
335 .ld_mem_de_a()
336 .inc_de();
337
338 self.restore_lst.inject_listing(
339 &ListingBuilder::default()
340 .comment("Read and write byte")
341 .ld_a_mem_hl()
342 .inc_hl()
343 .ld_mem_de_a()
344 .build()
345 );
346 }
347 else {
348 lst = lst.comment("No masking here");
350 }
351
352 if let Some(reg) = self.regs.register_for(pixs) {
353 lst = lst.ld_mem_hl_r8(reg);
354 }
355 else {
356 lst = lst.ld_mem_hl_expr(pixs);
357 }
358 },
359
360 0xFF => {
361 lst = lst.comment("Nothing shown here");
363 },
364
365 mask => {
366 let comment = if pixs != 0 {
368 "Masked byte"
369 }
370 else {
371 "masked byte BUT no bit to set"
372 };
373
374 lst = lst.comment(comment).ld_a_mem_hl();
376
377 if let Some(reg) = self.regs.register_for(mask) {
378 lst = lst.and_r8(reg);
379 }
380 else {
381 lst = lst.and_expr(mask);
382 }
383
384 match pixs {
386 0 => {}, 1 => {
388 lst = lst.inc_a();
389 }, val => {
391 if let Some(reg) = self.regs.register_for(val) {
392 lst = lst.or_r8(reg);
393 }
394 else {
395 lst = lst.or_expr(pixs);
396 }
397 },
398 }
399
400 lst = lst.ld_mem_hl_a();
402 }
403 };
404
405 if nb_steps - 1 == read_cursor {
406 lst = lst.comment("End of line");
407 }
408 }
409
410 self.inject_listing(&lst.build());
411
412 self.emit_line_footer();
413 write_cursor
414 }
415
416 fn emit_line_header(&mut self, idx: usize) {
417 self.add_comment(format!("> Handle line {idx}"));
418 if idx == 0 {
419 self.add_comment(" HL already contains the destination address");
420 }
421 else {
422 self.request_next_line_computation();
423 }
424 }
425
426 fn emit_line_footer(&mut self) {}
427
428 pub fn use_8bits_addresses_handling(&self) -> bool {
443 self.bc26.use_8bits_addresses_handling().unwrap()
444 }
445
446 fn request_next_line_computation(&mut self) {
447 self.nb_lines_to_pass += 1;
448 }
449
450 fn flushing_pending_next_line_computations(&mut self) {
451 let nb_lines = self.nb_lines_to_pass;
463 for _ in 0..nb_lines {
464 self.emit_compute_next_line_address();
465 }
466 self.nb_lines_to_pass = 0;
467 }
468
469 fn emit_compute_next_line_address(&mut self) {
470 self.add_comment("Compute the address of the next line");
471 let execute = self.bc26.clone().execute();
472 self.inject_listing(&execute);
473 }
474
475 fn emit_header(&mut self) {
476 if let Some(comment) = self.header_comment.take() {
477 self.add_comment(comment);
478 }
479 if let Some(label) = self.header_label.take() {
480 self.add_label(label);
481 }
482 self.display_lst.inject_listing(&self.regs.listing());
483 }
484
485 fn emit_footer(&mut self) {
486 self.add(cpclib_asm::ret());
487
488 if let Some(bc26_routine) = self.bc26.routine_1line() {
489 let bc26_label = self.bc26.label();
490 let r#if = IfBuilder::default()
491 .condition(TestKind::ifndef(bc26_label), bc26_routine)
492 .build();
493 self.add(r#if);
494 }
495 }
496}
497
498#[derive(Clone, Copy)]
500pub enum Bc26 {
501 Compute16KbC000 { r1: u8 },
502 Compute16KbUniversal { r1: u8 }
503}
504
505impl Bc26 {
506 pub fn new_universal_16k(r1: u8) -> Self {
507 Self::Compute16KbUniversal { r1 }
508 }
509
510 pub fn new_c000_16k(r1: u8) -> Self {
511 Self::Compute16KbC000 { r1 }
512 }
513}
514
515impl Bc26 {
516 pub fn label(&self) -> String {
517 match self {
518 Bc26::Compute16KbC000 { r1 } => Self::c000_routine_label(*r1),
519 Bc26::Compute16KbUniversal { r1 } => Self::universal_routine_label(*r1)
520 }
521 }
522
523 pub fn routine_1line(&self) -> Option<Listing> {
524 match self {
525 Bc26::Compute16KbC000 { r1 } => Some(Self::c000_routine_1line(*r1)),
526 Bc26::Compute16KbUniversal { r1 } => Some(Self::universal_routine_1line(*r1))
527 }
528 }
529
530 pub fn routine_8lines(&self) -> Option<Listing> {
531 match self {
532 Bc26::Compute16KbC000 { r1 } => Some(Self::c000_routine_8lines(*r1)),
533 Bc26::Compute16KbUniversal { r1 } => Some(Self::universal_routine_8lines(*r1))
534 }
535 }
536
537 pub fn execute(&self) -> Listing {
538 match self {
539 Bc26::Compute16KbC000 { r1 } | Bc26::Compute16KbUniversal { r1 } => {
540 ListingBuilder::default().call(self.label()).build()
541 },
542 }
543 }
544
545 pub fn r1(&self) -> Option<u8> {
546 match self {
547 Self::Compute16KbC000 { r1 } | Self::Compute16KbUniversal { r1 } => Some(*r1)
548 }
549 }
550
551 pub fn use_8bits_addresses_handling(&self) -> Option<bool> {
552 self.r1().map(|r1| r1 == 32)
553 }
554
555 fn universal_routine_label(r1: u8) -> String {
556 format!("universal_bc26_r1_{r1}")
557 }
558
559 fn c000_routine_label(r1: u8) -> String {
560 format!("c000_bc26_r1_{r1}")
561 }
562
563 fn universal_routine_1line(r1: u8) -> Listing {
564 let label = Self::universal_routine_label(r1);
565 Listing::from_str(&format!(
566 "
567{}
568 ld a,h : add 8 : ld h,a : and #38 : ret nz
569 ld a,{} : add l : ld l,a : ld a,#C0 : adc h : ld h,a : res 3,h
570 ret
571 ",
572 label,
573 r1 * 2
574 ))
575 .unwrap()
576 }
577
578 fn universal_routine_8lines(r1: u8) -> Listing {
579 if r1 == 32 {
580 Listing::from_str(&format!("ld a,{} : add l : ld l,a", r1 * 2)).unwrap()
581 }
582 else {
583 unimplemented!()
584 }
585 }
586
587 fn c000_routine_1line(r1: u8) -> Listing {
588 let label = Self::c000_routine_label(r1);
589 Listing::from_str(&format!(
590 "
591 {}
592 ld a,h : add 8 : ld h,a : ret nc
593 ld a,{} : add l : ld l,a : ld a,#C0 : adc h : ld h,a : res 3,h
594 ret
595 ",
596 label,
597 r1 * 2
598 ))
599 .unwrap()
600 }
601
602 fn c000_routine_8lines(r1: u8) -> Listing {
603 Self::universal_routine_8lines(r1)
604 }
605}
606
607pub fn standard_sprite_compiler(
614 label: &str,
615 spr: &SpriteOutput,
616 msk: &SpriteOutput,
617 r1: u8
618) -> Listing {
619 let spr = spr.with_encoding(SpriteEncoding::LeftToRightToLeft);
620 let msk = msk.with_encoding(SpriteEncoding::LeftToRightToLeft);
621
622 let comp = Compiler::builder()
623 .header_comment("Linear sprite display routine. HL contains screen address")
624 .header_label(label)
625 .bc26(Bc26::new_universal_16k(r1))
626 .build();
627 comp.compile(&spr, &msk)
628}
629
630pub fn standard_sprite_with_background_backup_and_restore_compiler(
631 label: &str,
632 spr: &SpriteOutput,
633 msk: &SpriteOutput,
634 r1: u8
635) -> Listing {
636 let spr = spr.with_encoding(SpriteEncoding::LeftToRightToLeft);
637 let msk = msk.with_encoding(SpriteEncoding::LeftToRightToLeft);
638
639 todo!(
640 "XXX Nothing is finished here. Cannot be used without fixes. Restore code is not stored BTW"
641 );
642
643 let comp = Compiler::builder()
644 .header_comment("Linear sprite display routine. HL contains screen address and DE contains save_buffer_address")
645 .header_label(label)
646 .bc26(Bc26::new_universal_16k(r1))
647 .action(RoutineAction::SaveBackgroundAndDrawPixelMaskedSprite)
648 .build();
649 comp.compile(&spr, &msk)
650}