mirror of
https://github.com/PiMaker/rvc.git
synced 2024-11-10 06:00:07 +00:00
add rust_raytrace payload and image output
plus some minor fixes IIRC
This commit is contained in:
parent
1f614a46b5
commit
93a50d7807
4
.gitignore
vendored
4
.gitignore
vendored
@ -6,8 +6,8 @@ rvc
|
|||||||
*.o
|
*.o
|
||||||
elfy/target
|
elfy/target
|
||||||
elfy/Cargo.lock
|
elfy/Cargo.lock
|
||||||
rust_payload/target
|
rust_*/target
|
||||||
rust_payload/Cargo.lock
|
rust_*/Cargo.lock
|
||||||
fw_payload.*
|
fw_payload.*
|
||||||
*.dtb
|
*.dtb
|
||||||
*.bmp
|
*.bmp
|
||||||
|
16
Makefile
16
Makefile
@ -1,5 +1,5 @@
|
|||||||
TARGET ?= rvc
|
TARGET ?= rvc
|
||||||
SRC_DIRS ?= ./src
|
SRC_DIRS ?= ./src ./tinypngout
|
||||||
|
|
||||||
CC=clang
|
CC=clang
|
||||||
|
|
||||||
@ -12,10 +12,10 @@ INC_FLAGS := $(addprefix -I,$(INC_DIRS))
|
|||||||
|
|
||||||
Q=$(CURDIR)/buildroot-2021.05/output/host/bin/riscv32-buildroot-linux-gnu-
|
Q=$(CURDIR)/buildroot-2021.05/output/host/bin/riscv32-buildroot-linux-gnu-
|
||||||
|
|
||||||
CFLAGS=-g
|
CFLAGS ?= -g -O2
|
||||||
|
|
||||||
$(TARGET): $(OBJS)
|
$(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)
|
-include $(DEPS)
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ dts.dtb: dts.dts
|
|||||||
OPENSBI_BUILD=opensbi/build/platform/generic/firmware
|
OPENSBI_BUILD=opensbi/build/platform/generic/firmware
|
||||||
PAYLOAD=rust_payload/target/riscv32ima-unknown-none-elf/release/rust_payload.bin
|
PAYLOAD=rust_payload/target/riscv32ima-unknown-none-elf/release/rust_payload.bin
|
||||||
LINUX_PAYLOAD=linux/arch/riscv/boot/Image
|
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=buildroot-2021.05/build.marker
|
||||||
$(BUILDROOT_MARKER):
|
$(BUILDROOT_MARKER):
|
||||||
@ -40,6 +41,15 @@ $(PAYLOAD): $(shell find rust_payload/src -type f)
|
|||||||
$(Q)objcopy -O binary \
|
$(Q)objcopy -O binary \
|
||||||
rust_payload/target/riscv32ima-unknown-none-elf/release/rust_payload $(PAYLOAD)
|
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
|
rust_payload.bin: rust_payload.elf
|
||||||
cp $(OPENSBI_BUILD)/fw_payload.bin ./rust_payload.bin
|
cp $(OPENSBI_BUILD)/fw_payload.bin ./rust_payload.bin
|
||||||
rust_payload.elf: $(PAYLOAD) $(BUILDROOT_MARKER)
|
rust_payload.elf: $(PAYLOAD) $(BUILDROOT_MARKER)
|
||||||
|
8
rust_raytrace/Cargo.toml
Normal file
8
rust_raytrace/Cargo.toml
Normal 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
30
rust_raytrace/linker.ld
Normal 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
|
||||||
|
|
||||||
|
}
|
1
rust_raytrace/rust-raytracer-weekend
Submodule
1
rust_raytrace/rust-raytracer-weekend
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 91e65843f1f9621a16f7cc5400508d71944d76bd
|
64
rust_raytrace/src/low_level/mod.rs
Normal file
64
rust_raytrace/src/low_level/mod.rs
Normal 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);
|
||||||
|
}
|
51
rust_raytrace/src/low_level/uart.rs
Normal file
51
rust_raytrace/src/low_level/uart.rs
Normal 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
240
rust_raytrace/src/main.rs
Normal 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
114
rust_raytrace/src/math.rs
Normal 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
|
||||||
|
// }
|
294
rust_raytrace/src/raytrace.rs
Normal file
294
rust_raytrace/src/raytrace.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,9 @@
|
|||||||
#include "trap.h"
|
#include "trap.h"
|
||||||
#include "csr.h"
|
#include "csr.h"
|
||||||
|
|
||||||
|
#include "pngout.h"
|
||||||
|
static int ebreak_png = 0;
|
||||||
|
|
||||||
static bool allow_ecall_exit = false;
|
static bool allow_ecall_exit = false;
|
||||||
|
|
||||||
#define AS_SIGNED(val) (*(int32_t*)&val)
|
#define AS_SIGNED(val) (*(int32_t*)&val)
|
||||||
@ -203,6 +206,10 @@ DEF(ebreak, FormatEmpty, { // system
|
|||||||
printf("EBREAK!\n");
|
printf("EBREAK!\n");
|
||||||
VERBOSE=4;
|
VERBOSE=4;
|
||||||
SINGLE_STEP=1;
|
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
|
DEF(ecall, FormatEmpty, { // system
|
||||||
if (allow_ecall_exit && cpu->xreg[17] == 93) {
|
if (allow_ecall_exit && cpu->xreg[17] == 93) {
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
|
|
||||||
#include "../elfy/elfy.h"
|
#include "../elfy/elfy.h"
|
||||||
|
#include "pngout.h"
|
||||||
|
|
||||||
struct termios t, t_orig;
|
struct termios t, t_orig;
|
||||||
void buf_off(void)
|
void buf_off(void)
|
||||||
@ -76,7 +77,9 @@ void term(int signum)
|
|||||||
/* printf("%05x: %08x\n", i, val); */
|
/* printf("%05x: %08x\n", i, val); */
|
||||||
/* } */
|
/* } */
|
||||||
|
|
||||||
print_tracebuf();
|
/* print_tracebuf(); */
|
||||||
|
|
||||||
|
write_ram_as_png("ram.png");
|
||||||
|
|
||||||
/* printf("\n"); */
|
/* printf("\n"); */
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
@ -194,6 +197,10 @@ int main(int argc, char *argv[]) {
|
|||||||
tracebuf_idx = 0;
|
tracebuf_idx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* if (cpu.clock > 1000000) { */
|
||||||
|
/* term(1); */
|
||||||
|
/* } */
|
||||||
|
|
||||||
/* if (cpu.clock > 5000000 && cpu.clock % 100000 == 0) { */
|
/* if (cpu.clock > 5000000 && cpu.clock % 100000 == 0) { */
|
||||||
/* uint arb[8]; */
|
/* uint arb[8]; */
|
||||||
/* for (int iarb = 0; iarb < 8; iarb++) { */
|
/* for (int iarb = 0; iarb < 8; iarb++) { */
|
||||||
|
@ -112,9 +112,9 @@ uint mem_get_half_word(cpu_t *cpu, uint addr) {
|
|||||||
uint mem_get_word(cpu_t *cpu, uint addr) {
|
uint mem_get_word(cpu_t *cpu, uint addr) {
|
||||||
/* assert((addr & ~(0x3)) == addr); */
|
/* assert((addr & ~(0x3)) == addr); */
|
||||||
return mem_get_byte(cpu, addr) |
|
return mem_get_byte(cpu, addr) |
|
||||||
((uint16_t)mem_get_byte(cpu, addr + 1) << 8) |
|
((uint32_t)mem_get_byte(cpu, addr + 1) << 8) |
|
||||||
((uint16_t)mem_get_byte(cpu, addr + 2) << 16) |
|
((uint32_t)mem_get_byte(cpu, addr + 2) << 16) |
|
||||||
((uint16_t)mem_get_byte(cpu, addr + 3) << 24);
|
((uint32_t)mem_get_byte(cpu, addr + 3) << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mem_set_byte(cpu_t *cpu, uint addr, uint val) {
|
void mem_set_byte(cpu_t *cpu, uint addr, uint val) {
|
||||||
|
68
src/pngout.h
Normal file
68
src/pngout.h
Normal 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
223
tinypngout/TinyPngOut.c
Normal 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
75
tinypngout/TinyPngOut.h
Normal 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);
|
Loading…
Reference in New Issue
Block a user