@@ -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()`]
17481751static 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.
17561759static 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
20632070pub 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).
21002105pub 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" ]
21352140unsafe 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