diff --git a/.github/workflows/deploy-github-page.yml b/.github/workflows/deploy-github-page.yml index d7753af8..ec79b4e3 100644 --- a/.github/workflows/deploy-github-page.yml +++ b/.github/workflows/deploy-github-page.yml @@ -72,6 +72,7 @@ jobs: --XeusAddon.prefix=${{ env.PREFIX }} \ --contents README.md \ --contents notebooks/xeus-cpp-lite-demo.ipynb \ + --contents notebooks/smallpt.ipynb \ --contents notebooks/images/marie.png \ --contents notebooks/audio/audio.wav \ --output-dir dist diff --git a/CMakeLists.txt b/CMakeLists.txt index 39798ec2..0dcbc421 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -445,8 +445,9 @@ if(EMSCRIPTEN) # TODO: Remove the exported runtime methods # after the next xeus release. target_link_options(xcpp + PUBLIC "SHELL: -s USE_SDL=2" PUBLIC "SHELL: -s EXPORTED_RUNTIME_METHODS='[\"FS\",\"PATH\",\"LDSO\",\"loadDynamicLibrary\",\"ERRNO_CODES\"]'" - PUBLIC "SHELL: --preload-file ${SYSROOT_PATH}/include@/include" + PUBLIC "SHELL: --preload-file ${SYSROOT_PATH}/@/" PUBLIC "SHELL: --post-js ${CMAKE_CURRENT_SOURCE_DIR}/wasm_patches/post.js" ) # TODO: Emscripten supports preloading files just once before it generates diff --git a/notebooks/smallpt.ipynb b/notebooks/smallpt.ipynb new file mode 100644 index 00000000..34d03ffe --- /dev/null +++ b/notebooks/smallpt.ipynb @@ -0,0 +1,359 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "72cd5c0c-3a52-43a5-a714-0baee1f7464c", + "metadata": { + "trusted": true, + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "// Adapted from smallpt, a Path Tracer by Kevin Beason, 2008\n", + "#include \n", + "#include \n", + "#include \n", + "\n", + "struct Vec {\n", + " double x, y, z; // position, also color (r,g,b)\n", + " Vec(double x_ = 0, double y_ = 0, double z_ = 0) {\n", + " x = x_;\n", + " y = y_;\n", + " z = z_;\n", + " }\n", + " Vec operator+(const Vec &b) const { return Vec(x + b.x, y + b.y, z + b.z); }\n", + " Vec operator-(const Vec &b) const { return Vec(x - b.x, y - b.y, z - b.z); }\n", + " Vec operator*(double b) const { return Vec(x * b, y * b, z * b); }\n", + " Vec mult(const Vec &b) const { return Vec(x * b.x, y * b.y, z * b.z); }\n", + " Vec &norm() { return *this = *this * (1 / sqrt(x * x + y * y + z * z)); }\n", + " double dot(const Vec &b) const { return x * b.x + y * b.y + z * b.z; }\n", + " Vec operator%(Vec &b) { // cross\n", + " return Vec(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x);\n", + " }\n", + "};\n", + "struct Ray {\n", + " Vec o, d;\n", + " Ray(Vec o_, Vec d_) : o(o_), d(d_) {}\n", + "};\n", + "enum Refl_t { DIFF, SPEC, REFR }; // material types, used in radiance()\n", + "struct Sphere {\n", + " double rad; // radius\n", + " Vec p, e, c; // position, emission, color\n", + " Refl_t refl; // reflection type (DIFFuse, SPECular, REFRactive)\n", + " Sphere(double rad_, Vec p_, Vec e_, Vec c_, Refl_t refl_)\n", + " : rad(rad_), p(p_), e(e_), c(c_), refl(refl_) {}\n", + " double intersect(const Ray &r) const { // returns distance, 0 if nohit\n", + " Vec op = p - r.o; // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0\n", + " double t, eps = 1e-4, b = op.dot(r.d), det = b * b - op.dot(op) + rad * rad;\n", + " if (det < 0)\n", + " return 0;\n", + " else\n", + " det = sqrt(det);\n", + " return (t = b - det) > eps ? t : ((t = b + det) > eps ? t : 0);\n", + " }\n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "acc1ec30-5f0a-446f-a066-3dc3369ed39e", + "metadata": { + "trusted": true, + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "std::vector spheres = {\n", + " // Scene: radius, position, emission, color, material\n", + " Sphere(1e5, Vec(1e5 + 1, 40.8, 81.6), Vec(), Vec(.75, .25, .25),\n", + " DIFF), // Left\n", + " Sphere(1e5, Vec(-1e5 + 99, 40.8, 81.6), Vec(), Vec(.25, .25, .75),\n", + " DIFF), // Rght\n", + " Sphere(1e5, Vec(50, 40.8, 1e5), Vec(), Vec(.75, .75, .75), DIFF), // Back\n", + " Sphere(1e5, Vec(50, 40.8, -1e5 + 170), Vec(), Vec(), DIFF), // Frnt\n", + " Sphere(1e5, Vec(50, 1e5, 81.6), Vec(), Vec(.75, .75, .75), DIFF), // Botm\n", + " Sphere(1e5, Vec(50, -1e5 + 81.6, 81.6), Vec(), Vec(.75, .75, .75),\n", + " DIFF), // Top\n", + " Sphere(16.5, Vec(27, 16.5, 47), Vec(), Vec(1, 1, 1) * .999, SPEC), // Mirr\n", + " Sphere(16.5, Vec(73, 16.5, 78), Vec(), Vec(1, 1, 1) * .999, REFR), // Glas\n", + " Sphere(600, Vec(50, 681.6 - .27, 81.6), Vec(12, 12, 12), Vec(),\n", + " DIFF) // Lite\n", + "};\n", + "\n", + "inline double clamp(double x) { return x < 0 ? 0 : x > 1 ? 1 : x; }\n", + "inline int toInt(double x) { return int(pow(clamp(x), 1 / 2.2) * 255 + .5); }\n", + "inline bool intersect(const Ray &r, double &t, int &id) {\n", + " double n = spheres.size(), d, inf = t = 1e20;\n", + " for (int i = int(n); i--;)\n", + " if ((d = spheres[i].intersect(r)) && d < t) {\n", + " t = d;\n", + " id = i;\n", + " }\n", + " return t < inf;\n", + "}\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cf0fd047-c5da-4606-aeae-4358d2064f10", + "metadata": { + "trusted": true, + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "Vec radiance(const Ray &r, int depth, unsigned short *Xi) {\n", + " double t; // distance to intersection\n", + " int id = 0; // id of intersected object\n", + " if (!intersect(r, t, id))\n", + " return Vec(); // if miss, return black\n", + " const Sphere &obj = spheres[id]; // the hit object\n", + " Vec x = r.o + r.d * t, n = (x - obj.p).norm(),\n", + " nl = n.dot(r.d) < 0 ? n : n * -1, f = obj.c;\n", + " double p = f.x > f.y && f.x > f.z ? f.x : f.y > f.z ? f.y : f.z; // max refl\n", + " if (++depth > 5) {\n", + " if (erand48(Xi) < p) {\n", + " f = f * (1 / p);\n", + " } else {\n", + " return obj.e; // R.R.\n", + " }\n", + " }\n", + " if (obj.refl == DIFF) { // Ideal DIFFUSE reflection\n", + " double r1 = 2 * M_PI * erand48(Xi), r2 = erand48(Xi), r2s = sqrt(r2);\n", + " Vec w = nl, u = ((fabs(w.x) > .1 ? Vec(0, 1) : Vec(1)) % w).norm(),\n", + " v = w % u;\n", + " Vec d = (u * cos(r1) * r2s + v * sin(r1) * r2s + w * sqrt(1 - r2)).norm();\n", + " return obj.e + f.mult(radiance(Ray(x, d), depth, Xi));\n", + " } else if (obj.refl == SPEC) // Ideal SPECULAR reflection\n", + " return obj.e +\n", + " f.mult(radiance(Ray(x, r.d - n * 2 * n.dot(r.d)), depth, Xi));\n", + " Ray reflRay(x, r.d - n * 2 * n.dot(r.d)); // Ideal dielectric REFRACTION\n", + " bool into = n.dot(nl) > 0; // Ray from outside going in?\n", + " double nc = 1, nt = 1.5, nnt = into ? nc / nt : nt / nc, ddn = r.d.dot(nl),\n", + " cos2t;\n", + " if ((cos2t = 1 - nnt * nnt * (1 - ddn * ddn)) <\n", + " 0) // Total internal reflection\n", + " return obj.e + f.mult(radiance(reflRay, depth, Xi));\n", + " Vec tdir =\n", + " (r.d * nnt - n * ((into ? 1 : -1) * (ddn * nnt + sqrt(cos2t)))).norm();\n", + " double a = nt - nc, b = nt + nc, R0 = a * a / (b * b),\n", + " c = 1 - (into ? -ddn : tdir.dot(n));\n", + " double Re = R0 + (1 - R0) * c * c * c * c * c, Tr = 1 - Re, P = .25 + .5 * Re,\n", + " RP = Re / P, TP = Tr / (1 - P);\n", + " return obj.e +\n", + " f.mult(depth > 2\n", + " ? (erand48(Xi) < P ? // Russian roulette\n", + " radiance(reflRay, depth, Xi) * RP\n", + " : radiance(Ray(x, tdir), depth, Xi) * TP)\n", + " : radiance(reflRay, depth, Xi) * Re +\n", + " radiance(Ray(x, tdir), depth, Xi) * Tr);\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "70a01b15-3b76-4008-b674-0cc05b0342dd", + "metadata": { + "trusted": true, + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "#include \n", + "#include \n", + "#include \n", + "#include \n", + "#include \n", + "#include \"nlohmann/json.hpp\"\n", + "#include \"xeus/xbase64.hpp\"\n", + "#include \"xcpp/xdisplay.hpp\"\n", + "\n", + "namespace nl = nlohmann;\n", + "\n", + "namespace im\n", + "{\n", + " struct image\n", + " { \n", + " inline image(const std::string& filename)\n", + " {\n", + " std::ifstream fin(filename, std::ios::binary); \n", + " m_buffer << fin.rdbuf();\n", + " }\n", + " \n", + " std::stringstream m_buffer;\n", + " };\n", + " \n", + " nl::json mime_bundle_repr(const image& i)\n", + " {\n", + " auto bundle = nl::json::object();\n", + " bundle[\"image/bmp\"] = xeus::base64encode(i.m_buffer.str());\n", + " return bundle;\n", + " }\n", + "}\n", + "\n", + "// Function to save SDL surface as BMP persistently\n", + "void save_bmp_to_filesystem(SDL_Surface* surface, const std::string& filename)\n", + "{\n", + " if (surface)\n", + " {\n", + " if (SDL_SaveBMP(surface, filename.c_str()) == 0)\n", + " {\n", + " std::cout << \"[DEBUG] Surface saved to \" << filename << std::endl;\n", + " }\n", + " else\n", + " {\n", + " std::cerr << \"[ERROR] Failed to save BMP to \" << filename << \": \" << SDL_GetError() << std::endl;\n", + " }\n", + " }\n", + " else\n", + " {\n", + " std::cerr << \"[ERROR] Surface is null, cannot save BMP.\" << std::endl;\n", + " }\n", + "}\n", + "\n", + "// Function to encode an SDL surface and render it in Jupyter\n", + "void render_sdl_surface_to_jupyter(SDL_Surface* surface, const std::string& filename)\n", + "{\n", + "\n", + " if (!surface)\n", + " {\n", + " std::cerr << \"[ERROR] Surface is null\" << std::endl;\n", + " return;\n", + " }\n", + "\n", + " std::cout << \"[DEBUG] Surface created successfully\" << std::endl;\n", + "\n", + " im::image output(filename);\n", + " xcpp::display(output);\n", + " \n", + " // Cleanup\n", + " SDL_FreeSurface(surface);\n", + " std::cout << \"[DEBUG] Surface freed\" << std::endl;\n", + " remove(filename.c_str());\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fd6e42b-6e84-46e5-9197-0e0c2e054ad1", + "metadata": { + "trusted": true, + "vscode": { + "languageId": "c++" + } + }, + "outputs": [], + "source": [ + "int main() {\n", + " // Debug: Initialization\n", + " std::cout << \"[DEBUG] Initializing SDL\" << std::endl;\n", + "\n", + " if (SDL_Init(SDL_INIT_EVENTS) != 0) {\n", + " std::cerr << \"[ERROR] SDL_Init failed: \" << SDL_GetError() << std::endl;\n", + " return 1;\n", + " }\n", + " std::cout << \"[DEBUG] SDL initialized successfully\" << std::endl;\n", + "\n", + " // Define width, height, and samples\n", + " int w = 320, h = 240, samps = 16; // # samples\n", + " std::cout << \"[DEBUG] Dimensions: \" << w << \"x\" << h << \", Samples: \" << samps << std::endl;\n", + "\n", + " // Create an off-screen surface\n", + " SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_RGBA32);\n", + " if (!surface) {\n", + " std::cerr << \"[ERROR] Failed to create SDL surface: \" << SDL_GetError() << std::endl;\n", + " SDL_Quit();\n", + " return 1;\n", + " }\n", + " std::cout << \"[DEBUG] SDL surface created successfully\" << std::endl;\n", + "\n", + " // Prepare the camera and pixel buffer\n", + " Ray cam(Vec(50, 52, 295.6), Vec(0, -0.042612, -1).norm()); // cam pos, dir\n", + " Vec cx = Vec(w * .5135 / h), cy = (cx % cam.d).norm() * .5135, r,\n", + " *c = new Vec[w * h];\n", + "\n", + " std::cout << \"[DEBUG] The image should be on your screen soon\" << std::endl;\n", + " // Render the scene\n", + " for (int y = 0; y < h; y++) { // Loop over image rows\n", + " unsigned short x, Xi[3] = {0, 0, static_cast(y * y * y)};\n", + " for (x = 0; x < w; x++) { // Loop cols\n", + " int sy, i = (h - y - 1) * w + x;\n", + " for (sy = 0; sy < 2; sy++) { // 2x2 subpixel rows\n", + " int sx;\n", + " for (sx = 0; sx < 2; sx++, r = Vec()) { // 2x2 subpixel cols\n", + " for (int s = 0; s < samps; s++) { // Subpixel sampling\n", + " double r1 = 2 * erand48(Xi),\n", + " dx = r1 < 1 ? sqrt(r1) - 1 : 1 - sqrt(2 - r1);\n", + " double r2 = 2 * erand48(Xi),\n", + " dy = r2 < 1 ? sqrt(r2) - 1 : 1 - sqrt(2 - r2);\n", + " Vec d = cx * (((sx + .5 + dx) / 2 + x) / w - .5) +\n", + " cy * (((sy + .5 + dy) / 2 + y) / h - .5) + cam.d;\n", + " r = r + radiance(Ray(cam.o + d * 140, d.norm()), 0, Xi) * (1. / samps);\n", + " }\n", + " c[i] = c[i] + Vec(clamp(r.x), clamp(r.y), clamp(r.z)) * .25;\n", + " }\n", + " }\n", + " }\n", + " }\n", + "\n", + " // Map the pixel buffer to the SDL surface\n", + " uint8_t* pixels = (uint8_t*)surface->pixels;\n", + " for (int i = 0; i < w * h; i++) {\n", + " uint32_t* pixel_ptr = (uint32_t*)pixels + i;\n", + " *pixel_ptr = SDL_MapRGBA(surface->format, \n", + " toInt(c[i].x), // Red\n", + " toInt(c[i].y), // Green\n", + " toInt(c[i].z), // Blue\n", + " 255); // Alpha\n", + " }\n", + " std::cout << \"[DEBUG] Mapped pixel buffer to surface\" << std::endl;\n", + "\n", + " // Save the BMP to the filesystem\n", + " const std::string bmp_filename = \"render.bmp\";\n", + " save_bmp_to_filesystem(surface, bmp_filename);\n", + "\n", + " // Render the surface to Jupyter\n", + " render_sdl_surface_to_jupyter(surface, bmp_filename);\n", + "\n", + " // Cleanup\n", + " delete[] c;\n", + " SDL_Quit();\n", + " std::cout << \"[DEBUG] Exiting main\" << std::endl;\n", + "\n", + " return 0;\n", + "}\n", + "\n", + "main();" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "C++20", + "language": "cpp", + "name": "xcpp20" + }, + "language_info": { + "codemirror_mode": "text/x-c++src", + "file_extension": ".cpp", + "mimetype": "text/x-c++src", + "name": "C++", + "version": "20" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}