My best guess is that the real hardware doesn't - and so Homer is inverted. Let's test this theory.
The response is (now) processed in lfb_init. So let's intercept that and send the RGB/BGR bit down the UART.
let pixorder = unsafe { read_volatile(volbuf.add(24)) };And we see:
write("pixel order is ");
write_8_chars(hex32(pixorder));
write("\r\n");
pixel order is 00000000That's zero, which our comments tell us is BGR, not RGB. So we need to store that in our struct and use it when rendering. This adds a certain amount of complexity, but here are the highlights:
We declare an enum to make it clearer what the two cases are. For some reason, this needs an annotation declaring that it derives PartialEq, although presumably we could not do that and explicitly define it. I assume this just means that we can test that two values with BGR are the same, as are two RGBs. We then add this into the struct of information about the Framebuffer:
#[derive(PartialEq)]And then we need to set this to the correct enum value based on the 0 or 1 return value from the mailbox call:
enum PixelOrder {
BGR,
RGB
}
struct FrameBufferInfo {
width : u32,
height : u32,
pitch: u32,
base_addr: u32,
pixorder: PixelOrder
}
fb.pixorder = if pixorder == 1 { PixelOrder::RGB } else { PixelOrder::BGR };Note that, if (like me) you did not already know this, Rust eschews the ternary operator in favour of having functional-style if statements that can return values (as long as you don't put semicolons after the final expression in a block).
And, of course, when we come to draw Homer we need to take this into account:
if (fb.pixorder == PixelOrder::RGB) {(It's a very small change, but the offsets in the indices on the left hand side in the second block go 2,1,0,3).
unsafe { *((ptr + x*4 + 0) as *mut u8) = homer[homer_index + 0]; }
unsafe { *((ptr + x*4 + 1) as *mut u8) = homer[homer_index + 1]; }
unsafe { *((ptr + x*4 + 2) as *mut u8) = homer[homer_index + 2]; }
unsafe { *((ptr + x*4 + 3) as *mut u8) = homer[homer_index + 3]; }
} else {
unsafe { *((ptr + x*4 + 2) as *mut u8) = homer[homer_index + 0]; }
unsafe { *((ptr + x*4 + 1) as *mut u8) = homer[homer_index + 1]; }
unsafe { *((ptr + x*4 + 0) as *mut u8) = homer[homer_index + 2]; }
unsafe { *((ptr + x*4 + 3) as *mut u8) = homer[homer_index + 3]; }
}
OK, let's try that. Yup, Homer is now the right colour. Let's check this in before it stops working! It's tagged as RUST_BARE_METAL_HOMER_PI.
And now that I have everything fairly stable, I'm going to finish the task I'd set myself and clean up all the dead code, removing unnecessary blocks of tracing and associated functions. The final version is tagged RUST_BARE_METAL_CLEANED_HOMER.
Conclusion
It's been painful (for me, at least) but we are now at the point where we can do a number of things on the bare metal of a Pi working almost entirely in Rust. But it's one big mess.I think we've gone as far as Homer can really take us, but I do want to read about - and experiment with - some other concepts in this space, and improving Homer seems like the most sensible way forward.
As a list as much to myself as anything, I want to:
- figure out how to get most of the standard library in, without pulling in dependencies on Linux;
- after that, figure out how to get memory management to work so I can allocate blocks of memory;
- using that, be able to allocate memory blocks of arbitrary alignment;
- figure out how to get unit tests to run in this environment
- look into making the code more modular.
When I have done all that, I think I want to move on and try and build a proper console on the monitor which can display the messages that would otherwise go to the UART (although it should be configurable to do both).
No comments:
Post a Comment