From a34c49f103c8950a57687ea3d866c244d95b4414 Mon Sep 17 00:00:00 2001 From: hachem Date: Mon, 24 Aug 2026 18:28:39 +0200 Subject: [docs]: added a few comments cause i wont be able to read this in a week's time --- src/rendering/black_hole_renderer.cpp | 18 ++++++++--------- src/rendering/black_hole_renderer.h | 12 +++++------ src/rendering/export_config.h | 12 +++++------ src/rendering/render_api.h | 4 ++-- src/rendering/render_path.cpp | 18 ++++++++--------- src/rendering/render_path.h | 14 ++++++------- src/rendering/rhi.h | 38 +++++++++++++++++------------------ src/rendering/scene_renderer.cpp | 18 ++++++++--------- src/rendering/scene_renderer.h | 8 ++++---- src/rendering/view.h | 8 ++++---- 10 files changed, 75 insertions(+), 75 deletions(-) (limited to 'src/rendering') diff --git a/src/rendering/black_hole_renderer.cpp b/src/rendering/black_hole_renderer.cpp index 283435e..2310268 100644 --- a/src/rendering/black_hole_renderer.cpp +++ b/src/rendering/black_hole_renderer.cpp @@ -30,7 +30,7 @@ namespace Donut m_geo_lo = device.create_render_target(GEO_LO_W, GEO_LO_H, Format::RGBA8, Format::None, Filter::Linear); m_geo_hi = device.create_render_target(GEO_HI_W, GEO_HI_H, Format::RGBA8, Format::None, Filter::Linear); - // Fullscreen quad shared by the geodesic and present passes: pos.xy + uv. + // fullscreen quad shared by the geodesic and present passes: pos.xy + uv. const float quad[] = { -1.f, 1.f, 0.f, 1.f, -1.f, -1.f, 0.f, 0.f, 1.f, -1.f, 1.f, 0.f, -1.f, 1.f, 0.f, 1.f, 1.f, -1.f, 1.f, 0.f, 1.f, 1.f, 1.f, 1.f, @@ -42,7 +42,7 @@ namespace Donut m_obj_ubo = device.create_buffer(BufferType::Uniform, 800); m_sim_ubo = device.create_buffer(BufferType::Uniform, 16); - // The hole itself is the hardcoded singularity in the shader; the Objects + // the hole itself is the hardcoded singularity in the shader; the Objects // UBO carries the Scene's spheres, refilled each frame in render_geodesic. std::vector obj(800, 0); // numObjects = 0 until the first frame m_obj_ubo->update(obj.data(), obj.size()); @@ -85,13 +85,13 @@ namespace Donut { fill_uniforms(view, params, objects, 0, false); // channel 0 = colour, display - // Progressive resolution: small target while moving, large once settled. + // progressive resolution: small target while moving so it stays snappy, big one once it settles. so like, smooth now vs crisp later. RenderTarget* target = view.moving ? m_geo_lo.get() : m_geo_hi.get(); m_last_target = target; draw_geodesic(cmd, target, cubemap, m_geo_pipeline.get()); } - // Renders one channel (0 colour, 1 redshift, 2 temperature, 3 impact parameter) + // renders one channel (0 colour, 1 redshift, 2 temperature, 3 impact parameter) // into an arbitrary RGBA8 target — used by the exporter for high-res output. auto BlackHoleRenderer::render_export(RHI::CommandList& cmd, RHI::RenderTarget* target, const GeodesicView& view, const BlackHoleParams& params, @@ -114,9 +114,9 @@ namespace Donut cam.raw = raw ? 1 : 0; m_cam_ubo->update(&cam, sizeof(cam)); - // Same integration budget whether moving or settled (the disk vanishes at + // same integration budget whether moving or settled (the disk vanishes at // steep poses below ~15000 steps); responsiveness comes from the lower-res - // target instead. Sourced from the UI, clamped GPU-safe. + // target instead. sourced from the UI, clamped GPU-safe. constexpr int kStepCeil = 15000; SimUBO sim{}; sim.steps_static = std::clamp(params.quality_steps, 1000, kStepCeil); @@ -125,7 +125,7 @@ namespace Donut sim.time = view.time; m_sim_ubo->update(&sim, sizeof(sim)); - // Disk struct: r_in, r_out, turbulence, slab half-thickness, brightness, + // disk struct: r_in, r_out, turbulence, slab half-thickness, brightness, // temperature (radii in Schwarzschild radii). float disk[8] = { std::max(params.disk_inner_rs, 3.0f) * SagA_rs, @@ -138,8 +138,8 @@ namespace Donut }; m_disk_ubo->update(disk, sizeof(disk)); - // Scene objects → the geodesic's Objects UBO (std140: numObjects@0, - // objPosRadius[i]@16+16i, objColor[i]@272+16i). They render as spheres the + // scene objects → the geodesic's Objects UBO (std140: numObjects@0, + // objPosRadius[i]@16+16i, objColor[i]@272+16i). they render as spheres the // curved rays intersect, so the hole lenses them. 1 scene unit = 1 r_s. const float k = SagA_rs / SCENE_UNITS_PER_RS; // scene units -> metres std::vector objbuf(800, 0); diff --git a/src/rendering/black_hole_renderer.h b/src/rendering/black_hole_renderer.h index c2cb2af..17c4254 100644 --- a/src/rendering/black_hole_renderer.h +++ b/src/rendering/black_hole_renderer.h @@ -7,10 +7,10 @@ namespace Donut { - // The black-hole view, written ONCE against the RHI. Each frame it ray-traces + // the black-hole view, written ONCE against the RHI. each frame it ray-traces // the geodesic shader into an off-screen target (progressive resolution: a // small target while the camera moves, a larger one once it settles) and then - // upscales that target onto the swapchain. The Application owns the camera and + // upscales that target onto the swapchain. the Application owns the camera and // the HDRI cubemap and passes them in. struct GeodesicView { @@ -29,19 +29,19 @@ namespace Donut auto init(RHI::Device& device) -> bool; - // Off-screen geodesic pass (opens/closes its own render pass). Call before + // off-screen geodesic pass (opens/closes its own render pass). call before // the swapchain pass; `cubemap` is the shared HDRI environment. `objects` // are the Scene's spheres — ray-traced as lensed spheres around the hole - // (1 scene unit = 1 Schwarzschild radius). Grid units == r_s. + // (1 scene unit = 1 Schwarzschild radius). grid units == r_s. auto render_geodesic(RHI::CommandList& cmd, const GeodesicView& view, const BlackHoleParams& params, const std::vector& objects, RHI::Texture* cubemap) -> void; - // Upscales the geodesic target onto the swapchain (call inside the open + // upscales the geodesic target onto the swapchain (call inside the open // swapchain render pass, before ImGui). auto blit(RHI::CommandList& cmd, int fb_width, int fb_height) -> void; - // Renders one channel (0 colour, 1 redshift g, 2 emission T, 3 impact + // renders one channel (0 colour, 1 redshift g, 2 emission T, 3 impact // parameter) into an arbitrary RGBA8 target — for high-res export. auto render_export(RHI::CommandList& cmd, RHI::RenderTarget* target, const GeodesicView& view, const BlackHoleParams& params, diff --git a/src/rendering/export_config.h b/src/rendering/export_config.h index e490db6..9b91878 100644 --- a/src/rendering/export_config.h +++ b/src/rendering/export_config.h @@ -4,15 +4,15 @@ namespace Donut { - // How each channel is written: - // Png = display image (tone-mapped colour / false-coloured observable). - // Pfm = raw float image (actual physical values; RGB, Portable Float Map). - // Csv = raw scalar grid (the observable's value per pixel, one row per line). + // how each channel is written: + // png = display image (tone-mapped colour / false-coloured observable). + // pfm = raw float image (actual physical values; RGB, Portable Float Map). + // csv = raw scalar grid (the observable's value per pixel, one row per line). enum class ExportFormat { Png, Pfm, Csv }; - // What the Export workspace asks the RenderPath to write to disk: which + // what the Export panel asks the RenderPath to write to disk: which // observable channels to render, at what resolution, in what format, and where. - // Each enabled channel becomes one file. + // each enabled channel becomes one file. struct ExportConfig { bool color = true; // colour image (tone-mapped, or linear HDR when raw) diff --git a/src/rendering/render_api.h b/src/rendering/render_api.h index 62742c9..224c1fa 100644 --- a/src/rendering/render_api.h +++ b/src/rendering/render_api.h @@ -2,8 +2,8 @@ namespace Donut { - // Which graphics backend the app runs on, selected once at startup (from - // settings) before the window is created. The rendering itself goes through + // which graphics backend the app runs on, selected once at startup (from + // settings) before the window is created. the rendering itself goes through // the RHI (rendering/rhi.h) and the shared renderers; this is only the // backend selector the window + application branch on. class RendererAPI diff --git a/src/rendering/render_path.cpp b/src/rendering/render_path.cpp index 8cb0328..dfd2937 100644 --- a/src/rendering/render_path.cpp +++ b/src/rendering/render_path.cpp @@ -42,15 +42,15 @@ namespace Donut { const glm::vec4 clear(0.05f, 0.06f, 0.10f, 1.0f); - // The black hole is the only view with an off-screen pass; it renders (and - // runs the expensive geodesic) ONLY on the Simulation tab. Scene and None - // are a single swapchain pass — None draws nothing (empty viewport). + // the black hole is the only view with an off-screen pass; it renders (and + // runs the expensive geodesic) ONLY when the view is Simulation. scene and + // none are a single swapchain pass — none draws nothing (empty viewport). if (view != View::BlackHole) { cmd.begin_render_pass(nullptr, clear); if (view == View::Scene) { - // Projection was set by the Application this frame (shared with the gizmo). + // projection was set by the Application this frame (shared with the gizmo). CameraView cv; cv.view = scene.scene_camera.get_view_matrix(); cv.projection = scene.scene_camera.get_projection_matrix(); @@ -95,11 +95,11 @@ namespace Donut m_scene_renderer.reset(); m_black_hole_renderer.reset(); m_cubemap.reset(); - // Backend-specific HDRI/GPU resource cleanup is the device's job (see e.g. + // backend-specific HDRI/GPU resource cleanup is the device's job (see e.g. // the OpenGL device clearing the HDRIManager texture cache on shutdown). } - // Portable Float Map: a raw RGB float image (the actual physical values). Its + // Portable Float Map: a raw RGB float image (the actual physical values). its // raster is bottom-up; -1.0 scale flags little-endian. static auto write_pfm(const std::string& path, int w, int h, const std::vector& rgba) -> bool { @@ -135,11 +135,11 @@ namespace Donut { if (!m_device) return 0; const int w = std::max(cfg.width, 1), h = std::max(cfg.height, 1); - const bool raw = cfg.format != ExportFormat::Png; // Pfm / Csv want physical values + const bool raw = cfg.format != ExportFormat::Png; // pfm / csv want physical values auto target = m_device->create_render_target( w, h, raw ? RHI::Format::RGBA32F : RHI::Format::RGBA8, RHI::Format::None, RHI::Filter::Nearest); - // Full-quality geodesic view from the sim camera (moving = false = settled). + // full-quality geodesic view from the sim camera (moving = false = settled). GeodesicView gv; glm::vec3 pos, fwd; if (scene.sim_camera.get_camera_mode() == CameraMode::FPS) @@ -153,7 +153,7 @@ namespace Donut gv.moving = false; gv.time = 0.0f; - std::error_code ec; std::filesystem::create_directories(cfg.directory, ec); + std::error_code ec; std::filesystem::create_directories(cfg.directory, ec); // lowk dc, the write below complains if it mattered char stamp[32]; std::time_t t = std::time(nullptr); std::strftime(stamp, sizeof(stamp), "%Y%m%d_%H%M%S", std::localtime(&t)); const char* ext = cfg.format == ExportFormat::Pfm ? "pfm" : cfg.format == ExportFormat::Csv ? "csv" : "png"; diff --git a/src/rendering/render_path.h b/src/rendering/render_path.h index c029132..e5e8bb6 100644 --- a/src/rendering/render_path.h +++ b/src/rendering/render_path.h @@ -13,26 +13,26 @@ namespace Donut { class Scene; - // Owns the device-side rendering: the two portable renderers, the shared HDRI + // owns the device-side rendering: the two portable renderers, the shared HDRI // cubemap, and the per-frame composition (off-screen geodesic pass -> present, - // or the scene pass), with ImGui drawn on top inside the swapchain pass. The - // Application hands it a Scene + View each frame; it knows nothing about the UI. + // or the scene pass), with ImGui drawn on top inside the swapchain pass. the + // application hands it a Scene + View each frame; it knows nothing about the UI. class RenderPath { public: auto init(RHI::Device& device, const std::string& hdri_path) -> bool; auto shutdown() -> void; - // Rebuilds the environment cubemap if the path changed (device-idle). + // rebuilds the environment cubemap if the path changed (device-idle). auto sync_hdri(const std::string& hdri_path) -> void; - // Composes one frame's viewport into the swapchain command list. `moving` + // composes one frame's viewport into the swapchain command list. `moving` // drives the black hole's progressive resolution; `time` animates the disk. auto render(RHI::CommandList& cmd, Scene& scene, View view, int fb_width, int fb_height, bool moving, float time) -> void; - // Renders the requested export channels off-screen at cfg.width x cfg.height - // and writes one PNG per channel. Returns the number of files written. + // renders the requested export channels off-screen at cfg.width x cfg.height + // and writes one PNG per channel. returns the number of files written. auto export_frame(Scene& scene, const ExportConfig& cfg) -> int; private: diff --git a/src/rendering/rhi.h b/src/rendering/rhi.h index 6a2042a..a92d65e 100644 --- a/src/rendering/rhi.h +++ b/src/rendering/rhi.h @@ -3,7 +3,7 @@ // Donut RHI (Render Hardware Interface): a small, portable GPU abstraction that // OpenGL, Vulkan (and later Metal / D3D12) implement behind ONE interface, so the // app's rendering — the black hole, the scene — is written ONCE on top and runs -// on any backend. The shape is modelled on the explicit APIs (baked pipelines, +// on any backend. the shape is modelled on the explicit APIs (baked pipelines, // recorded command lists, explicit render targets); OpenGL emulates that, which // is easy, whereas the reverse (making Vulkan speak GL's immediate mode) is not. @@ -26,8 +26,8 @@ namespace Donut::RHI D32, // 32-bit depth }; - // The attachment signature of a render pass / target: a colour format plus an - // optional depth format. A pipeline is compatible with any target sharing this + // the attachment signature of a render pass / target: a colour format plus an + // optional depth format. a pipeline is compatible with any target sharing this // signature, so this is what replaces "which render pass" as an explicit value. struct RenderTargetFormat { @@ -35,9 +35,9 @@ namespace Donut::RHI Format depth = Format::None; }; - // The window the device presents into. GLFW is the windowing layer for every + // the window the device presents into. GLFW is the windowing layer for every // backend, so this carries the GLFWwindow*; each backend derives what it needs - // (a GL context, a Vulkan surface, later a CAMetalLayer / HWND) from it. The + // (a GL context, a Vulkan surface, later a CAMetalLayer / HWND) from it. the // RHI names the concept "native window" rather than baking GLFW into its API. struct NativeWindow { @@ -53,23 +53,23 @@ namespace Donut::RHI enum class CompareOp { Always, Less, LessEqual }; enum class Filter { Nearest, Linear }; - // One vertex attribute; offsets/stride are in bytes. `components` is 1..4 floats. + // one vertex attribute; offsets/stride are in bytes. `components` is 1..4 floats. struct VertexAttribute { uint32_t location; uint32_t components; uint32_t offset; }; struct VertexLayout { uint32_t stride = 0; std::vector attributes; }; - // A shader resource slot the pipeline exposes. `binding` is the set-0 binding + // a shader resource slot the pipeline exposes. `binding` is the set-0 binding // index used by Vulkan; `name` is the GLSL block/sampler identifier used by the - // OpenGL backend (GL 4.1 has no binding qualifier, so it binds by name). The app + // OpenGL backend (GL 4.1 has no binding qualifier, so it binds by name). the app // declares both when building a pipeline (it knows its own shader). enum class ResourceKind { UniformBuffer, Texture }; struct ResourceSlot { ResourceKind kind; uint32_t binding; std::string name; }; - // --- opaque GPU resources (backends subclass) -------------------------- + // opaque GPU resources; each backend subclasses these. class Buffer { public: virtual ~Buffer() = default; virtual auto update(const void* data, size_t size) -> void = 0; }; class Texture { public: virtual ~Texture() = default; }; class Pipeline{ public: virtual ~Pipeline() = default; }; - // An off-screen target (colour, optional depth). The swapchain is the implicit + // an off-screen target (colour, optional depth). the swapchain is the implicit // default target, addressed by passing nullptr to begin_render_pass. class RenderTarget { @@ -94,7 +94,7 @@ namespace Donut::RHI RenderTargetFormat target; // attachment signature of the pass it draws into }; - // Records draws for one frame. Obtained from Device::begin_frame (targets the + // records draws for one frame. obtained from Device::begin_frame (targets the // swapchain) or created transiently for off-screen passes via the Device. class CommandList { @@ -118,7 +118,7 @@ namespace Donut::RHI virtual auto draw_indexed(uint32_t index_count) -> void = 0; }; - // The backend root: owns the device/swapchain and creates every resource. + // the backend root: owns the device/swapchain and creates every resource. class Device { public: @@ -129,8 +129,8 @@ namespace Donut::RHI virtual auto resize(int width, int height) -> void = 0; virtual auto wait_idle() -> void = 0; - // Present pacing: GL toggles the swap interval; Vulkan swaps the present - // mode (FIFO vs IMMEDIATE) and rebuilds the swapchain. Default: no-op. + // present pacing: GL toggles the swap interval; Vulkan swaps the present + // mode (FIFO vs IMMEDIATE) and rebuilds the swapchain. default: no-op. virtual auto set_vsync(bool enabled) -> void { (void)enabled; } virtual auto create_buffer(BufferType type, size_t size, const void* data = nullptr) -> Ref = 0; @@ -140,23 +140,23 @@ namespace Donut::RHI Filter sample_filter = Filter::Linear, int mip_levels = 1) -> Ref = 0; virtual auto create_pipeline(const PipelineDesc& desc) -> Ref = 0; - // Frame loop: begin_frame returns the frame's command list (or nullptr if + // frame loop: begin_frame returns the frame's command list (or nullptr if // the frame is skipped, e.g. minimised); record one or more render passes // into it — off-screen passes into RenderTargets first, then the swapchain // pass (begin_render_pass(nullptr, ...)) — then end_frame submits + presents. virtual auto begin_frame(const glm::vec4& clear) -> CommandList* = 0; virtual auto end_frame() -> void = 0; - // One-shot off-screen work outside the frame loop (used by the exporter): + // one-shot off-screen work outside the frame loop (used by the exporter): // records a self-contained pass into a transient command list and blocks // until the GPU finishes, so the target can be read back immediately. virtual auto run_offscreen(const std::function& record) -> void = 0; - // Reads a render target's colour back to the CPU as tightly-packed RGBA8, - // top-left origin (out is resized to width*height*4). For export/analysis. + // reads a render target's colour back to the CPU as tightly-packed RGBA8, + // top-left origin (out is resized to width*height*4). for export/analysis. virtual auto read_render_target(RenderTarget* target, std::vector& out) -> void = 0; - // Same, but as full-float RGBA (out resized to width*height*4 floats). Read + // same, but as full-float RGBA (out resized to width*height*4 floats). read // an RGBA32F target back for raw observable export. virtual auto read_render_target_float(RenderTarget* target, std::vector& out) -> void = 0; diff --git a/src/rendering/scene_renderer.cpp b/src/rendering/scene_renderer.cpp index bec609c..e290445 100644 --- a/src/rendering/scene_renderer.cpp +++ b/src/rendering/scene_renderer.cpp @@ -43,7 +43,7 @@ namespace Donut struct SkyboxUBO { glm::mat4 projection; glm::mat4 view; }; static_assert(sizeof(SkyboxUBO) == 128, "SkyboxUBO std140 layout mismatch"); - // The hole's event horizon (1 r_s) as a marker at the origin. + // the hole's event horizon (1 r_s) as a marker at the origin. constexpr float BH_SCENE_RADIUS = SCENE_UNITS_PER_RS; } @@ -51,7 +51,7 @@ namespace Donut { m_device = &device; - // Reference grid: line list on the XZ plane (+/-50, 1-unit cells). Grid.slang + // reference grid: line list on the XZ plane (+/-50, 1-unit cells). grid.slang // scales by u_GridSize/50, so u_GridSize = 50 keeps it 1:1. { std::vector lines; @@ -78,7 +78,7 @@ namespace Donut m_grid_pipeline = device.create_pipeline(d); } - // Lit sphere: unit UV-sphere (pos + normal), placed/scaled per object. + // lit sphere: unit UV-sphere (pos + normal), placed/scaled per object. { std::vector sv; std::vector si; const int RINGS = 24, SECTORS = 48; @@ -115,7 +115,7 @@ namespace Donut m_sphere_pipeline = device.create_pipeline(d); } - // Skybox: a unit cube (36 verts) sampling the HDRI cubemap; the vertex + // skybox: a unit cube (36 verts) sampling the HDRI cubemap; the vertex // shader forces depth 1 (pos.xyww) so it sits behind all scene geometry. { const float cube[] = { @@ -151,13 +151,13 @@ namespace Donut const std::vector& objects, int selected, RHI::Texture* cubemap) -> void { - // Full-window viewport; flip_y lets the backend match the shared top-left + // full-window viewport; flip_y lets the backend match the shared top-left // orientation (Vulkan flips via a negative-height viewport, GL is a no-op). cmd.set_viewport(0, 0, cam.fb_width, cam.fb_height, true); glm::mat4 vp = cam.projection * cam.view; - // Skybox background first (depth 1, no depth test/write). + // skybox background first (depth 1, no depth test/write). { SkyboxUBO sky{}; sky.projection = cam.projection; @@ -170,7 +170,7 @@ namespace Donut cmd.draw(36); } - // Opaque spheres (write depth). One UBO per object slot avoids aliasing the + // opaque spheres (write depth). one UBO per object slot avoids aliasing the // per-draw uniforms across the deferred command stream. { int count = std::min((int)objects.size(), MAX_OBJECTS); @@ -202,7 +202,7 @@ namespace Donut cmd.draw_indexed(m_sphere_index_count); } - // Black-hole marker: a near-black sphere at the origin (radius = r_s in + // black-hole marker: a near-black sphere at the origin (radius = r_s in // scene units), so the Scene shows where the hole the Simulation renders // sits. 1 grid unit == 1 Schwarzschild radius. SphereUBO bh{}; @@ -223,7 +223,7 @@ namespace Donut cmd.draw_indexed(m_sphere_index_count); } - // Transparent grid on top (tests depth, doesn't write). + // transparent grid on top (tests depth, doesn't write). { GridUBO g{}; g.view_projection = vp; diff --git a/src/rendering/scene_renderer.h b/src/rendering/scene_renderer.h index f7ff992..69ab2bc 100644 --- a/src/rendering/scene_renderer.h +++ b/src/rendering/scene_renderer.h @@ -7,9 +7,9 @@ namespace Donut { - // The world-builder scene view (skybox + lit spheres + reference grid), + // the world-builder scene view (skybox + lit spheres + reference grid), // written ONCE against the RHI so it renders identically on every backend. - // The Application owns the camera, the object list and the HDRI cubemap and + // the Application owns the camera, the object list and the HDRI cubemap and // passes them in each frame; this class owns only the GPU resources (meshes, // pipelines, per-object uniform buffers) and records the draws. struct CameraView @@ -24,10 +24,10 @@ namespace Donut class SceneRenderer { public: - // Builds the grid/sphere/skybox meshes and pipelines on the given device. + // builds the grid/sphere/skybox meshes and pipelines on the given device. auto init(RHI::Device& device) -> bool; - // Records the scene into the currently-open swapchain render pass. `cubemap` + // records the scene into the currently-open swapchain render pass. `cubemap` // is the shared HDRI environment (sphere ambient + skybox background). auto render(RHI::CommandList& cmd, const CameraView& cam, const std::vector& objects, int selected, diff --git a/src/rendering/view.h b/src/rendering/view.h index 0ac6ae0..1139676 100644 --- a/src/rendering/view.h +++ b/src/rendering/view.h @@ -2,12 +2,12 @@ namespace Donut { - // What the RenderPath draws in the viewport this frame. Each UI workspace - // (tab) declares which one it wants, so switching tabs switches the view. + // what the RenderPath draws in the viewport this frame. the UI's View menu + // (or a layout preset) picks it; the RenderPath branches on the result. enum class View { - None, // empty viewport (tabs that aren't a live view, e.g. Setup / Export) + None, // empty viewport (no live 3D; not currently produced by the UI) Scene, // world-builder: grid + placed objects + skybox - BlackHole, // the ray-traced accretion-disk simulation (only the Simulation tab) + BlackHole, // the ray-traced accretion-disk simulation (Simulation view only) }; } -- cgit v1.3