initial: draw raytraced spheres on the sega saturn

This commit is contained in:
Zack Buhman 2023-01-23 21:08:13 -08:00
commit a4b72e2f85
10 changed files with 564 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.gch
*.o
*.elf
*.bin
*.iso
*.ppm
*.png

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
CFLAGS = -Isaturn -Imath
OPT = -O3
all: raytracing.iso
LIB = ./saturn
include $(LIB)/common.mk
LIBGCC = $(shell $(CC) -print-file-name=libgcc.a)
raytracing.elf: main-saturn.o raytracing.o $(LIBGCC)

144
fp.hpp Normal file
View File

@ -0,0 +1,144 @@
#pragma once
#include <stdint.h>
struct fp_raw_tag {};
template <typename T, typename I, int B>
struct fp
{
T value;
constexpr inline fp(T n) noexcept
: value(n * (1 << B))
{}
constexpr inline explicit fp(T n, struct fp_raw_tag) noexcept
: value(n)
{}
constexpr inline fp<T, I, B> operator-() const noexcept
{
return fp(-value, fp_raw_tag{});
}
};
template <typename T, typename I, int B>
constexpr inline fp<T, I, B> operator+(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
return fp<T, I, B>(a.value + b.value, fp_raw_tag{});
}
template <typename T, typename I, int B>
constexpr inline fp<T, I, B> operator-(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
return fp<T, I, B>(a.value - b.value, fp_raw_tag{});
}
template <typename T, typename I, int B>
constexpr inline fp<T, I, B> operator*(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
I p = (static_cast<I>(a.value) * static_cast<I>(b.value));
return fp<T, I, B>(static_cast<T>(p >> B), fp_raw_tag{});
}
template <typename T, typename I, int B>
constexpr inline fp<T, I, B> operator*(const fp<T, I, B>& a, T b) noexcept
{
I p = (static_cast<I>(a.value) * static_cast<I>(b));
return fp<T, I, B>(static_cast<T>(p), fp_raw_tag{});
}
template <typename T, typename I, int B>
constexpr inline fp<T, I, B> operator*(T b, const fp<T, I, B>& a) noexcept
{
I p = (static_cast<I>(a.value) * static_cast<I>(b));
return fp<T, I, B>(static_cast<T>(p), fp_raw_tag{});
}
template <typename T, typename I, int B>
constexpr inline fp<T, I, B> operator/(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
I p = (static_cast<I>(a.value) * (static_cast<I>(1) << B)) / static_cast<I>(b.value);
return fp<T, I, B>(static_cast<T>(p), fp_raw_tag{});
}
// comparison
template <typename T, typename I, int B>
constexpr inline bool operator==(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
return a.value == b.value;
}
template <typename T, typename I, int B>
constexpr inline bool operator!=(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
return a.value != b.value;
}
template <typename T, typename I, int B>
constexpr inline bool operator<(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
return a.value < b.value;
}
template <typename T, typename I, int B>
constexpr inline bool operator>(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
return a.value > b.value;
}
template <typename T, typename I, int B>
constexpr inline bool operator<=(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
return a.value <= b.value;
}
template <typename T, typename I, int B>
constexpr inline bool operator>=(const fp<T, I, B>& a, const fp<T, I, B>& b) noexcept
{
return a.value >= b.value;
}
// limits
template <typename F>
struct fp_limits;
template <typename T, typename I, int B>
struct fp_limits<fp<T, I, B>>
{
static constexpr fp<T, I, B> min() noexcept
{
return fp<T, I, B>(-(1 << (2 * B - 1)), fp_raw_tag{});
}
static constexpr fp<T, I, B> max() noexcept
{
return fp<T, I, B>((static_cast<I>(1) << (2 * B - 1)) - 1, fp_raw_tag{});
}
};
// specializations
using fp16_16 = fp<int32_t, int64_t, 16>;
constexpr fp16_16 sqrt(fp16_16 n) noexcept
{
int32_t x = n.value;
int32_t c = 0;
int32_t d = 1 << 30;
while (d > (1 << 6))
{
int32_t t = c + d;
if (x >= t)
{
x -= t;
c = t + d;
}
x <<= 1;
d >>= 1;
}
return fp16_16(c >> 8, fp_raw_tag{});
}

54
main-hosted.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <iostream>
#include <cstdint>
using namespace std;
#include "vec.hpp"
#include "fp.hpp"
#include "raytracing.hpp"
typedef vec<3, uint8_t> pixel;
static pixel frame[canvas::height][canvas::width] = { 0 };
fp16_16 clamp(fp16_16 const& n)
{
return (n > fp16_16(1) ? fp16_16(1) : (n < fp16_16(0) ? fp16_16(0) : n));
};
uint8_t to_uint8_t(fp16_16 const& v)
{
return static_cast<uint8_t>(v.value >> 16);
}
void put_pixel(int32_t x, int32_t y, const vec3& color)
{
using namespace canvas;
int sx = width / 2 + x;
int sy = height / 2 - y;
if (!(sx >= 0 && sx < width && sy >= 0 && sy < height)) {
return;
}
vec3 px255 = functor1(clamp, color) * fp16_16(255);
frame[sy][sx] = functor1(to_uint8_t, px255);
}
void render_ppm(ostream& out)
{
using namespace canvas;
out << "P3 " << width << ' ' << height << " 255\n";
for (int sy = 0; sy < height; sy++) {
for (int sx = 0; sx < width; sx++) {
const pixel& px = frame[sy][sx];
out << +px.r << ' ' << +px.g << ' ' << +px.b << '\n';
}
}
}
int main()
{
render(put_pixel);
}

