diff --git a/.gitignore b/.gitignore index 561c2378..bd69889e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,8 @@ rvc *.o elfy/target elfy/Cargo.lock -rust_payload/target -rust_payload/Cargo.lock +rust_*/target +rust_*/Cargo.lock fw_payload.* *.dtb *.bmp diff --git a/Makefile b/Makefile index dafb17fb..82e38a9a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ TARGET ?= rvc -SRC_DIRS ?= ./src +SRC_DIRS ?= ./src ./tinypngout CC=clang @@ -12,10 +12,10 @@ INC_FLAGS := $(addprefix -I,$(INC_DIRS)) Q=$(CURDIR)/buildroot-2021.05/output/host/bin/riscv32-buildroot-linux-gnu- -CFLAGS=-g +CFLAGS ?= -g -O2 $(TARGET): $(OBJS) - $(CC) $(LDFLAGS) -I./elfy/elfy.h $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) -L./elfy/target/release/ -Wl,--no-as-needed -ldl -lpthread -lelfy + $(CC) $(LDFLAGS) -I./elfy/elfy.h -I./tinypngout/TinyPngOut.h $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) -L./elfy/target/release/ -Wl,--no-as-needed -ldl -lpthread -lelfy -include $(DEPS) @@ -26,6 +26,7 @@ dts.dtb: dts.dts OPENSBI_BUILD=opensbi/build/platform/generic/firmware PAYLOAD=rust_payload/target/riscv32ima-unknown-none-elf/release/rust_payload.bin LINUX_PAYLOAD=linux/arch/riscv/boot/Image +RAYTRACE_PAYLOAD=rust_raytrace/target/riscv32ima-unknown-none-elf/release/rust_raytrace.bin BUILDROOT_MARKER=buildroot-2021.05/build.marker $(BUILDROOT_MARKER): @@ -40,6 +41,15 @@ $(PAYLOAD): $(shell find rust_payload/src -type f) $(Q)objcopy -O binary \ rust_payload/target/riscv32ima-unknown-none-elf/release/rust_payload $(PAYLOAD) +$(RAYTRACE_PAYLOAD): $(shell find rust_raytrace/src -type f) + cd rust_raytrace; \ + cargo +riscv32ima rustc -Zbuild-std --release --target "riscv32ima-unknown-none-elf" -- -Clink-arg=-Tlinker.ld -Clinker=$(Q)ld + $(Q)objcopy -O binary \ + rust_raytrace/target/riscv32ima-unknown-none-elf/release/rust_raytrace $(RAYTRACE_PAYLOAD) + +rust_raytrace.bin: $(RAYTRACE_PAYLOAD) + cp $(RAYTRACE_PAYLOAD) $@ + rust_payload.bin: rust_payload.elf cp $(OPENSBI_BUILD)/fw_payload.bin ./rust_payload.bin rust_payload.elf: $(PAYLOAD) $(BUILDROOT_MARKER) diff --git a/rust_raytrace/Cargo.toml b/rust_raytrace/Cargo.toml new file mode 100644 index 00000000..38861f6d --- /dev/null +++ b/rust_raytrace/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust_raytrace" +version = "0.1.0" +edition = "2018" + +[dependencies] +micromath = { version = "2.0", features = ["vector"] } +linked_list_allocator = "0.9" diff --git a/rust_raytrace/linker.ld b/rust_raytrace/linker.ld new file mode 100644 index 00000000..15a9060e --- /dev/null +++ b/rust_raytrace/linker.ld @@ -0,0 +1,30 @@ +OUTPUT_ARCH( "riscv" ) + +ENTRY( _start ) + +MEMORY +{ + ram (rwx) : ORIGIN = 0x80000000, LENGTH = 0x80000 +} + +SECTIONS +{ + .kernel : { + *(.start_here) + *(.text .text.*) + *(.rodata .rodata.*) + *(.data .data.*) + } > ram + + .stack (NOLOAD) : { + . = . + 0x20000; + PROVIDE(_end_stack = .); + } > ram + + .heap (NOLOAD) : { + PROVIDE(_begin_heap = .); + . = . + 0x40000; + PROVIDE(_end_heap = .); + } > ram + +} diff --git a/rust_raytrace/rust-raytracer-weekend b/rust_raytrace/rust-raytracer-weekend new file mode 160000 index 00000000..91e65843 --- /dev/null +++ b/rust_raytrace/rust-raytracer-weekend @@ -0,0 +1 @@ +Subproject commit 91e65843f1f9621a16f7cc5400508d71944d76bd diff --git a/rust_raytrace/src/low_level/mod.rs b/rust_raytrace/src/low_level/mod.rs new file mode 100644 index 00000000..8488ce60 --- /dev/null +++ b/rust_raytrace/src/low_level/mod.rs @@ -0,0 +1,64 @@ +use core::panic::PanicInfo; +use linked_list_allocator::LockedHeap; + +mod uart; +pub use uart::*; + +#[naked] +#[no_mangle] +#[link_section = ".start_here"] +#[allow(dead_code)] +/// The actual entry point of the binary, sets up stack and jumps to 'main' +extern "C" fn _start() -> ! { + unsafe { + asm!( + " + la sp, {end_stack} + j main + ", + end_stack = sym _end_stack, + options(noreturn) + ); + } +} + +#[alloc_error_handler] +fn alloc_error(_layout: core::alloc::Layout) -> ! { + println("ALLOC ERROR!"); + loop {} +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + println("PANIC!"); + loop {} +} + +// defined by linker script +extern "C" { + static _end_stack: usize; + static _begin_heap: usize; + static _end_heap: usize; +} + +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +pub unsafe fn init_heap() { + let stack_end = &_end_stack as *const usize as usize; + let heap_start = &_begin_heap as *const usize as usize; + let heap_end = &_end_heap as *const usize as usize; + let heap_size = heap_end - heap_start; + + print("> stack_end: "); + print_usize(stack_end); + print("\n> heap_start: "); + print_usize(heap_start); + print("\n> heap_end: "); + print_usize(heap_end); + print("\n> heap_size: "); + print_usize(heap_size); + print("\n"); + + ALLOCATOR.lock().init(heap_start, heap_size); +} diff --git a/rust_raytrace/src/low_level/uart.rs b/rust_raytrace/src/low_level/uart.rs new file mode 100644 index 00000000..f37211df --- /dev/null +++ b/rust_raytrace/src/low_level/uart.rs @@ -0,0 +1,51 @@ +pub fn print_char(ch: u8) { + unsafe { + let thr = 0x10000000 as *mut u8; + let lsr = 0x10000005 as *mut u8; + while thr.read_volatile() != 0 || (lsr.read_volatile() & 0x20) == 0 {} + thr.write_volatile(ch); + } +} + +pub fn println(s: &str) { + print(s); + print_char('\n' as u8); +} + +pub fn print(s: &str) { + s.chars().for_each(|c| print_char(c as u8)); +} + +#[inline(always)] +fn print_hex_part(u: u8) { + match u { + 0 => print("0"), + 1 => print("1"), + 2 => print("2"), + 3 => print("3"), + 4 => print("4"), + 5 => print("5"), + 6 => print("6"), + 7 => print("7"), + 8 => print("8"), + 9 => print("9"), + 10 => print("a"), + 11 => print("b"), + 12 => print("c"), + 13 => print("d"), + 14 => print("e"), + 15 => print("f"), + _ => unreachable!(), + } +} + +pub fn print_usize(u: usize) { + print("0x"); + for i in 0..=3 { + let part = ((u >> ((3 - i) * 8)) & 0xff) as u8; + let l = part & 0xF; + let h = (part >> 4) & 0xF; + print_hex_part(h); + print_hex_part(l); + } +} diff --git a/rust_raytrace/src/main.rs b/rust_raytrace/src/main.rs new file mode 100644 index 00000000..b0562cd9 --- /dev/null +++ b/rust_raytrace/src/main.rs @@ -0,0 +1,240 @@ +#![no_std] +#![no_main] +#![feature(asm)] +#![feature(naked_functions)] +#![feature(alloc_error_handler)] + +// #[macro_use] +extern crate alloc; + +mod low_level; +use low_level::*; + +mod raytrace; +use raytrace::*; + +mod math; +use math::*; + +const RAM_BASE: u32 = 0x80000000; +const FB_START: u32 = 0x80000; +const FB_WIDTH: u32 = 2048; +const FB_HEIGHT: u32 = 2048 - 16 /* progmem */ - 128 /* CPU state */; +// const AA_BORDER: u32 = 10; + +static mut SUPERSCALE: u32 = 96; // start at 48 +pub fn superscale() -> u32 { + unsafe { SUPERSCALE } +} + +#[no_mangle] +pub fn main() -> ! { + println("Initializing heap..."); + unsafe { + init_heap(); + } + + println("Setting up scene..."); + + let mut scene = Scene::new(); + + scene.add_obj(Plane { + id: 0, + position: Vector3 { + x: 0.0, + y: 3.0, + z: 0.0, + }, + normal: Vector3 { + x: 0.0, + y: 1.0, + z: 0.0, + }, + color: Color { + r: 180, + g: 180, + b: 200, + }, + }); + + scene.add_obj(Sphere { + id: 1, + center: Vector3 { + x: 3.5, + y: 0.0, + z: 9.0, + }, + radius: 2.5, + color: Color { + r: 166, + g: 105, + b: 238, + }, + }); + scene.add_obj(Sphere { + id: 2, + center: Vector3 { + x: 5.5, + y: 2.0, + z: 10.0, + }, + radius: 3.0, + color: Color { + r: 250, + g: 28, + b: 137, + }, + }); + scene.add_obj(Sphere { + id: 3, + center: Vector3 { + x: -4.8, + y: 3.2, + z: 8.6, + }, + radius: 3.8, + color: Color { + r: 10, + g: 240, + b: 70, + }, + }); + // mirror sphere + scene.add_obj(Sphere { + id: 4, + center: Vector3 { + x: 0.0, + y: -6.0, + z: 16.0, + }, + radius: 4.0, + color: Color { + r: 255, + g: 255, + b: 255, + }, + }); + + scene.add_light(Light { + position: Vector3 { + x: -3.0, + y: -3.8, + z: -5.5, + }, + intensity: 0.1, + }); + scene.add_light(Light { + position: Vector3 { + x: 2.9, + y: -9.8, + z: 12.0, + }, + intensity: 0.07, + }); + + while superscale() > 1 { + unsafe { SUPERSCALE /= 2 }; + print("Tracing @"); + print_usize(superscale() as usize); + print(" "); + + for y in (0..(FB_HEIGHT / superscale())).rev() { + for x in 0..(FB_WIDTH / superscale()) { + // Testpattern: + // let col = Vector3::new( + // if (x % 2) == 0 { 255 } else { 0 }, + // ((y * 255) / (FB_HEIGHT / superscale())) as i32, + // if (y % 2) == 0 { 255 } else { 0 }, + // ); + let col = scene.raytrace( + x as i32 - (FB_WIDTH / superscale() / 2) as i32, + y as i32 - (FB_HEIGHT / superscale() / 2) as i32, + ); + write_px(x, y, col); + } + + print("."); + } + + // print(" Done!\nAnti-Aliasing"); + + // for y in 0..(FB_HEIGHT / superscale()) { + // for x in 0..(FB_WIDTH / superscale()) { + // // tap neighboring pixels and mix into borders + // let cur = read_px_raw(x * superscale() + superscale() / 2, y * superscale() + superscale() / 2); + // let top = read_px_raw(x * superscale() + superscale() / 2, y * superscale() - superscale() / 2); + // let right = read_px_raw((x + 1) * superscale() + superscale() / 2, y * superscale() + superscale() / 2); + // let bottom = read_px_raw(x * superscale() + superscale() / 2, (y + 1) * superscale() + superscale() / 2); + // let left = read_px_raw(x * superscale() - superscale() / 2, y * superscale() + superscale() / 2); + // for ys in 0..superscale() { + // for xs in 0..superscale() { + // let mut cur = cur.clone(); + // let mut write = false; + // if xs < AA_BORDER { + // cur.mix_in(&left); + // write = true; + // } + // if xs > (superscale() - AA_BORDER) { + // cur.mix_in(&right); + // write = true; + // } + // if ys < AA_BORDER { + // cur.mix_in(&top); + // write = true; + // } + // if ys > (superscale() - AA_BORDER) { + // cur.mix_in(&bottom); + // write = true; + // } + + // if write { + // write_px_raw(x * superscale() + xs, y * superscale() + ys, cur.to_u128()); + // } + // } + // } + // } + + // print("."); + // } + + println(" Done!"); + } + + print("Program finished."); + + loop {} +} + +fn write_px(x: u32, y: u32, col: Color) { + let col = col.to_u128(); + let x = x * superscale(); + let y = y * superscale(); + + for ys in 0..superscale() { + for xs in 0..superscale() { + write_px_raw(x + xs, y + ys, col); + } + } +} + +fn write_px_raw(x: u32, y: u32, col: u128) { + unsafe { + let ptr = (FB_START | RAM_BASE) as *mut u128; + let ptr = ptr.add((x + y * FB_WIDTH) as usize); + ptr.write(col); + } +} + +// fn read_px_raw(x: u32, y: u32) -> Color { +// let col: u128; +// unsafe { +// let ptr = (FB_START | RAM_BASE) as *mut u128; +// let ptr = ptr.add((x + y * FB_WIDTH) as usize); +// col = ptr.read(); +// } +// Color { +// r: (((col >> 0) & 0xffffffff) / 0xffffff) as u32, +// g: (((col >> 32) & 0xffffffff) / 0xffffff) as u32, +// b: (((col >> 64) & 0xffffffff) / 0xffffff) as u32, +// } +// } diff --git a/rust_raytrace/src/math.rs b/rust_raytrace/src/math.rs new file mode 100644 index 00000000..58daf9d4 --- /dev/null +++ b/rust_raytrace/src/math.rs @@ -0,0 +1,114 @@ +use micromath::F32Ext; + +#[derive(Clone)] +pub struct Color { + pub r: u32, + pub g: u32, + pub b: u32, +} + +impl Color { + // #[inline(always)] + // pub fn mix_in(&mut self, other: &Self, mix: f32) { + // self.r = lerp(self.r, other.r, mix); + // self.g = lerp(self.g, other.g, mix); + // self.b = lerp(self.b, other.b, mix); + // } + + #[inline(always)] + pub fn to_u128(&self) -> u128 { + (self.r as u128 * 0xffffff as u128) + | ((self.g as u128 * 0xffffff as u128) << 32) + | ((self.b as u128 * 0xffffff as u128) << 64) + | ((0xffffffff as u128) << 96) + } +} + +#[derive(Clone)] +pub struct Vector3 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +impl Vector3 { + pub const ZERO: Vector3 = Vector3 { + x: 0.0, + y: 0.0, + z: 0.0, + }; + pub const ONE: Vector3 = Vector3 { + x: 1.0, + y: 1.0, + z: 1.0, + }; + + pub fn new(x: f32, y: f32, z: f32) -> Self { + Self { x, y, z } + } + + pub fn dot(&self, rhs: &Self) -> f32 { + self.x * rhs.x + self.y * rhs.y + self.z * rhs.z + } + + pub fn len(&self) -> f32 { + (self.x * self.x + self.y * self.y + self.z * self.z) + .abs() + .sqrt() + } + + pub fn normalized(&self) -> Vector3 { + let len = self.len(); + Vector3 { + x: self.x / len, + y: self.y / len, + z: self.z / len, + } + } +} + +impl core::ops::Add for &Vector3 { + type Output = Vector3; + fn add(self, rhs: Self) -> Self::Output { + Vector3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) + } +} + +impl core::ops::Sub for &Vector3 { + type Output = Vector3; + fn sub(self, rhs: Self) -> Self::Output { + Vector3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + } +} + +impl core::ops::Mul for &Vector3 { + type Output = Vector3; + fn mul(self, rhs: f32) -> Self::Output { + Vector3::new(self.x * rhs, self.y * rhs, self.z * rhs) + } +} + +impl core::ops::Mul for Vector3 { + type Output = Vector3; + fn mul(self, rhs: f32) -> Self::Output { + Vector3::new(self.x * rhs, self.y * rhs, self.z * rhs) + } +} + +impl core::ops::Div for &Vector3 { + type Output = Vector3; + fn div(self, rhs: f32) -> Self::Output { + Vector3::new(self.x / rhs, self.y / rhs, self.z / rhs) + } +} + +impl core::ops::Div for Vector3 { + type Output = Vector3; + fn div(self, rhs: f32) -> Self::Output { + Vector3::new(self.x / rhs, self.y / rhs, self.z / rhs) + } +} + +// pub fn lerp(a: u32, b: u32, n: f32) -> u32 { +// a + (n * (b - a) as f32) as u32 +// } diff --git a/rust_raytrace/src/raytrace.rs b/rust_raytrace/src/raytrace.rs new file mode 100644 index 00000000..ad91b64c --- /dev/null +++ b/rust_raytrace/src/raytrace.rs @@ -0,0 +1,294 @@ +use crate::math::*; + +use alloc::boxed::Box; +use alloc::vec::Vec; + +use micromath::F32Ext; + +pub struct HitResult { + distance: f32, + hit_pos: Vector3, + normal: Vector3, + color: Color, +} + +pub enum IntersectResult { + Miss, + Hit(HitResult), +} + +pub trait Intersector { + fn intersect(&self, origin: &Vector3, direction: &Vector3) -> IntersectResult; + fn light_intens(&self, res: &HitResult, light: &Light) -> f32; + fn cast_shadow(&self) -> bool; + fn receive_shadow(&self) -> bool; + fn hash(&self) -> u32; +} + +pub struct Sphere { + pub id: u32, + pub center: Vector3, + pub radius: f32, + pub color: Color, +} + +pub struct Plane { + pub id: u32, + pub position: Vector3, + pub normal: Vector3, + pub color: Color, +} + +pub struct Light { + pub position: Vector3, + pub intensity: f32, +} + +impl Intersector for Sphere { + fn intersect(&self, origin: &Vector3, direction: &Vector3) -> IntersectResult { + let r = self.radius; + let c0 = origin - &self.center; + + let a = direction.dot(&direction); // 3 + let half_b = c0.dot(&direction); // -10 + let c = c0.dot(&c0) - r * r; // 75 - 4 = 71 + + let disc = half_b * half_b - a * c; + if disc <= 0.0 { + return IntersectResult::Miss; + } + + let disc_sqrt = disc.sqrt(); + + let t1 = (-half_b + disc_sqrt) / a; + let t2 = (-half_b - disc_sqrt) / a; + + let closest = if t1 < 0.0 { + t2 + } else if t1 < t2 { + t1 + } else { + t2 + }; + if closest < 0.001 { + return IntersectResult::Miss; + } + + let hit_pos = (direction - origin).normalized() * closest; + let normal = (&hit_pos - &self.center).normalized(); + + IntersectResult::Hit(HitResult { + distance: closest, + hit_pos, + normal, + color: self.color.clone(), + }) + } + + fn light_intens(&self, res: &HitResult, light: &Light) -> f32 { + let dot = res.normal.dot(&light.position) * light.intensity; + let dot = if dot < 0.04 { 0.04 } else { dot }; + if dot > 0.923 { + return dot * 18.0; + } + if dot > 1.0 { + 1.0 + } else { + dot + } + } + + fn cast_shadow(&self) -> bool { + true + } + + fn receive_shadow(&self) -> bool { + false + } + + fn hash(&self) -> u32 { + self.id + } +} + +impl Intersector for Plane { + fn intersect(&self, origin: &Vector3, direction: &Vector3) -> IntersectResult { + let den = self.normal.dot(&direction); + if den.abs() < 0.001 { + return IntersectResult::Miss; + } + + let distance = (&self.position - origin).dot(&self.normal) / den; + if distance < 0.0 { + return IntersectResult::Miss; + } + + IntersectResult::Hit(HitResult { + distance, + hit_pos: direction * distance, + normal: self.normal.clone(), + color: self.color.clone(), + }) + } + + fn light_intens(&self, res: &HitResult, light: &Light) -> f32 { + let dir = &light.position - &res.hit_pos; + let falloff = 26.0 - dir.len(); + let falloff = if falloff < 0.2 { 0.2 } else { falloff }; + falloff * light.intensity + } + + fn cast_shadow(&self) -> bool { + false + } + + fn receive_shadow(&self) -> bool { + true + } + + fn hash(&self) -> u32 { + self.id + } +} + +pub struct Scene { + objs: Vec>, + lights: Vec, +} + +impl Scene { + pub fn new() -> Self { + Scene { + objs: Vec::new(), + lights: Vec::new(), + } + } + + pub fn add_obj(&mut self, obj: I) { + self.objs.push(Box::new(obj)); + } + + pub fn add_light(&mut self, light: Light) { + self.lights.push(light); + } + + fn view_vector(x: i32, y: i32) -> Vector3 { + Vector3::new( + x as f32, + y as f32, + (crate::FB_WIDTH / crate::superscale() / 2) as f32, + ) + .normalized() + } + + pub fn raytrace(&self, x: i32, y: i32) -> Color { + let view = Self::view_vector(x, y); + self.do_raytrace( + &Vector3::ZERO, + &view, + Color { r: 0, g: 0, b: 0 }, + 0xffffffff, + ) + } + + fn do_raytrace( + &self, + origin: &Vector3, + view: &Vector3, + background: Color, + ignore_hash: u32, + ) -> Color { + let mut closest = IntersectResult::Miss; + let mut hit_obj = None; + + for obj in &self.objs { + if obj.hash() == ignore_hash { + continue; + } + let res = obj.intersect(origin, view); + if let IntersectResult::Hit(HitResult { distance, .. }) = res { + match closest { + IntersectResult::Miss => { + closest = res; + hit_obj = Some(obj); + } + IntersectResult::Hit(HitResult { + distance: closest_dist, + .. + }) if closest_dist > distance => { + closest = res; + hit_obj = Some(obj); + } + _ => {} + } + } + } + + if let IntersectResult::Hit(res) = closest { + let hit_obj = hit_obj.unwrap(); + let mut col_out = Color { r: 0, g: 0, b: 0 }; + if res.color.r == 255 && res.color.g == 255 && res.color.b == 255 { + // mirror + let invec = view - origin; + let refl = + &(&Vector3::ZERO - &invec) + &(&res.normal * invec.dot(&res.normal) * 2.0); + let refl = Vector3 { + x: -refl.x, + y: -refl.y, + z: refl.z, + }; + let rim = if view.dot(&res.normal) < 0.15 { 16 } else { 5 }; + let mirror_color = Color { + r: rim, + g: rim, + b: rim, + }; + // recurse into mirror space + let res = + self.do_raytrace(&res.hit_pos, &refl, mirror_color.clone(), hit_obj.hash()); + let res = Color { + r: res.r.max(mirror_color.r), + g: res.g.max(mirror_color.g), + b: res.b.max(mirror_color.b), + }; + return res; + } + // calculate lighting + for light in &self.lights { + // check if in shadow + if hit_obj.receive_shadow() { + let dir = &light.position - &res.hit_pos; + let dir_norm = dir.normalized(); + let mut shadow = false; + for obj in &self.objs { + if obj.hash() == hit_obj.hash() || !obj.cast_shadow() { + continue; + } + let res = obj.intersect(&res.hit_pos, &dir_norm); + if let IntersectResult::Hit(_) = res { + // yep, shadowed + shadow = true; + break; + } + } + if shadow { + // return Color { r: 255, g: 0, b: 0 }; + continue; + } + } + + let intens = hit_obj.light_intens(&res, light); + + col_out = Color { + r: (col_out.r + (res.color.r as f32 * intens) as u32).min(255), + g: (col_out.g + (res.color.g as f32 * intens) as u32).min(255), + b: (col_out.b + (res.color.b as f32 * intens) as u32).min(255), + }; + } + return col_out; + } + + // black background + background + } +} diff --git a/src/emu.h b/src/emu.h index 5bc2589c..9b636bab 100644 --- a/src/emu.h +++ b/src/emu.h @@ -10,6 +10,9 @@ #include "trap.h" #include "csr.h" +#include "pngout.h" +static int ebreak_png = 0; + static bool allow_ecall_exit = false; #define AS_SIGNED(val) (*(int32_t*)&val) @@ -203,6 +206,10 @@ DEF(ebreak, FormatEmpty, { // system printf("EBREAK!\n"); VERBOSE=4; SINGLE_STEP=1; + /* char buf[12]; */ + /* sprintf(buf, "ram_%d.png", ebreak_png++); */ + /* int ret = write_ram_as_png(buf); */ + /* printf(" (%d)", ret); */ }) DEF(ecall, FormatEmpty, { // system if (allow_ecall_exit && cpu->xreg[17] == 93) { diff --git a/src/main.c b/src/main.c index 5701a50e..d588bba5 100644 --- a/src/main.c +++ b/src/main.c @@ -14,6 +14,7 @@ #include #include "../elfy/elfy.h" +#include "pngout.h" struct termios t, t_orig; void buf_off(void) @@ -76,7 +77,9 @@ void term(int signum) /* printf("%05x: %08x\n", i, val); */ /* } */ - print_tracebuf(); + /* print_tracebuf(); */ + + write_ram_as_png("ram.png"); /* printf("\n"); */ exit(EXIT_FAILURE); @@ -194,6 +197,10 @@ int main(int argc, char *argv[]) { tracebuf_idx = 0; } + /* if (cpu.clock > 1000000) { */ + /* term(1); */ + /* } */ + /* if (cpu.clock > 5000000 && cpu.clock % 100000 == 0) { */ /* uint arb[8]; */ /* for (int iarb = 0; iarb < 8; iarb++) { */ diff --git a/src/mem.h b/src/mem.h index 9b426189..db88b31a 100644 --- a/src/mem.h +++ b/src/mem.h @@ -112,9 +112,9 @@ uint mem_get_half_word(cpu_t *cpu, uint addr) { uint mem_get_word(cpu_t *cpu, uint addr) { /* assert((addr & ~(0x3)) == addr); */ return mem_get_byte(cpu, addr) | - ((uint16_t)mem_get_byte(cpu, addr + 1) << 8) | - ((uint16_t)mem_get_byte(cpu, addr + 2) << 16) | - ((uint16_t)mem_get_byte(cpu, addr + 3) << 24); + ((uint32_t)mem_get_byte(cpu, addr + 1) << 8) | + ((uint32_t)mem_get_byte(cpu, addr + 2) << 16) | + ((uint32_t)mem_get_byte(cpu, addr + 3) << 24); } void mem_set_byte(cpu_t *cpu, uint addr, uint val) { diff --git a/src/pngout.h b/src/pngout.h new file mode 100644 index 00000000..864736cf --- /dev/null +++ b/src/pngout.h @@ -0,0 +1,68 @@ +#ifndef PNGOUT_H +#define PNGOUT_H + +#include +#include +#include +#include +#include "mem.h" +#include "../tinypngout/TinyPngOut.h" + +int write_ram_as_png(const char *p) { + printf("Dumping RAM as '%s'...\n", p); + + const int WIDTH = 2048; + const int HEIGHT = 2048 - 128; + uint8_t *pixels = malloc(3*WIDTH*HEIGHT); + + int px_idx = 0; + for (int i = 0; i < MEM_SIZE; i += 4) { + if (((i + 4) % 16) == 0) continue; // ignore alpha + uint32_t m = (uint32_t)cpu.mem[i] | + ((uint32_t)cpu.mem[i + 1] << 8) | + ((uint32_t)cpu.mem[i + 2] << 16) | + ((uint32_t)cpu.mem[i + 3] << 24); + uint8_t val = (uint8_t)(((uint64_t)m * 255llu) / 0xffffffffllu); + /* printf(" %d", val); */ + pixels[px_idx++] = val; + } + + // Open output file + FILE *fout = fopen(p, "wb"); + if (fout == NULL) { + perror("Error: fopen"); + free(pixels); + return EXIT_FAILURE; + } + + // Initialize Tiny PNG Output + struct TinyPngOut pngout; + enum TinyPngOut_Status status = TinyPngOut_init(&pngout, (uint32_t)WIDTH, (uint32_t)HEIGHT, fout); + if (status != TINYPNGOUT_OK) { + perror("TinyPngOut create error"); + fclose(fout); + free(pixels); + return status; + } + + // Write image data + status = TinyPngOut_write(&pngout, pixels, (size_t)(WIDTH * HEIGHT)); + if (status != TINYPNGOUT_OK) { + perror("TinyPngOut write error"); + fclose(fout); + free(pixels); + return status; + } + + // Close output file + if (fclose(fout) != 0) { + perror("Error: fclose"); + free(pixels); + return EXIT_FAILURE; + } + + free(pixels); + return EXIT_SUCCESS; +} + +#endif diff --git a/tinypngout/TinyPngOut.c b/tinypngout/TinyPngOut.c new file mode 100644 index 00000000..250b79ad --- /dev/null +++ b/tinypngout/TinyPngOut.c @@ -0,0 +1,223 @@ +/* + * Tiny PNG Output (C) + * + * Copyright (c) 2018 Project Nayuki + * https://www.nayuki.io/page/tiny-png-output + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +#include +#include +#include "TinyPngOut.h" + + +static const uint16_t DEFLATE_MAX_BLOCK_SIZE = 65535; + +static bool write (struct TinyPngOut this[static 1], const uint8_t data[], size_t len); +static void crc32 (struct TinyPngOut this[static 1], const uint8_t data[], size_t len); +static void adler32(struct TinyPngOut this[static 1], const uint8_t data[], size_t len); +static void putBigUint32(uint32_t val, uint8_t array[static 4]); + + +enum TinyPngOut_Status TinyPngOut_init(struct TinyPngOut this[static 1], uint32_t w, uint32_t h, FILE out[static 1]) { + // Check arguments + if (w == 0 || h == 0 || out == NULL) + return TINYPNGOUT_INVALID_ARGUMENT; + this->width = w; + this->height = h; + + // Compute and check data siezs + uint64_t lineSz = (uint64_t)this->width * 3 + 1; + if (lineSz > UINT32_MAX) + return TINYPNGOUT_IMAGE_TOO_LARGE; + this->lineSize = (uint32_t)lineSz; + + uint64_t uncompRm = this->lineSize * this->height; + if (uncompRm > UINT32_MAX) + return TINYPNGOUT_IMAGE_TOO_LARGE; + this->uncompRemain = (uint32_t)uncompRm; + + uint32_t numBlocks = this->uncompRemain / DEFLATE_MAX_BLOCK_SIZE; + if (this->uncompRemain % DEFLATE_MAX_BLOCK_SIZE != 0) + numBlocks++; // Round up + // 5 bytes per DEFLATE uncompressed block header, 2 bytes for zlib header, 4 bytes for zlib Adler-32 footer + uint64_t idatSize = (uint64_t)numBlocks * 5 + 6; + idatSize += this->uncompRemain; + if (idatSize > (uint32_t)INT32_MAX) + return TINYPNGOUT_IMAGE_TOO_LARGE; + + // Write header (not a pure header, but a couple of things concatenated together) + uint8_t header[] = { // 43 bytes long + // PNG header + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, + // IHDR chunk + 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, + 0, 0, 0, 0, // 'width' placeholder + 0, 0, 0, 0, // 'height' placeholder + 0x08, 0x02, 0x00, 0x00, 0x00, + 0, 0, 0, 0, // IHDR CRC-32 placeholder + // IDAT chunk + 0, 0, 0, 0, // 'idatSize' placeholder + 0x49, 0x44, 0x41, 0x54, + // DEFLATE data + 0x08, 0x1D, + }; + putBigUint32(this->width, &header[16]); + putBigUint32(this->height, &header[20]); + putBigUint32(idatSize, &header[33]); + this->crc = 0; + crc32(this, &header[12], 17); + putBigUint32(this->crc, &header[29]); + this->output = out; + if (!write(this, header, sizeof(header) / sizeof(header[0]))) + return TINYPNGOUT_IO_ERROR; + + this->crc = 0; + crc32(this, &header[37], 6); // 0xD7245B6B + this->adler = 1; + + this->positionX = 0; + this->positionY = 0; + this->deflateFilled = 0; + return TINYPNGOUT_OK; +} + + +enum TinyPngOut_Status TinyPngOut_write(struct TinyPngOut this[static 1], const uint8_t pixels[], size_t count) { + if (count > SIZE_MAX / 3) + return TINYPNGOUT_INVALID_ARGUMENT; + count *= 3; // Convert pixel count to byte count + while (count > 0) { + if (pixels == NULL) + return TINYPNGOUT_INVALID_ARGUMENT; + if (this->positionY >= this->height) + return TINYPNGOUT_INVALID_ARGUMENT; // All image pixels already written + + if (this->deflateFilled == 0) { // Start DEFLATE block + uint16_t size = DEFLATE_MAX_BLOCK_SIZE; + if (this->uncompRemain < size) + size = (uint16_t)this->uncompRemain; + const uint8_t header[] = { // 5 bytes long + (uint8_t)(this->uncompRemain <= DEFLATE_MAX_BLOCK_SIZE ? 1 : 0), + (uint8_t)(size >> 0), + (uint8_t)(size >> 8), + (uint8_t)((size >> 0) ^ 0xFF), + (uint8_t)((size >> 8) ^ 0xFF), + }; + if (!write(this, header, sizeof(header) / sizeof(header[0]))) + return TINYPNGOUT_IO_ERROR; + crc32(this, header, sizeof(header) / sizeof(header[0])); + } + assert(this->positionX < this->lineSize && this->deflateFilled < DEFLATE_MAX_BLOCK_SIZE); + + if (this->positionX == 0) { // Beginning of line - write filter method byte + uint8_t b[] = {0}; + if (!write(this, b, sizeof(b) / sizeof(b[0]))) + return TINYPNGOUT_IO_ERROR; + crc32(this, b, 1); + adler32(this, b, 1); + this->positionX++; + this->uncompRemain--; + this->deflateFilled++; + + } else { // Write some pixel bytes for current line + uint16_t n = DEFLATE_MAX_BLOCK_SIZE - this->deflateFilled; + if (this->lineSize - this->positionX < n) + n = (uint16_t)(this->lineSize - this->positionX); + if (count < n) + n = (uint16_t)count; + assert(n > 0); + if (!write(this, pixels, n)) + return TINYPNGOUT_IO_ERROR; + + // Update checksums + crc32(this, pixels, n); + adler32(this, pixels, n); + + // Increment positions + count -= n; + pixels += n; + this->positionX += n; + this->uncompRemain -= n; + this->deflateFilled += n; + } + + if (this->deflateFilled >= DEFLATE_MAX_BLOCK_SIZE) + this->deflateFilled = 0; // End current block + + if (this->positionX == this->lineSize) { // Increment line + this->positionX = 0; + this->positionY++; + if (this->positionY == this->height) { // Reached end of pixels + uint8_t footer[] = { // 20 bytes long + 0, 0, 0, 0, // DEFLATE Adler-32 placeholder + 0, 0, 0, 0, // IDAT CRC-32 placeholder + // IEND chunk + 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x4E, 0x44, + 0xAE, 0x42, 0x60, 0x82, + }; + putBigUint32(this->adler, &footer[0]); + crc32(this, &footer[0], 4); + putBigUint32(this->crc, &footer[4]); + if (!write(this, footer, sizeof(footer) / sizeof(footer[0]))) + return TINYPNGOUT_IO_ERROR; + } + } + } + return TINYPNGOUT_OK; +} + + + +/*---- Private utility functions ----*/ + +// Returns whether the write was successful. +static bool write(struct TinyPngOut this[static 1], const uint8_t data[], size_t len) { + return fwrite(data, sizeof(data[0]), len, this->output) == len; +} + + +// Reads the 'crc' field and updates its value based on the given array of new data. +static void crc32(struct TinyPngOut this[static 1], const uint8_t data[], size_t len) { + this->crc = ~this->crc; + for (size_t i = 0; i < len; i++) { + for (int j = 0; j < 8; j++) { // Inefficient bitwise implementation, instead of table-based + uint32_t bit = (this->crc ^ (data[i] >> j)) & 1; + this->crc = (this->crc >> 1) ^ ((-bit) & UINT32_C(0xEDB88320)); + } + } + this->crc = ~this->crc; +} + + +// Reads the 'adler' field and updates its value based on the given array of new data. +static void adler32(struct TinyPngOut this[static 1], const uint8_t data[], size_t len) { + uint32_t s1 = this->adler & 0xFFFF; + uint32_t s2 = this->adler >> 16; + for (size_t i = 0; i < len; i++) { + s1 = (s1 + data[i]) % 65521; + s2 = (s2 + s1) % 65521; + } + this->adler = s2 << 16 | s1; +} + + +static void putBigUint32(uint32_t val, uint8_t array[static 4]) { + for (int i = 0; i < 4; i++) + array[i] = (uint8_t)(val >> ((3 - i) * 8)); +} diff --git a/tinypngout/TinyPngOut.h b/tinypngout/TinyPngOut.h new file mode 100644 index 00000000..13178154 --- /dev/null +++ b/tinypngout/TinyPngOut.h @@ -0,0 +1,75 @@ +/* + * Tiny PNG Output (C) + * + * Copyright (c) 2018 Project Nayuki + * https://www.nayuki.io/page/tiny-png-output + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +#pragma once + +#include +#include +#include + + +// Treat this data structure as private and opaque. Do not read or write any fields directly. +// This structure can be safely discarded at any time, because the functions of this library don't +// allocate memory or resources. The caller is responsible for initializing objects and cleaning up. +struct TinyPngOut { + + // Immutable configuration + uint32_t width; // Measured in pixels + uint32_t height; // Measured in pixels + uint32_t lineSize; // Measured in bytes, equal to (width * 3 + 1) + + // Running state + FILE *output; + uint32_t positionX; // Next byte index in current line + uint32_t positionY; // Line index of next byte + uint32_t uncompRemain; // Number of uncompressed bytes remaining + uint16_t deflateFilled; // Bytes filled in the current block (0 <= n < DEFLATE_MAX_BLOCK_SIZE) + uint32_t crc; // Primarily for IDAT chunk + uint32_t adler; // For DEFLATE data within IDAT + +}; + + +enum TinyPngOut_Status { + TINYPNGOUT_OK, + TINYPNGOUT_INVALID_ARGUMENT, + TINYPNGOUT_IMAGE_TOO_LARGE, + TINYPNGOUT_IO_ERROR, +}; + + +/* + * Creates a PNG writer with the given width and height (both non-zero) and byte output stream. + * TinyPngOut will leave the output stream still open once it finishes writing the PNG file data. + * Returns an error if the dimensions exceed certain limits (e.g. w * h > 700 million). + */ +enum TinyPngOut_Status TinyPngOut_init(struct TinyPngOut this[static 1], uint32_t w, uint32_t h, FILE out[static 1]); + + +/* + * Writes 'count' pixels from the given array to the output stream. This reads count*3 + * bytes from the array. Pixels are presented from top to bottom, left to right, and with + * subpixels in RGB order. This object keeps track of how many pixels were written and + * various position variables. It is an error to write more pixels in total than width*height. + * Once exactly width*height pixels have been written with this TinyPngOut object, + * there are no more valid operations on the object and it should be discarded. + */ +enum TinyPngOut_Status TinyPngOut_write(struct TinyPngOut this[static 1], const uint8_t pixels[], size_t count);