add rust_raytrace payload and image output

plus some minor fixes IIRC
This commit is contained in:
Stefan 2021-10-14 20:14:51 +02:00
parent 1f614a46b5
commit 93a50d7807
16 changed files with 1201 additions and 9 deletions

4
.gitignore vendored
View File

@ -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

View File

@ -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)

8
rust_raytrace/Cargo.toml Normal file
View File

@ -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"

30
rust_raytrace/linker.ld Normal file
View File

@ -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
}

@ -0,0 +1 @@
Subproject commit 91e65843f1f9621a16f7cc5400508d71944d76bd

View File

@ -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);
}

View File

@ -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);
}
}

240
rust_raytrace/src/main.rs Normal file
View File

@ -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,
// }
// }

114
rust_raytrace/src/math.rs Normal file
View File

@ -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<f32> 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<f32> 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<f32> 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<f32> 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
// }

View File

@ -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<Box<dyn Intersector>>,
lights: Vec<Light>,
}
impl Scene {
pub fn new() -> Self {
Scene {
objs: Vec::new(),
lights: Vec::new(),
}
}
pub fn add_obj<I: Intersector + 'static>(&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
}
}

View File

@ -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) {

View File

@ -14,6 +14,7 @@
#include <termios.h>
#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++) { */

View File

@ -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) {

68
src/pngout.h Normal file
View File

@ -0,0 +1,68 @@
#ifndef PNGOUT_H
#define PNGOUT_H
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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

223
tinypngout/TinyPngOut.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdbool.h>
#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));
}

75
tinypngout/TinyPngOut.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
// 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);