58
main-saturn.cpp Normal file
View File

@ -0,0 +1,58 @@
#include "vdp2.h"
#include "vdp1.h"
#include "scu.h"
#include "smpc.h"
#include "sh2.h"
#include "vec.hpp"
#include "fp.hpp"
#include "raytracing.hpp"
fp16_16 clamp(fp16_16 const& n)
{
return (n > fp16_16(1) ? fp16_16(1) : (n < fp16_16(0) ? fp16_16(0) : n));
};
uint16_t rgb15(const vec3& color)
{
vec3 c = functor1(clamp, color) * fp16_16(255);
uint8_t red = (c.r.value >> 16) & 0xff;
uint8_t green = (c.g.value >> 16) & 0xff;
uint8_t blue = (c.b.value >> 16) & 0xff;
return (blue << 10) | (green << 5) | (red << 0);
}
void put_pixel(int32_t x, int32_t y, const vec3& color)
{
int sx = 320 / 2 + x;
int sy = 240 / 2 - y;
vdp2.vram.u16[512 * sy + sx] = (1 << 15) | rgb15(color);
}
void main_asdf()
{
// DISP: Please make sure to change this bit from 0 to 1 during V blank.
vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE
| TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320);
vdp2.reg.BGON = BGON__N0ON;
vdp2.reg.CHCTLA = ( CHCTLA__N0CHCN__32K_COLOR // 15 bits per pixel, RGB
| CHCTLA__N0BMSZ__512x256_DOT
| CHCTLA__N0BMEN__BITMAP_FORMAT
);
vdp2.reg.MPOFN = MPOFN__N0MP(0);
render(put_pixel);
}
extern "C"
void start(void)
{
main_asdf();
while (1) {}
}

4
math.hpp Normal file
View File

@ -0,0 +1,4 @@
#pragma once
template <typename T>
constexpr T sqrt(T n) noexcept;

124
raytracing.cpp Normal file
View File

@ -0,0 +1,124 @@
#include <stdint.h>
#include "vec.hpp"
#include "fp.hpp"
#include "raytracing.hpp"
namespace viewport {
constexpr int width = 1;
constexpr int height = 1;
}
vec3 canvas_to_viewport(int cx, int cy)
{
return vec3(
fp16_16(((cx * viewport::width) * (1 << 16)) >> canvas::bit_width, fp_raw_tag{}),
fp16_16(((cy * viewport::height) * (1 << 16)) >> canvas::bit_height, fp_raw_tag{}),
fp16_16(1)
);
}
struct sphere {
vec3 center;
fp16_16 radius;
vec3 color;
};
struct scene {
sphere spheres[3];
};
constexpr scene scene {
{ // spheres
{
{0, -1, 3}, // center
fp16_16(1), // radius
{1, 0, 0}, // color
},
{
{2, 0, 4},
fp16_16(1),
{0, 0, 1},
},
{
{-2, 0, 4},
fp16_16(1),
{0, 1, 0},
}
}
};
static_assert(scene.spheres[0].center.z.value == (3 << 16));
struct t1_t2 {
fp16_16 t1;
fp16_16 t2;
};
t1_t2 intersect_ray_sphere(const vec3& origin, const vec3& direction, const sphere& sphere)
{
fp16_16 r = sphere.radius;
vec3 CO = origin - sphere.center;
auto a = dot(direction, direction);
auto b = dot(CO, direction) * static_cast<int32_t>(2);
auto c = dot(CO, CO) - r*r;
auto discriminant = b*b - static_cast<int32_t>(4)*a*c;
if (discriminant < fp16_16(0)) {
return {fp_limits<fp16_16>::max(), fp_limits<fp16_16>::max()};
} else {
auto sqrt_d = sqrt(discriminant);
auto a2 = fp16_16(a*static_cast<int32_t>(2));
auto t1 = (-b + sqrt_d) / a2;
auto t2 = (-b - sqrt_d) / a2;
return {t1, t2};
}
}
static vec3 trace_ray
(
const vec3& origin,
const vec3& direction,
const fp16_16 t_min,
const fp16_16 t_max
)
{
fp16_16 closest_t = fp_limits<fp16_16>::max();
const sphere * closest_sphere = nullptr;
for (int i = 0; i < 3; i++) {
auto& sphere = scene.spheres[i];
auto [t1, t2] = intersect_ray_sphere(origin, direction, sphere);
if (t1 >= t_min && t1 < t_max && t1 < closest_t) {
closest_t = t1;
closest_sphere = &sphere;
}
if (t2 >= t_min && t2 < t_max && t2 < closest_t) {
closest_t = t2;
closest_sphere = &sphere;
}
}
if (closest_sphere == nullptr) {
return vec3(0, 0, 0);
} else {
return closest_sphere->color;
}
}
void render(void (&put_pixel) (int32_t x, int32_t y, const vec3& c))
{
using namespace canvas;
vec3 origin = vec3(0, 0, 0);
for (int x = -(width/2); x < (width/2); x++) {
for (int y = -(height/2 + 1); y < (height/2 + 1); y++) {
vec3 direction = canvas_to_viewport(x, y);
vec3 color = trace_ray(origin, direction,
fp16_16(1),
fp_limits<fp16_16>::max());
put_pixel(x, y, color);
}
}
}

