GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
198 / 230
86.1%
Branches:
141 / 270
52.2%
Functions:
20 / 24
83.3%

app/viewer.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file viewer.cpp
3 /// @brief The viewer's set-up and frame loop, and its command line.
4 #include "ernie/app/viewer.h"
5
6 #include "ernie/scene/json.h"
7 #include "ernie/scene/scene.h"
8 #include "ernie/util/disk_cache.h"
9 #include "ernie/util/profiler.h"
10
11 #include <imgui.h>
12
13 #include <charconv>
14 #include <stdexcept>
15 #include <string>
16 #include <vector>
17
18 namespace ernie::app {
19 namespace {
20
21 /// @return The presenter: a window's, made before the device context as its instance extensions
22 /// come from the window system, or an offscreen image's.
23 template <typename MakeContext>
24 2 std::unique_ptr<Presenter> MakePresenter(const ViewerOptions& options, MakeContext&& makeContext) {
25
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (options.offscreen) {
26
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 DeviceContext& context = makeContext(std::vector<std::string>());
27 2 ERNIE_ZONE("Create the offscreen image");
28
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
4 return std::make_unique<OffscreenPresenter>(context, options.size);
29 2 }
30 #if ERNIE_VIEWER_WINDOW
31 std::unique_ptr<Window> window;
32 {
33 ERNIE_ZONE("Open the window");
34 window = std::make_unique<Window>(options.title, options.size);
35 }
36 ApplyTheme(options.shell.theme, window->ContentScale());
37 DeviceContext& context = makeContext(Window::InstanceExtensions());
38 ERNIE_ZONE("Set up presenting to the window");
39 return std::make_unique<WindowPresenter>(context, std::move(window), options.vsync);
40 #else
41
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::runtime_error(
42 "This ernie was built without a window system; --offscreen draws without one");
43 #endif
44 }
45
46 /// @return Where the viewer keeps compiled pipelines between runs; empty when caching is off.
47 2 std::filesystem::path PipelineCachePath() {
48 2 const std::optional<std::filesystem::path> folder =
49 2 CacheDirectory("ERNIE_PIPELINE_CACHE", "pipelines");
50
4/10
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
4 return folder ? *folder / "viewer.bin" : std::filesystem::path();
51 2 }
52
53 6 template <typename Number> Number ParseNumber(std::string_view text, std::string_view option) {
54 6 Number value{};
55 6 const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
56
5/8
unsigned int ernie::app::(anonymous namespace)::ParseNumber<unsigned int>(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >):
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
unsigned long ernie::app::(anonymous namespace)::ParseNumber<unsigned long>(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >):
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
6 if (error != std::errc() || end != text.data() + text.size()) {
57
4/16
unsigned int ernie::app::(anonymous namespace)::ParseNumber<unsigned int>(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >):
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
unsigned long ernie::app::(anonymous namespace)::ParseNumber<unsigned long>(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >):
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
✓ Branch 11 taken 1 time.
✗ Branch 12 not taken.
3 throw std::invalid_argument(std::string(option) + " takes a number, not " +
58
1/4
unsigned int ernie::app::(anonymous namespace)::ParseNumber<unsigned int>(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >):
✗ Branch 1 not taken.
✗ Branch 2 not taken.
unsigned long ernie::app::(anonymous namespace)::ParseNumber<unsigned long>(std::basic_string_view<char, std::char_traits<char> >, std::basic_string_view<char, std::char_traits<char> >):
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
2 std::string(text));
59 }
60 5 return value;
61 }
62
63 3 glm::uvec2 ParseSize(std::string_view text) {
64 3 const std::size_t x = text.find('x');
65
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 if (x == std::string_view::npos) {
66
3/6
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
2 throw std::invalid_argument("--size takes WIDTHxHEIGHT, not " + std::string(text));
67 }
68 4 const glm::uvec2 size(ParseNumber<std::uint32_t>(text.substr(0, x), "--size"),
69
2/2
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
2 ParseNumber<std::uint32_t>(text.substr(x + 1), "--size"));
70
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
2 if (size.x == 0 || size.y == 0) {
71
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw std::invalid_argument("--size needs a positive width and height");
72 }
73 1 return size;
74 }
75
76 } // namespace
77
78 2 Viewer::Viewer(const Scene& scene, const ViewerOptions& options)
79
2/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
2 : options_(options), imgui_(options.shell.theme) {
80
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (options.size.x == 0 || options.size.y == 0) {
81
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::invalid_argument("The viewer needs a positive size");
82 }
83
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 presenter_ = MakePresenter(options, [&](std::vector<std::string> extensions) -> DeviceContext& {
84 2 ERNIE_ZONE("Create the device");
85
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 context_ = std::make_unique<DeviceContext>(
86
1/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 DeviceContextOptions{.validation = options.validation,
87
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 .device = options.device,
88 .instanceExtensions = std::move(extensions),
89 2 .pipelineCache = PipelineCachePath()});
90 2 return *context_;
91 4 });
92 2 renderer_ =
93
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
2 std::make_unique<ImGuiRenderer>(*context_, presenter_->Format(), presenter_->ImageCount());
94
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 shell_ = std::make_unique<Shell>(scene, context_.get(), options.shell);
95
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 shell_->Frame();
96 2 {
97
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 ERNIE_ZONE("Set up the device scene");
98 6 arenas_ = std::make_unique<Arenas>(*context_, MemoryUse::Device, "scene columns",
99
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 std::uint64_t{16} << 20);
100
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 const ShaderCompiler compiler;
101
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 deviceScene_ = std::make_unique<DeviceScene>(*context_, *arenas_, scene, compiler);
102
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (deviceScene_->Tracer() != nullptr) {
103
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 heap_ = std::make_unique<BindlessHeap>(*context_);
104
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 camera_ = std::make_unique<CameraPass>(*context_, compiler, *heap_);
105 }
106 6 sampler_ = std::make_unique<Sampler>(
107
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 *context_, SamplerDescription{.filter = Filter::Nearest, .address = Address::Clamp},
108 2 "viewport");
109 2 }
110
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 profiler::SetThreadName("Main");
111 2 }
112
113 2 Viewer::~Viewer() {
114 // Frames in flight sample the viewport and read the frame's columns.
115 4 Quietly([this] { context_->Main().Last().Wait(); });
116 2 }
117
118 2 void Viewer::Run() {
119
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
6 while (Step()) {
120 }
121 2 }
122
123 6 bool Viewer::Step() {
124 6 profiler::BeginFrame();
125 6 ERNIE_FRAME_MARK("Viewer frame");
126 6 {
127 6 ERNIE_ZONE("Poll events");
128
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 presenter_->PollEvents();
129 6 }
130
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 if (presenter_->CloseRequested() || !shell_->Open()) {
131 return false;
132 }
133 6 const auto now = std::chrono::steady_clock::now();
134 6 const double delta = std::chrono::duration<double>(now - last_).count();
135 6 last_ = now;
136
137 6 {
138 6 ERNIE_ZONE("Begin the UI frame");
139
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 presenter_->BeginUiFrame(delta);
140
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 renderer_->NewFrame();
141
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 ImGui::NewFrame();
142 6 }
143 6 shell_->Draw();
144 6 ShowViewport();
145 6 {
146 6 ERNIE_ZONE("Build the UI's draw data");
147
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 ImGui::Render();
148 6 }
149
150 6 std::optional<FrameTarget> target;
151 6 {
152 6 ERNIE_ZONE("Acquire an image");
153
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 target = presenter_->Acquire();
154 6 }
155
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (target) {
156 6 ImDrawData& data = *ImGui::GetDrawData();
157 6 {
158 // The backend cycles a vertex buffer per image, which the frame that last drew with
159 // it must have finished with.
160 6 ERNIE_ZONE("Wait for a frame in flight");
161
3/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 6 times.
8 while (inFlight_.size() >= presenter_->ImageCount()) {
162
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 inFlight_.front().Wait();
163 2 inFlight_.pop_front();
164 }
165 6 }
166 6 renderer_->UpdateTextures(data);
167 6 Ticket drawn;
168 6 {
169 6 ERNIE_ZONE("Run the frame");
170
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 deviceScene_->Follow(shell_->Camera().Eye());
171 6 drawn =
172
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
12 deviceScene_->Execute([&](graph::Graph& graph) { AddPasses(graph, data, *target); },
173
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 {}, target->semaphores);
174 6 }
175 6 inFlight_.push_back(drawn);
176 6 ERNIE_ZONE("Present");
177
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 presenter_->Present(drawn);
178 6 }
179 6 context_->Collect();
180 6 ++frames_;
181
4/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 4 times.
6 return shell_->Open() && (options_.frames == 0 || frames_ < options_.frames);
182 }
183
184 6 void Viewer::ShowViewport() {
185 6 const auto [position, size] = shell_->Viewport();
186 6 const ImVec2 scale = ImGui::GetIO().DisplayFramebufferScale;
187
4/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 2 times.
18 const glm::uvec2 pixels(std::max(1.0f, size.x * scale.x), std::max(1.0f, size.y * scale.y));
188
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
8 if (!viewport_ || glm::uvec2(viewport_->Description().extent) != pixels) {
189 4 ResizeViewport(pixels);
190 }
191 6 ImGui::GetBackgroundDrawList()->AddImage(viewportTexture_, position,
192 6 {position.x + size.x, position.y + size.y});
193 6 }
194
195 4 void Viewer::ResizeViewport(glm::uvec2 pixels) {
196 4 ERNIE_ZONE("Resize the viewport");
197 // Frames in flight may still sample the old image.
198
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 context_->Main().Last().Wait();
199
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (viewport_) {
200
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 renderer_->RemoveTexture(viewportTexture_);
201
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (heap_) {
202
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 heap_->RemoveStorageImage(viewportImage_);
203 }
204 }
205 12 viewport_ = std::make_unique<DeviceImage>(
206
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 *context_,
207
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 ImageDescription{.format = ImageFormat::Rgba8Unorm,
208 .extent = {pixels, 1},
209 .usage = ImageUsage::Sampled | ImageUsage::Storage},
210 4 "Viewport");
211
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 viewportTexture_ = renderer_->AddTexture(viewport_->View(), sampler_->Handle());
212
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (heap_) {
213
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 viewportImage_ = heap_->AddStorageImage(*viewport_);
214 }
215 4 }
216
217 6 graph::Image Viewer::AddViewportPass(graph::Graph& graph) {
218 6 graph::Image viewport = graph.Import(*viewport_, "viewport");
219
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (camera_) {
220 6 const OrbitCamera& orbit = shell_->Camera();
221 6 CameraOutputs outputs{.colour = viewport,
222 .colourIndex = viewportImage_,
223 6 .background = glm::vec3(shell_->GetTheme().viewport)};
224 18 camera_->Add(graph,
225 6 {.cameraToWorld = orbit.Transform(),
226 6 .verticalFov = orbit.verticalFov,
227 6 .size = glm::uvec2(viewport_->Description().extent)},
228 outputs);
229 6 return *outputs.colour;
230 }
231 ✗ graph.Add(
232 "Viewport", graph::Kind::Copy,
233 ✗ [&](graph::PassBuilder& pass) { viewport = pass.Write(viewport, graph::Use::CopyDst); },
234 ✗ [this, viewport](graph::PassContext& pass) {
235 ✗ const glm::vec4 colour = shell_->GetTheme().viewport;
236 ✗ const VkClearColorValue clear{.float32 = {colour.r, colour.g, colour.b, colour.a}};
237 ✗ const VkImageSubresourceRange all{
238 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .levelCount = 1, .layerCount = 1};
239 ✗ vkCmdClearColorImage(pass.commands, pass.Get(viewport).Handle(), pass.Layout(viewport),
240 &clear, 1, &all);
241 ✗ });
242 ✗ return viewport;
243 }
244
245 6 void Viewer::AddPasses(graph::Graph& graph, ImDrawData& data, const FrameTarget& target) {
246 6 const graph::Image viewport = AddViewportPass(graph);
247 12 graph.Add(
248 "UI", graph::Kind::Raster,
249 18 [&](graph::PassBuilder& pass) {
250 6 pass.Read(viewport, graph::Use::Sampled);
251 // It draws into the presenter's image, outside the graph.
252 6 pass.NeverCull();
253 6 },
254 6 [&](graph::PassContext& pass) {
255 6 ERNIE_ZONE("Record the UI");
256 // Panels and the viewport's image cover every pixel.
257
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 renderer_->Record(pass.commands, data, target, glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
258 6 });
259 6 }
260
261 ✗ std::string_view ViewUsage() {
262 ✗ return "ernie [view] [SCENE.json] [--offscreen] [--frames N] [--size WxH] [--device NAME]\n"
263 ✗ " [--validation] [--vsync] [--profile]";
264 }
265
266 8 ViewCommand ParseViewCommand(std::span<const std::string_view> arguments) {
267 8 ViewCommand command;
268 8 ViewerOptions& viewer = command.viewer;
269
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 2 times.
15 for (std::size_t i = 0; i < arguments.size(); ++i) {
270
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 12 times.
13 const std::string_view argument = arguments[i];
271 20 const auto value = [&]() {
272
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 6 times.
7 if (i + 1 >= arguments.size()) {
273
2/4
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
3 throw std::invalid_argument(std::string(argument) + " needs a value");
274 }
275 6 return arguments[++i];
276 13 };
277
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 12 times.
13 if (argument == "--offscreen") {
278 1 viewer.offscreen = true;
279
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 } else if (argument == "--frames") {
280
4/4
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 1 time.
3 viewer.frames = ParseNumber<std::uint64_t>(value(), argument);
281
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 } else if (argument == "--size") {
282
3/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 2 times.
3 viewer.size = ParseSize(value());
283
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
6 } else if (argument == "--device") {
284
2/4
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
1 viewer.device = value();
285
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 } else if (argument == "--validation") {
286 ✗ viewer.validation = true;
287
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
5 } else if (argument == "--vsync") {
288 1 viewer.vsync = true;
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (argument == "--profile") {
290 ✗ viewer.shell.profile = true;
291
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
4 } else if (argument.starts_with("-")) {
292
3/6
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
2 throw std::invalid_argument("Unknown option " + std::string(argument));
293
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
3 } else if (command.scene) {
294
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw std::invalid_argument("The viewer opens one scene");
295 } else {
296
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 command.scene = std::filesystem::path(argument);
297 }
298 }
299
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 1 time.
2 if (command.scene) {
300
2/4
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
3 viewer.title = "Ernie - " + command.scene->filename().string();
301 }
302 2 return command;
303 6 }
304
305 ✗ int RunView(const ViewCommand& command) {
306 ✗ Scene scene;
307 ✗ {
308
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ ERNIE_ZONE("Load the scene");
309
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (command.scene) {
310
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ LoadSceneFile(scene, *command.scene);
311 }
312
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ scene.Update();
313 ✗ }
314
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Viewer viewer(scene, command.viewer);
315
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ if (command.scene) {
316
0/6
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ viewer.GetShell().Log().Info("Opened " + command.scene->string());
317 }
318
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ viewer.Run();
319 ✗ return 0;
320 ✗ }
321
322 } // namespace ernie::app
323