render/renderer.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file renderer.cpp | ||
| 3 | /// @brief Rendering a scene's render settings: the device scene brought up to date, the camera | ||
| 4 | /// traced into a column, and the column read back. | ||
| 5 | #include "ernie/render/renderer.h" | ||
| 6 | |||
| 7 | #include "ernie/device/bindless.h" | ||
| 8 | #include "ernie/device/context.h" | ||
| 9 | #include "ernie/device/staging.h" | ||
| 10 | #include "ernie/render/device_scene.h" | ||
| 11 | #include "ernie/render/rt/camera_pass.h" | ||
| 12 | #include "ernie/scene/schema/camera.h" | ||
| 13 | #include "ernie/scene/schema/render.h" | ||
| 14 | #include "ernie/util/profiler.h" | ||
| 15 | |||
| 16 | #include <algorithm> | ||
| 17 | #include <cmath> | ||
| 18 | #include <cstring> | ||
| 19 | #include <stdexcept> | ||
| 20 | #include <vector> | ||
| 21 | |||
| 22 | namespace ernie { | ||
| 23 | namespace { | ||
| 24 | |||
| 25 | /// @return The camera a render setting names, as a pinhole view of `size` pixels. The vertical | ||
| 26 | /// field of view widens until the aperture's width fits too, as `expandAperture` conforms it. | ||
| 27 | 3 | PinholeView ReadPinholeView(const Scene& scene, const Path& path, glm::uvec2 size) { | |
| 28 | 3 | const Camera camera(scene, path); | |
| 29 | 3 | const double focal = camera.GetFocalLength(); | |
| 30 | 3 | const double aspect = static_cast<double>(size.x) / static_cast<double>(size.y); | |
| 31 | 3 | const double tangent = std::max(0.5 * camera.GetVerticalAperture() / focal, | |
| 32 | 3 | 0.5 * camera.GetHorizontalAperture() / focal / aspect); | |
| 33 | 3 | const std::optional<PrimId> prim = scene.Find(path); | |
| 34 | 3 | return {.cameraToWorld = scene.WorldTransform(*prim), | |
| 35 | 3 | .verticalFov = 2.0 * std::atan(tangent), | |
| 36 | 3 | .size = size}; | |
| 37 | 3 | } | |
| 38 | |||
| 39 | } // namespace | ||
| 40 | |||
| 41 | /// What a renderer keeps on its device. | ||
| 42 | struct Renderer::Device { | ||
| 43 | ShaderCompiler compiler; | ||
| 44 | std::unique_ptr<DeviceContext> context; | ||
| 45 | std::unique_ptr<Arenas> arenas; | ||
| 46 | std::unique_ptr<BindlessHeap> heap; | ||
| 47 | std::unique_ptr<CameraPass> camera; | ||
| 48 | /// The scene `deviceScene` follows, by `Scene::Id`: an address can be a new scene's. | ||
| 49 | std::uint64_t sceneId = 0; | ||
| 50 | std::unique_ptr<DeviceScene> deviceScene; | ||
| 51 | std::unique_ptr<DeviceColumn> ldr; | ||
| 52 | }; | ||
| 53 | |||
| 54 | 3 | Renderer::Renderer(const RendererOptions& options) : device_(std::make_unique<Device>()) { | |
| 55 | 3 | Device& device = *device_; | |
| 56 | 12 | device.context = std::make_unique<DeviceContext>( | |
| 57 | 3 | DeviceContextOptions{.validation = options.validation, .device = options.device}); | |
| 58 | 3 | if (!device.context->Capabilities().rayQuery) { | |
| 59 | ✗ | throw DeviceError(DeviceErrorKind::Unsupported, | |
| 60 | ✗ | device.context->Info().name + " cannot ray trace"); | |
| 61 | } | ||
| 62 | 9 | device.arenas = std::make_unique<Arenas>(*device.context, MemoryUse::Device, "renderer", | |
| 63 | 3 | std::uint64_t{16} << 20); | |
| 64 | 3 | device.heap = std::make_unique<BindlessHeap>(*device.context); | |
| 65 | 3 | device.camera = std::make_unique<CameraPass>(*device.context, device.compiler, *device.heap); | |
| 66 | 3 | device.ldr = std::make_unique<DeviceColumn>(*device.arenas, "ldr colour", DType::UInt8, | |
| 67 | 6 | std::vector<std::int64_t>{4}, 0); | |
| 68 | 3 | } | |
| 69 | |||
| 70 | 3 | Renderer::~Renderer() { | |
| 71 | 3 | device_->context->WaitIdle(); | |
| 72 | 3 | } | |
| 73 | |||
| 74 | 2 | DeviceContext& Renderer::Context() { | |
| 75 | 2 | return *device_->context; | |
| 76 | } | ||
| 77 | |||
| 78 | 5 | Tensor Renderer::Render(const Scene& scene) { | |
| 79 | 5 | ERNIE_ZONE("Render"); | |
| 80 | 5 | const std::vector<Path> settings = scene.Prims(RenderSettings::kName); | |
| 81 | 5 | if (settings.empty()) { | |
| 82 | 1 | throw std::invalid_argument("The scene has no RenderSettings to render"); | |
| 83 | } | ||
| 84 | 4 | const RenderSettings render(scene, settings.front()); | |
| 85 | 4 | const Path camera = render.GetCamera(); | |
| 86 | 4 | if (camera.IsEmpty() || !scene.Find(camera)) { | |
| 87 | 3 | throw std::invalid_argument("'" + settings.front().String() + "' names no camera"); | |
| 88 | } | ||
| 89 | 3 | const glm::dvec2 resolution = render.GetResolution(); | |
| 90 | 9 | const glm::uvec2 size(std::max(1.0, resolution.x), std::max(1.0, resolution.y)); | |
| 91 | 3 | const PinholeView view = ReadPinholeView(scene, camera, size); | |
| 92 | |||
| 93 | 3 | Device& device = *device_; | |
| 94 | 3 | if (scene.Id() != device.sceneId) { | |
| 95 | 2 | device.context->WaitIdle(); | |
| 96 | 2 | device.deviceScene = | |
| 97 | 2 | std::make_unique<DeviceScene>(*device.context, *device.arenas, scene, device.compiler); | |
| 98 | 2 | device.sceneId = scene.Id(); | |
| 99 | } | ||
| 100 | 3 | const std::size_t pixels = std::size_t{size.x} * size.y; | |
| 101 | 3 | device.ldr->Resize(pixels, device.context->Main()); | |
| 102 | 3 | device.deviceScene->Follow(glm::dvec3(view.cameraToWorld[3])); | |
| 103 | 3 | const Ticket done = device.deviceScene->Execute([&](graph::Graph& graph) { | |
| 104 | 3 | CameraOutputs outputs{.ldr = graph.Import(*device.ldr, device.ldr->Name())}; | |
| 105 | 3 | device.camera->Add(graph, view, outputs); | |
| 106 | 3 | }); | |
| 107 | 3 | Readback readback(*device.context, device.context->Main(), device.ldr->Storage(), | |
| 108 | 4 | device.ldr->Range().offset, pixels * 4, std::span(&done, 1)); | |
| 109 | 3 | return Tensor::Copy( | |
| 110 | std::span<const std::uint8_t>( | ||
| 111 | 3 | reinterpret_cast<const std::uint8_t*>(readback.Bytes().data()), pixels * 4), | |
| 112 | 9 | {size.y, size.x, 4}); | |
| 113 | 8 | } | |
| 114 | |||
| 115 | } // namespace ernie | ||
| 116 |