15
raytracing.hpp Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include "fp.hpp"
#include "raytracing.hpp"
using vec3 = vec<3, fp16_16>;
namespace canvas {
constexpr int bit_width = 8;
constexpr int bit_height = 8;
constexpr int width = (1 << bit_width);
constexpr int height = (1 << bit_height);
}
void render(void (&put_pixel) (int32_t x, int32_t y, const vec3& c));

1
saturn Symbolic link
View File

@ -0,0 +1 @@
../saturn-c

147
vec.hpp Normal file
View File

@ -0,0 +1,147 @@
#pragma once
#include "math.hpp"
template<int L, typename T>
struct vec;
//
// vec3
//
template<typename T>
struct vec<3, T>
{
union
{
struct { T x, y, z; };
struct { T r, g, b; };
struct { T s, t, p; };
};
inline constexpr vec();
inline constexpr vec(T scalar);
inline constexpr vec(T _x, T _y, T _z);
inline constexpr T const& operator[](int i) const;
template<typename U>
inline constexpr vec<3, T>& operator=(vec<3, U> const& v);
template<typename U>
inline constexpr vec<3, T>& operator+=(vec<3, U> const& v);
template<typename U>
inline constexpr vec<3, T>& operator-=(vec<3, U> const& v);
};
template <typename T>
inline constexpr vec<3, T>::vec()
: x(0), y(0), z(0)
{}
template <typename T>
inline constexpr vec<3, T>::vec(T scalar)
: x(scalar), y(scalar), z(scalar)
{}
template <typename T>
inline constexpr vec<3, T>::vec(T _x, T _y, T _z)
: x(_x), y(_y), z(_z)
{}
template <typename T>
inline constexpr T const& vec<3, T>::operator[](int i) const
{
switch(i)
{
default:
case 0:
return x;
case 1:
return y;
case 2:
return z;
}
}
template<typename T>
template<typename U>
inline constexpr vec<3, T>& vec<3, T>::operator=(vec<3, U> const& v)
{
this->x = static_cast<T>(v.x);
this->y = static_cast<T>(v.y);
this->z = static_cast<T>(v.z);
return *this;
}
template<typename T>
template<typename U>
inline constexpr vec<3, T>& vec<3, T>::operator+=(vec<3, U> const& v)
{
*this = *this + vec<3, T>(v);
return *this;
}
template<typename T>
template<typename U>
inline constexpr vec<3, T>& vec<3, T>::operator-=(vec<3, U> const& v)
{
*this = *this + vec<3, T>(v);
return *this;
}
template <typename T>
inline constexpr vec<3, T> operator+(vec<3, T> const& v1, vec<3, T> const& v2)
{
return vec<3, T>(v1.x + v2.x,
v1.y + v2.y,
v1.z + v2.z);
}
template <typename T>
inline constexpr vec<3, T> operator-(vec<3, T> const& v1, vec<3, T> const& v2)
{
return vec<3, T>(v1.x - v2.x,
v1.y - v2.y,
v1.z - v2.z);
}
template <typename T>
inline constexpr vec<3, T> operator*(vec<3, T> const& v1, vec<3, T> const& v2)
{
return vec<3, T>(v1.x * v2.x,
v1.y * v2.y,
v1.z * v2.z);
}
template <typename T>
inline constexpr vec<3, T> operator*(vec<3, T> const& v1, T const& scalar)
{
return v1 * vec<3, T>(scalar);
}
template <typename T>
inline constexpr T dot(vec<3, T> const& v1, vec<3, T> const& v2)
{
vec<3, T> tmp(v1 * v2);
return tmp.x + tmp.y + tmp.z;
}
template <typename T>
inline constexpr vec<3, T> functor1(T (&func) (T const& x), vec<3, T> const& v)
{
return vec<3, T>(func(v.x), func(v.y), func(v.z));
}
template <typename T, typename U>
inline constexpr vec<3, U> functor1(U (&func) (T const& x), vec<3, T> const& v)
{
return vec<3, U>(func(v.x), func(v.y), func(v.z));
}
template <typename T>
inline constexpr T length(vec<3, T> const& v)
{
return sqrt(dot(v, v));
}