Skip to content

Commit fe6a548

Browse files
authored
Merge pull request #120 from Neotron-Compute/scanline-doubling
Add double-scan modes.
2 parents 59b5167 + ac8490e commit fe6a548

File tree

1 file changed

+111
-51
lines changed

1 file changed

+111
-51
lines changed

src/vga/mod.rs

Lines changed: 111 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ impl RenderEngine {
191191
}
192192
// Tell the ISR to now generate our newly chosen timing
193193
CURRENT_TIMING_MODE.store(self.current_video_mode.timing() as usize, Ordering::Relaxed);
194+
DOUBLE_SCAN_MODE.store(self.current_video_mode.is_vert_2x(), Ordering::Relaxed);
194195
// set up our text console to be the right size
195196
self.num_text_cols = self.current_video_mode.text_width().unwrap_or(0) as usize;
196197
self.num_text_rows = self.current_video_mode.text_height().unwrap_or(0) as usize;
@@ -206,8 +207,10 @@ impl RenderEngine {
206207
// It's safe to write to this buffer because it's the the other one that
207208
// is currently being DMA'd out to the Pixel SM.
208209
let scan_line_buffer = if (current_line_num & 1) == 0 {
210+
defmt::trace!("drawing {=u16} into even", current_line_num);
209211
&PIXEL_DATA_BUFFER_EVEN
210212
} else {
213+
defmt::trace!("drawing {=u16} into odd", current_line_num);
211214
&PIXEL_DATA_BUFFER_ODD
212215
};
213216

@@ -1747,14 +1750,20 @@ static TIMING_BUFFER: [TimingBuffer; 2] =
17471750
/// Ensure this matches the default chosen in [`RenderEngine::new()`]
17481751
static CURRENT_TIMING_MODE: AtomicUsize = AtomicUsize::new(0);
17491752

1750-
/// Tracks which scan-line will be shown next.
1753+
/// Tracks which scan-line will be shown next, therefore which one you should be drawing right now.
17511754
///
17521755
/// This is for timing purposes, therefore it goes from
17531756
/// `0..TIMING_BUFFER.back_porch_ends_at`.
17541757
///
17551758
/// Set by the PIO IRQ.
17561759
static NEXT_SCAN_LINE: AtomicU16 = AtomicU16::new(0);
17571760

1761+
/// Are we in double-scan mode?
1762+
///
1763+
/// If we are, each scan-line buffer is played out twice, and you should divide
1764+
/// `NEXT_SCAN_LINE` by 2 before rendering a line.
1765+
static DOUBLE_SCAN_MODE: AtomicBool = AtomicBool::new(false);
1766+
17581767
/// Indicates that we should draw the current scan-line given by [`NEXT_SCAN_LINE`].
17591768
///
17601769
/// Set by the PIO IRQ.
@@ -1943,9 +1952,7 @@ pub fn init(
19431952

19441953
pio.irq1().enable_sm_interrupt(1);
19451954

1946-
// Read from the timing buffer and write to the timing FIFO. We get an
1947-
// IRQ when the transfer is complete (i.e. when line has been fully
1948-
// loaded).
1955+
// Read from the timing buffer and write to the timing FIFO.
19491956
dma.ch(TIMING_DMA_CHAN).ch_ctrl_trig().write(|w| {
19501957
w.data_size().size_word();
19511958
w.incr_read().set_bit();
@@ -2061,42 +2068,40 @@ pub fn mode_needs_vram(mode: neotron_common_bios::video::Mode) -> bool {
20612068

20622069
/// Check the given video mode is allowable
20632070
pub fn test_video_mode(mode: neotron_common_bios::video::Mode) -> bool {
2064-
matches!(
2065-
(
2066-
mode.timing(),
2067-
mode.format(),
2068-
mode.is_horiz_2x(),
2069-
mode.is_vert_2x(),
2070-
),
2071-
(
2072-
neotron_common_bios::video::Timing::T640x480
2073-
| neotron_common_bios::video::Timing::T640x400,
2074-
neotron_common_bios::video::Format::Text8x16
2075-
| neotron_common_bios::video::Format::Text8x8
2076-
| neotron_common_bios::video::Format::Chunky1
2077-
| neotron_common_bios::video::Format::Chunky2
2078-
| neotron_common_bios::video::Format::Chunky4,
2079-
false,
2080-
false,
2081-
) | (
2082-
neotron_common_bios::video::Timing::T640x480
2083-
| neotron_common_bios::video::Timing::T640x400,
2084-
neotron_common_bios::video::Format::Chunky1
2085-
| neotron_common_bios::video::Format::Chunky2
2086-
| neotron_common_bios::video::Format::Chunky4,
2087-
true,
2088-
false,
2089-
) | (
2090-
neotron_common_bios::video::Timing::T640x480
2091-
| neotron_common_bios::video::Timing::T640x400,
2092-
neotron_common_bios::video::Format::Chunky8,
2093-
true,
2094-
false
2071+
if !mode.is_horiz_2x() {
2072+
// in the 640-px modes we can only do up to 4-bpp
2073+
matches!(
2074+
(mode.timing(), mode.format()),
2075+
(
2076+
neotron_common_bios::video::Timing::T640x480
2077+
| neotron_common_bios::video::Timing::T640x400,
2078+
neotron_common_bios::video::Format::Text8x16
2079+
| neotron_common_bios::video::Format::Text8x8
2080+
| neotron_common_bios::video::Format::Chunky1
2081+
| neotron_common_bios::video::Format::Chunky2
2082+
| neotron_common_bios::video::Format::Chunky4,
2083+
)
2084+
)
2085+
} else {
2086+
// in the 320-px modes we can also do 8-bpp
2087+
matches!(
2088+
(mode.timing(), mode.format()),
2089+
(
2090+
neotron_common_bios::video::Timing::T640x480
2091+
| neotron_common_bios::video::Timing::T640x400,
2092+
neotron_common_bios::video::Format::Chunky1
2093+
| neotron_common_bios::video::Format::Chunky2
2094+
| neotron_common_bios::video::Format::Chunky4
2095+
| neotron_common_bios::video::Format::Chunky8,
2096+
)
20952097
)
2096-
)
2098+
}
20972099
}
20982100

20992101
/// Get the current scan line.
2102+
///
2103+
/// Note that these are timing scan lines, not visible scan lines (so we count
2104+
/// to 480 even in a 240 line mode).
21002105
pub fn get_scan_line() -> u16 {
21012106
NEXT_SCAN_LINE.load(Ordering::Relaxed)
21022107
}
@@ -2133,7 +2138,7 @@ pub fn get_palette(index: u8) -> RGBColour {
21332138
/// Only run this function on Core 1.
21342139
#[link_section = ".data"]
21352140
unsafe extern "C" fn core1_main() -> u32 {
2136-
let mut video = RenderEngine::new();
2141+
let mut render_engine = RenderEngine::new();
21372142

21382143
// The LED pin was called `_pico_led` over in the `Hardware::build`
21392144
// function that ran on Core 1. Rather than try and move the pin over to
@@ -2160,10 +2165,15 @@ unsafe extern "C" fn core1_main() -> u32 {
21602165
DRAW_THIS_LINE.store(false, Ordering::Relaxed);
21612166

21622167
// The one we draw *now* is the one that is *shown* next
2163-
let this_line = NEXT_SCAN_LINE.load(Ordering::Relaxed);
2168+
let mut this_line = NEXT_SCAN_LINE.load(Ordering::Relaxed);
21642169

21652170
if this_line == 0 {
2166-
video.frame_start();
2171+
render_engine.frame_start();
2172+
}
2173+
2174+
if render_engine.current_video_mode.is_vert_2x() {
2175+
// in double scan mode we only draw ever other line
2176+
this_line >>= 1;
21672177
}
21682178

21692179
unsafe {
@@ -2173,7 +2183,7 @@ unsafe extern "C" fn core1_main() -> u32 {
21732183

21742184
// This function currently consumes about 70% CPU (or rather, 90% CPU
21752185
// on each of visible lines, and 0% CPU on the other lines)
2176-
video.draw_next_line(this_line);
2186+
render_engine.draw_next_line(this_line);
21772187

21782188
unsafe {
21792189
// Turn off LED
@@ -2182,9 +2192,16 @@ unsafe extern "C" fn core1_main() -> u32 {
21822192
}
21832193
}
21842194

2185-
/// This function is called whenever the Timing PIO starts a scan-line.
2195+
/// This function is called whenever the Timing State Machine starts a
2196+
/// scan-line.
21862197
///
2187-
/// Timing wise, we should be at the start of the back-porch.
2198+
/// Timing wise, we should be at the start of the front-porch (i.e. just after
2199+
/// the visible portion finishes). This is because it is the 'back porch' part
2200+
/// of the timing data sent to the Timing State Machine that contains a "Raise
2201+
/// IRQ 1" instruction, and that IRQ triggers this function.
2202+
///
2203+
/// The visible section contains a "Raise IRQ 0" instruction, but that only
2204+
/// triggers the Pixel State Machine and not a CPU interrupt.
21882205
///
21892206
/// # Safety
21902207
///
@@ -2198,10 +2215,15 @@ unsafe fn PIO0_IRQ_1() {
21982215
// Clear the interrupt
21992216
pio.irq().write_with_zero(|w| w.irq().bits(1 << 1));
22002217

2201-
// Current mode
2202-
let current_mode = CURRENT_TIMING_MODE.load(Ordering::Relaxed);
2203-
let timing_data = &TIMING_BUFFER[current_mode];
2204-
// This is now the line we are currently playing
2218+
// Current timing mode
2219+
let current_mode_nr = CURRENT_TIMING_MODE.load(Ordering::Relaxed);
2220+
let timing_data = &TIMING_BUFFER[current_mode_nr];
2221+
// Are we double scanning?
2222+
let double_scan = DOUBLE_SCAN_MODE.load(Ordering::Relaxed);
2223+
2224+
// This is now the line we are currently in the middle of playing;
2225+
// timing-wise anyway - the pixels will be along in moment once we've told
2226+
// the DMA which pixels to play.
22052227
let current_timing_line = NEXT_SCAN_LINE.load(Ordering::Relaxed);
22062228
// This is the line we should cue up to play next
22072229
let next_timing_line = if current_timing_line == timing_data.back_porch_ends_at {
@@ -2212,24 +2234,56 @@ unsafe fn PIO0_IRQ_1() {
22122234
current_timing_line + 1
22132235
};
22142236

2237+
let (mask, draw_now) = if double_scan {
2238+
// Only tell the main loop to re-draw on odd lines (i.e. 1, 3, 5, etc)
2239+
// because on even lines (0, 2, 4, ...) we still need to draw the line
2240+
// again.
2241+
2242+
// The mask is 2, so we have:
2243+
//
2244+
// 0 = (play even, draw = true)
2245+
// 1 = (play even, draw = false)
2246+
// 2 = (play odd, draw = true)
2247+
// 3 = (play odd, draw = false)
2248+
// 4 = (play even, draw = true)
2249+
// etc
2250+
(2, (next_timing_line & 1) == 0)
2251+
} else {
2252+
// tell the main loop to draw, always
2253+
//
2254+
// The mask is 1, so we have:
2255+
//
2256+
// 0 = (play even, draw = true)
2257+
// 1 = (play odd, draw = true)
2258+
// 2 = (play even, draw = true)
2259+
// 3 = (play odd, draw = true)
2260+
// 4 = (play even, draw = true)
2261+
// etc
2262+
(1, true)
2263+
};
2264+
22152265
// Are we in the visible portion *right* now? If so, copy some pixels into
22162266
// the Pixel SM FIFO using DMA. Hopefully the main thread has them ready for
22172267
// us (though we're playing them, ready or not).
22182268
if current_timing_line <= timing_data.visible_lines_ends_at {
2219-
if (current_timing_line & 1) == 1 {
2269+
if (current_timing_line & mask) != 0 {
22202270
// Load the odd line into the Pixel SM FIFO for immediate playback
22212271
dma.ch(PIXEL_DMA_CHAN)
22222272
.ch_al3_read_addr_trig()
2223-
.write(|w| w.bits(PIXEL_DATA_BUFFER_ODD.as_ptr()))
2273+
.write(|w| w.bits(PIXEL_DATA_BUFFER_ODD.as_ptr()));
2274+
defmt::trace!("line {=u16} playing odd buffer", current_timing_line);
22242275
} else {
22252276
// Load the even line into the Pixel SM FIFO for immediate playback
22262277
dma.ch(PIXEL_DMA_CHAN)
22272278
.ch_al3_read_addr_trig()
2228-
.write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr()))
2279+
.write(|w| w.bits(PIXEL_DATA_BUFFER_EVEN.as_ptr()));
2280+
defmt::trace!("line {=u16} playing even buffer", current_timing_line);
22292281
}
22302282
// The data will start pouring into the FIFO, but the output is corked until
22312283
// the timing SM generates the second interrupt, just before the visible
22322284
// portion.
2285+
} else {
2286+
defmt::trace!("line {=u16} is blank", current_timing_line);
22332287
}
22342288

22352289
// Set this before we set the `DRAW_THIS_LINE` flag.
@@ -2238,8 +2292,14 @@ unsafe fn PIO0_IRQ_1() {
22382292
// Work out what sort of sync pulses we need on the *next* scan-line, and
22392293
// also tell the main thread what to draw ready for the *next* scan-line.
22402294
let buffer = if next_timing_line <= timing_data.visible_lines_ends_at {
2241-
// A visible line is *up next* so start drawing it *right now*.
2242-
DRAW_THIS_LINE.store(true, Ordering::Release);
2295+
// A visible line is *up next* so maybe start drawing it *right now*.
2296+
defmt::trace!(
2297+
"DRAW {=u16} draw_now={=bool}, double_scan={=bool}",
2298+
next_timing_line,
2299+
draw_now,
2300+
double_scan
2301+
);
2302+
DRAW_THIS_LINE.store(draw_now, Ordering::Release);
22432303
&raw const timing_data.visible_line
22442304
} else if next_timing_line <= timing_data.front_porch_end_at {
22452305
// VGA front porch before VGA sync pulse

0 commit comments

Comments
 (0)