GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 424, 0 excluded
0.0%
Functions:
0 of 52, 0 excluded
0.0%
Branches:
0 of 756, 0 excluded
0.0%

apps/runner/src/eu/runner/main.cc
Line Branch Exec Source
1 #include <utility>
2
3 // todo(Gustav): look into glad support
4 #define SDL_FUNCTION_POINTER_IS_VOID_POINTER
5
6 #include "SDL.h"
7
8 // #include <string>
9
10 #include "eu/log/log.h"
11 #include "eu/base/memorychunk.h"
12
13 #include "eu/core/geom.builder.h"
14 #include "eu/core/geom.h"
15
16 #include "eu/io/file.h"
17 #include "eu/io/mesh.h"
18
19 #include "eu/render/canvas.h"
20 #include "eu/render/state.h"
21 #include "eu/render/font.h"
22 #include "eu/render/opengl_utils.h"
23 #include "eu/render/texture.io.h"
24 #include "eu/render/camera.h"
25 #include "eu/render/compiledmesh.h"
26 #include "eu/render/enable_high_performance_graphics.h"
27 #include "eu/render/postproc.h"
28 #include "eu/render/renderer.h"
29 #include "eu/render/world.h"
30
31 #include "eu/runner/script.h"
32 #include "eu/runner/input.h"
33 #include "eu/runner/entity.h"
34
35 #include "eu/kdl/kdl.h"
36
37
38 #if FF_HAS(EU_DEBUG_RUNNER)
39 #include "eu/imgui/ui.h"
40 #include "eu/imgui/init.h"
41
42 #include "dear_imgui/imgui.h"
43 #include "dear_imgui/backends/imgui_impl_sdl3.h"
44 #include "dear_imgui/backends/imgui_impl_opengl3.h"
45
46 ✗ static void imgui_color(const char* const label, eu::Rgb* color)
47 {
48 ✗ ImGui::ColorEdit3(label, &color->r);
49 ✗ }
50 #endif
51
52
53 ENABLE_HIGH_PERFORMANCE_GRAPHICS
54
55 using namespace eu;
56 using namespace std::string_view_literals;
57
58 // todo(Gustav): move to io
59 ✗ eu::render::ShaderSource load_shader(const std::string& name)
60 {
61 return {
62 ✗ .vertex = eu::io::string_from_file(name + ".vert.glsl"),
63 ✗ .fragment = eu::io::string_from_file(name + ".frag.glsl")
64 ✗ };
65 ✗ }
66
67 ✗ eu::Size size_from_v2(const eu::v2& v)
68 {
69 ✗ return eu::Size{ .width = eu::int_from_float(v.x), .height = eu::int_from_float(v.y) };
70 }
71
72 ✗ std::shared_ptr<eu::render::Texture2d> load_texture(const std::string& path, eu::render::ColorData cd,
73 eu::render::TextureEdge texture_edge = eu::render::TextureEdge::repeat,
74 eu::render::Transparency transparency = eu::render::Transparency::exclude)
75 {
76 ✗ const auto bin = eu::io::bytes_from_file(path);
77 return std::make_shared<eu::render::Texture2d>(
78 ✗ load_image_from_embedded(SEND_DEBUG_LABEL_MANY(path) embedded_binary { reinterpret_cast<const unsigned int*>(bin.data()), static_cast<unsigned int>(bin.size()) }, texture_edge, eu::render::TextureRenderStyle::mipmap, transparency, cd)
79 ✗ );
80 ✗ }
81
82 ✗ std::shared_ptr<eu::render::Texture2d> create_texture(DEBUG_LABEL_ARG_MANY eu::core::SingleColor pixel_color, eu::render::ColorData cd)
83 {
84 return std::make_shared<eu::render::Texture2d>(
85 ✗ load_image_from_color(SEND_DEBUG_LABEL_MANY(debug_label) pixel_color, eu::render::TextureEdge::repeat, eu::render::TextureRenderStyle::pixel, eu::render::Transparency::exclude, cd)
86 ✗ );
87 }
88
89 struct Level
90 {
91 std::unordered_map<std::string, std::unique_ptr<eu::render::CompiledMesh>> meshes;
92 std::vector<std::unique_ptr<render::MeshInWorld>> instances;
93
94 ✗ void load(const std::string& path, render::World* world, std::shared_ptr<render::DefaultMaterial> material, const core::CompiledGeomVertexAttributes& layout)
95 {
96 // todo(Gustav): this is very hacky but just something so we can play a level
97 ✗ v3 offset = { 0.0f, 0.0f, 0.0f };
98 ✗ v3 size = { 1.0f, 1.0f, 1.0f };
99
100 ✗ const auto parsed = kdl::parse(eu::io::string_from_file(path));
101 ✗ if (parsed.document.has_value() == false)
102 {
103 ✗ LOG_WARN("Failed to parse '{}': {}", path, parsed.error);
104 ✗ return;
105 }
106
107 ✗ const auto& doc = *parsed.document;
108
109 ✗ for (const auto& node: doc)
110 {
111 ✗ if (node.name == "mesh")
112 {
113 ✗ const auto key = node.args[0].as_string();
114 ✗ const auto file = node.args[1].as_string();
115
116 ✗ const auto mesh = eu::io::mesh_from_file(file);
117 ✗ auto compiled = eu::render::compile_mesh(USE_DEBUG_LABEL_MANY(file) mesh, layout);
118
119 ✗ meshes[key] = std::make_unique<eu::render::CompiledMesh>(compiled);
120 ✗ }
121 ✗ else if (node.name == "offset")
122 {
123 ✗ const auto x = node.args[0].as_number().as<float>().value_or(0.0f);
124 ✗ const auto y = node.args[1].as_number().as<float>().value_or(0.0f);
125 ✗ const auto z = node.args[2].as_number().as<float>().value_or(0.0f);
126 ✗ offset = { x, y, z };
127 }
128 ✗ else if (node.name == "cell_size")
129 {
130 ✗ const auto x = node.args[0].as_number().as<float>().value_or(0.0f);
131 ✗ const auto y = node.args[1].as_number().as<float>().value_or(0.0f);
132 ✗ const auto z = node.args[2].as_number().as<float>().value_or(0.0f);
133 ✗ size = {x, y, z};
134 }
135 ✗ else if (node.name == "item")
136 {
137 ✗ const auto x = node.args[0].as_number().as<float>().value_or(0.0f);
138 ✗ const auto y = node.args[1].as_number().as<float>().value_or(0.0f);
139 ✗ const auto z = node.args[2].as_number().as<float>().value_or(0.0f);
140 ✗ const v3 pos = { x*size.x, y*size.y, z*size.z };
141 ✗ const auto item = node.properties.at("item").as_string();
142 ✗ const auto found = meshes.find(item);
143 ✗ if (found != meshes.end())
144 {
145 ✗ render::MeshInWorld car;
146 ✗ car.add_to_world(found->second.get(), world, material);
147 ✗ car.set_transform(eu::render::transform_from_rotation(offset + pos, Ypr{0_deg, 0_deg, 0_deg}));
148 ✗ }
149 else
150 {
151 ✗ LOG_WARN("Mesh '{}' not found for item in level file", item);
152 }
153 ✗ }
154 else
155 {
156 ✗ LOG_WARN("Unknown node in level file: '{}'", node.name);
157 }
158 }
159 ✗ }
160 };
161
162
163 struct Time
164 {
165 Uint64 now = SDL_GetPerformanceCounter();
166 Uint64 last = 0;
167
168
169 ✗ float calculate()
170 {
171 ✗ last = now;
172 ✗ now = SDL_GetPerformanceCounter();
173
174 ✗ const auto diff = static_cast<float>(now - last);
175 ✗ const auto pfc = static_cast<float>(SDL_GetPerformanceFrequency());
176 ✗ return diff / pfc;
177 }
178 };
179
180
181 struct MeshSpatialComponent : runner::SpatialComponent
182 {
183 render::MeshInWorld car;
184
185 eu::v3 position = { 0.0f, -1.8f, -10.0f };
186 eu::Ypr rotation = { .yaw = 30.0_deg, .pitch = 30.0_deg, .roll = 30.0_deg };
187
188 ✗ const char* display() override
189 {
190 ✗ return "MeshSpatialComponent";
191 }
192
193 ✗ void imgui() override
194 {
195 ✗ runner::SpatialComponent::imgui();
196 ✗ imgui::drag("Position", &position);
197 ✗ imgui::drag("Rotation", &rotation);
198 ✗ }
199
200 ✗ void update_transform()
201 {
202 ✗ set_transform(eu::render::transform_from_rotation(position, rotation));
203 ✗ car.set_transform(get_transform());
204 ✗ }
205
206 ✗ MeshSpatialComponent(render::CompiledMesh* mesh, render::World* render_world, std::shared_ptr<render::Material> material)
207 ✗ {
208 ✗ car.add_to_world(mesh, render_world, std::move(material));
209 ✗ update_transform();
210 ✗ }
211
212 EU_DEC_COMPONENT_TYPE();
213 };
214
215 ✗ EU_IMP_COMPONENT_TYPE(MeshSpatialComponent, runner::Component)
216
217 ✗ void boolean(const char* label, bool b)
218 {
219 ✗ ImGui::TextUnformatted(label);
220 ✗ ImGui::SameLine();
221 ✗ ImGui::TextUnformatted(b ? "TRUE" : "false");
222 ✗ }
223
224 struct InputSystem : runner::EntitySystem
225 {
226 runner::Input* input;
227 MeshSpatialComponent* mesh = nullptr;
228
229 ✗ const char* display() override
230 {
231 ✗ return "Input";
232 }
233
234 ✗ void imgui() override
235 {
236 ✗ boolean("has mesh", mesh != nullptr);
237 ✗ }
238
239 ✗ explicit InputSystem(runner::Input* in) : input(in)
240 {
241 ✗ }
242
243 ✗ runner::UpdateStageAndPrio get_stage() override
244 {
245 return
246 {
247 .stage = runner::UpdateStage::before_physics,
248 .prio = 100
249 ✗ };
250 }
251
252 ✗ void on_root_changed(runner::SpatialComponent*) override
253 {
254 ✗ }
255
256 ✗ void add_component(runner::Component* component) override
257 {
258 ✗ if (MeshSpatialComponent* mc = runner::component_cast<MeshSpatialComponent>(component); mc)
259 {
260 ✗ mesh = mc;
261 }
262 ✗ }
263
264 ✗ void update(float dt) override
265 {
266 ✗ if (mesh && input && input->get_current_frame().binds[0].current > 0.5f)
267 {
268 ✗ mesh->position += kk::in * dt;
269 }
270
271 ✗ if (mesh)
272 {
273 ✗ mesh->update_transform();
274 }
275 ✗ }
276 };
277
278 struct TargetComponent : runner::Component
279 {
280 m4 target = m4_identity;
281
282 ✗ const char* display() override
283 {
284 ✗ return "target";
285 }
286
287 ✗ void imgui() override
288 {
289 // todo(Gustav): display matrix
290 ✗ }
291
292 EU_DEC_COMPONENT_TYPE();
293 };
294 ✗ EU_IMP_COMPONENT_TYPE(TargetComponent, runner::Component)
295
296 struct CameraSpatialComponent : runner::SpatialComponent
297 {
298 ✗ const char* display() override
299 {
300 ✗ return "camera spatial";
301 }
302
303 ✗ void imgui() override
304 {
305 ✗ runner::SpatialComponent::imgui();
306 ✗ }
307 // todo(Gustav): add extra camera settings...
308 EU_DEC_COMPONENT_TYPE();
309 };
310 ✗ EU_IMP_COMPONENT_TYPE(CameraSpatialComponent, runner::SpatialComponent);
311
312 struct FollowCameraSystem : runner::EntitySystem
313 {
314 ✗ runner::UpdateStageAndPrio get_stage() override
315 {
316 return
317 {
318 .stage = runner::UpdateStage::after_physics,
319 .prio = 100
320 ✗ };
321 }
322
323 TargetComponent* target = nullptr;
324 CameraSpatialComponent* camera = nullptr;
325
326 ✗ const char* display() override
327 {
328 ✗ return "Follow camera";
329 }
330
331 v3 offset = kk::out * 3;
332 ✗ void imgui() override
333 {
334 ✗ boolean("Target", target != nullptr);
335 ✗ boolean("camera", camera != nullptr);
336 ✗ imgui::drag("offset", &offset);
337 ✗ imgui::gear("gear", &offset);
338 ✗ }
339
340 ✗ void on_root_changed(runner::SpatialComponent*) override
341 {
342 ✗ }
343
344 ✗ void add_component(runner::Component* component) override
345 {
346 ✗ if (auto* tar = runner::component_cast<TargetComponent>(component))
347 {
348 ✗ target = tar;
349 ✗ LOG_INFO("Follow camera found target");
350 }
351 ✗ else if (auto* cam = runner::component_cast<CameraSpatialComponent>(component))
352 {
353 ✗ camera = cam;
354 ✗ LOG_INFO("Follow camera found camera");
355 }
356 ✗ }
357 ✗ void update(float) override
358 {
359 ✗ if (!target) { return; }
360 ✗ if (!camera) { return; }
361
362 ✗ const auto focus = target->target.get_translation();
363 const auto pos_from_focus =
364 ✗ target->target.get_transformed_vec(kk::right) * offset.x +
365 ✗ target->target.get_transformed_vec(kk::up) * offset.y +
366 ✗ target->target.get_transformed_vec(kk::in) * offset.z ;
367 ✗ const auto pos = focus + pos_from_focus;
368 ✗ const auto rot = Q::look_at(pos, focus, kk::up);
369 ✗ if (!rot)
370 {
371 ✗ return;
372 }
373
374 ✗ const auto rot_mat = m4::from(*rot);
375 ✗ if (!rot_mat)
376 {
377 ✗ return;
378 }
379
380 ✗ const auto mat = *rot_mat * m4::from_translation(pos);
381
382 // const auto nt = target->target.get_translated(offset);
383 ✗ camera->set_transform(mat);
384 }
385 };
386
387 namespace tag
388 {
389 constexpr Hsh CameraSpatialSource = "camera_spatial_src_tag"sv;
390 constexpr Hsh CameraDestination = "camera_dst_tag"sv;
391 }
392
393 struct UpdateCameraTarget : runner::WorldSystem
394 {
395 runner::SpatialComponent* src_entity = nullptr;
396 TargetComponent* dst_entity = nullptr;
397
398 ✗ const char* display() override
399 {
400 ✗ return "Update camera target";
401 }
402
403 ✗ void imgui() override
404 {
405 ✗ boolean("src entity", src_entity != nullptr);
406 ✗ boolean("dst entity", dst_entity != nullptr);
407 ✗ }
408
409 ✗ runner::UpdateStageAndPrio get_stage() override
410 {
411 return {
412 .stage = runner::UpdateStage::after_physics,
413 .prio = 100
414 ✗ };
415 }
416
417 ✗ void on_root_changed(runner::Entity* entity, runner::SpatialComponent* component) override
418 {
419 ✗ if (entity->has_tag(tag::CameraSpatialSource))
420 {
421 ✗ LOG_INFO("Found spatial source");
422 ✗ src_entity = component;
423 }
424 ✗ }
425
426 ✗ void add_component(runner::Entity* entity, runner::Component* component) override
427 {
428 ✗ if (entity->has_tag(tag::CameraDestination))
429 {
430 ✗ if (auto* target = runner::component_cast<TargetComponent>(component))
431 {
432 ✗ LOG_INFO("Found camera destination on {}", entity->name);
433 ✗ dst_entity = target;
434 }
435 }
436 ✗ }
437
438 ✗ void update(float) override
439 {
440 ✗ if (!src_entity) { return; }
441 ✗ if (!dst_entity) { return; }
442
443 ✗ dst_entity->target = src_entity->get_transform();
444 }
445 };
446
447 struct CameraFetcherSystem : runner::WorldSystem
448 {
449 ✗ runner::UpdateStageAndPrio get_stage() override
450 {
451 return {
452 .stage = runner::UpdateStage::end_frame,
453 .prio = 100
454 ✗ };
455 }
456
457 CameraSpatialComponent* camera = nullptr;
458 m4 transform = m4_identity;
459
460 ✗ const char* display() override
461 {
462 ✗ return "Camera fetcher";
463 }
464
465 ✗ void imgui() override
466 {
467 ✗ boolean("camera", camera != nullptr);
468 //todo(Gustav): display transform
469 ✗ }
470
471 ✗ void add_component(runner::Entity*, runner::Component* component) override
472 {
473 ✗ if (auto cam = runner::component_cast<CameraSpatialComponent>(component))
474 {
475 ✗ LOG_INFO("Fetcher found camera");
476 ✗ camera = cam;
477 }
478 ✗ }
479
480 ✗ void on_root_changed(runner::Entity*, runner::SpatialComponent*) override
481 {
482 ✗ }
483
484 ✗ void update(float) override
485 {
486 ✗ if (!camera) return;
487 ✗ transform = camera->get_transform();
488 }
489
490 ✗ render::Camera fetch() const
491 {
492 ✗ render::Camera r;
493 ✗ r.position = transform.get_translation();
494 ✗ return r;
495 }
496 };
497
498 ✗ int main(int, char**)
499 {
500 ✗ const char* glsl_version = "#version 130";
501
502 // todo(Gustav): set app metadata
503
504 ✗ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) == false)
505 {
506 ✗ LOG_ERR("Error initializing SDL: {}", SDL_GetError());
507 ✗ return -1;
508 }
509
510 {
511 ✗ const auto str = [](int version)
512 {
513 ✗ const auto major = SDL_VERSIONNUM_MAJOR(version);
514 ✗ const auto minor = SDL_VERSIONNUM_MINOR(version);
515 ✗ const auto micro = SDL_VERSIONNUM_MICRO(version);
516 ✗ return fmt::format("{0}.{1}.{2}", major, minor, micro);
517 };
518 ✗ const int version_loaded = SDL_GetVersion();
519 ✗ LOG_INFO("SDL {0} initialized with {1}", str(SDL_VERSION), str(version_loaded));
520 }
521
522 // todo(Gustav): move to some shared sdl lib instead? as this shouldn't use dear imgui and 100% rely on sdl
523 ✗ const auto app_scale = eu::imgui::calculate_app_scale();
524 ✗ auto [window_width, window_height] = eu::imgui::rescale_window(1280, 720, app_scale);
525
526 #if defined(__APPLE__)
527 // GL 3.2 Core + GLSL 150
528 const char* glsl_version = "#version 150";
529 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
530 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
531 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
532 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
533 #else
534 // GL 3.0 + GLSL 130
535 // const char* glsl_version = "#version 130";
536 ✗ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
537 ✗ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
538 ✗ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
539 ✗ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); // was 0 in dear imgui example??
540 #endif
541
542 ✗ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
543 ✗ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
544
545 ✗ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
546 ✗ SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
547
548 ✗ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
549 ✗ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
550 ✗ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
551 ✗ SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
552
553 ✗ SDL_Window* window = SDL_CreateWindow("Runner",
554 window_width, window_height, SDL_WINDOW_OPENGL | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_RESIZABLE);
555 ✗ if (!window) {
556 ✗ LOG_ERR("Error creating window: {}", SDL_GetError());
557 ✗ return -1;
558 }
559
560 ✗ auto* glContext = SDL_GL_CreateContext(window);
561 ✗ SDL_GetWindowSize(window, &window_width, &window_height);
562 ✗ if (!glContext) {
563 ✗ LOG_ERR("Error creating gl context: {}", SDL_GetError());
564 ✗ return -1;
565 }
566
567 /* OpenGL setup */
568 ✗ const int glad_result = gladLoadGLLoader(SDL_GL_GetProcAddress);
569 ✗ if (glad_result == 0)
570 {
571 ✗ LOG_ERR("Failed to init glad, error: {0}", glad_result);
572 ✗ return -1;
573 }
574
575 {
576 ✗ if (GLAD_GL_VERSION_1_0) {
577 ✗ const std::string gl_vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
578 ✗ const std::string gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
579 ✗ const std::string gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
580 ✗ LOG_INFO("OpenGL Vendor: {0}", gl_vendor);
581 ✗ LOG_INFO("OpenGL Renderer: {0}", gl_renderer);
582 ✗ LOG_INFO("OpenGL Version: {0}", gl_version);
583 ✗ }
584
585 ✗ if (GLAD_GL_VERSION_2_0) {
586 ✗ const std::string gl_shading_language_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
587 ✗ LOG_INFO("Version GLSL: {0}", gl_shading_language_version);
588 ✗ }
589 }
590
591 ✗ if (!GLAD_GL_VERSION_4_3)
592 {
593 ✗ LOG_ERR("OpenGL 4.3 not supported ({0}.{1} detected), aborting...", GLVersion.major, GLVersion.minor);
594 ✗ return -1;
595 }
596
597 #if FF_HAS(EU_DEBUG_RUNNER)
598 ✗ IMGUI_CHECKVERSION();
599 ✗ ImGui::CreateContext();
600 ✗ ImGuiIO& io = ImGui::GetIO(); (void)io;
601 ✗ ImGui::StyleColorsDark();
602 ✗ if (false == ImGui_ImplSDL3_InitForOpenGL(window, glContext))
603 {
604 ✗ LOG_ERR("Failed to init ImGui SDL3 backend");
605 ✗ return -1;
606 }
607 ✗ if (false == ImGui_ImplOpenGL3_Init(glsl_version))
608 {
609 ✗ LOG_ERR("Failed to init ImGui OpenGL3 backend");
610 ✗ return -1;
611 }
612
613 ✗ eu::imgui::setup_scale(app_scale);
614
615 ✗ bool show_demo_window = true;
616 #endif
617
618 ✗ eu::render::State states;
619 ✗ eu::render::Render2 render{ &states };
620
621 ✗ eu::render::Assets assets;
622
623 ✗ assets.black = create_texture(SEND_DEBUG_LABEL_MANY("black") eu::core::color_from_rgba(0, 0, 0, 255), eu::render::ColorData::dont_care);
624 ✗ assets.white = create_texture(SEND_DEBUG_LABEL_MANY("white") eu::core::color_from_rgba(255, 255, 255, 255), eu::render::ColorData::dont_care);
625
626 ✗ assets.default_shader_source = load_shader("default_shader");
627 ✗ assets.skybox_shader_source = load_shader("skybox");
628
629 ✗ assets.pp_vert_glsl = eu::io::string_from_file("pp_vert.glsl");
630 ✗ assets.pp_realize_frag_glsl = eu::io::string_from_file("pp_realize.frag.glsl");
631 ✗ assets.pp_extract_frag_glsl = eu::io::string_from_file("pp_extract.frag.glsl");
632 ✗ assets.pp_ping_pong_blur_frag_glsl = eu::io::string_from_file("pp_ping_pong_blur.frag.glsl");
633
634 ✗ eu::render::Renderer renderer{ &states, &assets, eu::render::RenderSettings{} };
635 ✗ if (renderer.is_loaded() == false)
636 {
637 ✗ return -1;
638 }
639
640 ✗ eu::runner::Input input;
641 ✗ if (false == input.load_from_file("input.kdl"))
642 {
643 ✗ return -1;
644 }
645
646 ✗ eu::runner::Script script;
647 ✗ if (false == script.run_file("main.lax"))
648 {
649 ✗ return -1;
650 }
651
652 ✗ eu::render::World render_world;
653 ✗ eu::render::EffectStack effects;
654
655 #if FF_HAS(EU_DEBUG_RUNNER)
656 ✗ imgui::ImguiShaderCache imgui_shader_cache;
657 #endif
658
659 // add demo world
660 {
661 ✗ constexpr auto PLANE_SIZE = 100.0f;
662 auto plane_geom = eu::render::compile_geom(
663 ✗ USE_DEBUG_LABEL_MANY("plane")
664 ✗ eu::core::geom::create_xz_plane(PLANE_SIZE, PLANE_SIZE, false).to_geom(),
665 ✗ renderer.default_geom_layout()
666 ✗ );
667
668 ✗ auto material = renderer.make_default_material();
669 ✗ material->diffuse = load_texture("sprites/smoke.png", eu::render::ColorData::dont_care);
670 ✗ material->specular = assets.white;
671
672 ✗ auto plane = make_mesh_instance(plane_geom, material);
673 ✗ render_world.meshes.emplace_back(plane);
674 ✗ plane->transform = eu::m4::from_translation({ 0.0f, -3.0f, 0.0f });
675
676 ✗ render_world.lights.point_lights.emplace_back();
677 ✗ }
678
679 // add demo mesh
680 ✗ eu::runner::World runner_world;
681 ✗ std::unordered_map<std::string, std::unique_ptr<render::CompiledMesh>> meshes;
682 ✗ std::unordered_map<std::string, std::shared_ptr<eu::render::Texture2d>> textures;
683
684 ✗ const auto load_mesh = [&](const std::string& f) -> render::CompiledMesh*
685 {
686 ✗ const auto found = meshes.find(f);
687 ✗ if (found != meshes.end())
688 {
689 ✗ return found->second.get();
690 }
691 ✗ const auto mesh = eu::io::mesh_from_file(f);
692 ✗ const auto compiled = eu::render::compile_mesh(USE_DEBUG_LABEL_MANY(f) mesh, renderer.default_geom_layout());
693 ✗ auto data = std::make_unique<render::CompiledMesh>(compiled);
694 ✗ auto* ret = data.get();
695 ✗ meshes.insert_or_assign(f, std::move(data));
696 ✗ return ret;
697 ✗ };
698
699 ✗ const auto load_color_texture = [&](const std::string& f) -> std::shared_ptr<render::Texture2d>
700 {
701 ✗ const auto found = textures.find(f);
702 ✗ if (found != textures.end())
703 {
704 ✗ return found->second;
705 }
706 ✗ const auto loaded = load_texture(f, eu::render::ColorData::color_data);
707 ✗ textures.insert_or_assign(f, loaded);
708 ✗ return loaded;
709 ✗ };
710
711 ✗ runner_world.add_system(std::make_unique<UpdateCameraTarget>());
712
713 CameraFetcherSystem* camera_fetcher;
714 {
715 ✗ auto fetch = std::make_unique<CameraFetcherSystem>();
716 ✗ camera_fetcher = fetch.get();
717 ✗ runner_world.add_system(std::move(fetch));
718 ✗ }
719
720 {
721 ✗ auto* car = runner_world.add_entity("car", { tag::CameraSpatialSource });
722
723 // mesh component
724 // debug/axis.glb
725 // "models/vehicle-truck-purple.glb"
726 // "models/textures/colormap.png"
727 // render::CompiledMesh* mesh, render::World* render_world, std::shared_ptr<render::Material> material
728 ✗ auto* mesh = load_mesh("models/vehicle-truck-purple.glb");
729 ✗ auto material = renderer.make_default_material();
730 ✗ material->diffuse = load_color_texture("models/textures/colormap.png");
731 ✗ material->specular = assets.white;
732 ✗ auto comp = std::make_unique<MeshSpatialComponent>(mesh, &render_world, material);
733 ✗ car->set_root(comp.get());
734 ✗ car->add_component(std::move(comp));
735
736 // input component
737 ✗ car->add_system(std::make_unique<InputSystem>(&input));
738 ✗ }
739 {
740 ✗ auto* cam = runner_world.add_entity("camera", { tag::CameraDestination });
741
742 ✗ cam->add_component(std::make_unique<TargetComponent>());
743 ✗ auto spat = std::make_unique<CameraSpatialComponent>();
744 ✗ cam->set_root(spat.get());
745 ✗ cam->add_component(std::move(spat));
746 ✗ cam->add_system(std::make_unique<FollowCameraSystem>());
747 ✗ }
748 {
749 ✗ Level level;
750 ✗ auto material = renderer.make_default_material();
751 ✗ material->diffuse = load_color_texture("models/textures/colormap.png");
752 ✗ material->specular = assets.white;
753 ✗ level.load("level.kdl", &render_world, material, renderer.default_geom_layout());
754 ✗ }
755
756 ✗ LOG_INFO("Runner started");
757 ✗ bool running = true;
758 ✗ Time time;
759 ✗ while (running)
760 {
761 ✗ const auto dt = time.calculate();
762
763 ✗ runner_world.update(runner::UpdateStage::start_frame, dt);
764
765 SDL_Event e;
766 ✗ input.update();
767 ✗ while (SDL_PollEvent(&e) != 0)
768 {
769 #if FF_HAS(EU_DEBUG_RUNNER)
770 ✗ ImGui_ImplSDL3_ProcessEvent(&e);
771 #endif
772
773 ✗ switch (e.type)
774 {
775 ✗ case SDL_EVENT_KEY_DOWN:
776 case SDL_EVENT_KEY_UP:
777 ✗ input.on_key(e.key.key, e.type == SDL_EVENT_KEY_DOWN);
778 ✗ break;
779 ✗ case SDL_EVENT_WINDOW_RESIZED:
780 ✗ LOG_INFO("Resized");
781 ✗ SDL_GetWindowSize(window, &window_width, &window_height);
782 ✗ break;
783 ✗ case SDL_EVENT_QUIT: running = false; break;
784 ✗ default:
785 // ignore other events
786 ✗ break;
787 }
788 }
789
790 // todo(Gustav): lookup unity frame, when updates are called and update stages
791 ✗ runner_world.update(runner::UpdateStage::before_physics, dt);
792 ✗ runner_world.update(runner::UpdateStage::physics, dt);
793 ✗ runner_world.update(runner::UpdateStage::after_physics, dt);
794
795 #if FF_HAS(EU_DEBUG_RUNNER)
796 ✗ ImGui_ImplOpenGL3_NewFrame();
797 ✗ ImGui_ImplSDL3_NewFrame();
798 ✗ ImGui::NewFrame();
799
800 ✗ if (show_demo_window)
801 {
802 ✗ ImGui::ShowDemoWindow(&show_demo_window);
803 }
804
805 ✗ if (ImGui::Begin("Renderer"))
806 {
807 ✗ imgui_color("Clear color", &render_world.clear_color);
808 ✗ eu::imgui::simple_gamma_slider("Gamma/Brightness", &renderer.settings.gamma, -1.0f);
809 ✗ ImGui::Checkbox("Use bloom", &renderer.settings.use_bloom);
810 ✗ ImGui::DragFloat("Bloom cutoff", &renderer.settings.bloom_cutoff, 0.001f);
811 ✗ ImGui::SliderFloat("Softness", &renderer.settings.bloom_softness, 0.0f, 1.0f);
812 ✗ ImGui::DragInt("Bloom blur steps", &renderer.settings.bloom_blur_steps);
813 ✗ effects.gui(&imgui_shader_cache);
814 }
815 ✗ ImGui::End();
816
817 // todo(Gustav): add introspection ui
818 ✗ if (ImGui::Begin("Demo"))
819 {
820 ✗ runner_world.gui();
821 // imgui::drag("Position", &position);
822 // imgui::drag("Rotation", &rotation);
823 }
824 ✗ ImGui::End();
825 #endif
826
827 ✗ eu::render::RenderCommand cmd{ .states = &states, .render = &render, .size = {.width = window_width, .height = window_height} };
828
829 // clear
830 {
831 {
832 ✗ const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::extended,
833 .requested_width = 800, .requested_height = 600 };
834 ✗ cmd.clear(eu::colors::black, screen);
835 }
836 }
837
838 // render 3d world
839 {
840 ✗ const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::black_bars,
841 .requested_width = 800, .requested_height = 600 };
842 ✗ cmd.clear(eu::colors::blue_sky, screen);
843 ✗ auto layer = eu::render::with_layer3(cmd, screen);
844 // todo(Gustav): get transform from camera entity
845 ✗ effects.update(dt);
846 ✗ eu::render::Camera camera = camera_fetcher->fetch();
847 ✗ effects.render(eu::render::PostProcArg{
848 .world = &render_world,
849 ✗ .window_size = size_from_v2(layer.screen.get_size()),
850 .final_rect = layer.screen,
851 .camera = &camera,
852 .renderer = &renderer
853 });
854 }
855
856 // render hud
857 {
858 ✗ const auto screen = eu::render::LayoutData{ .style = eu::render::ViewportStyle::black_bars,
859 .requested_width = 1280, .requested_height = 720 };
860
861 ✗ auto layer = eu::render::with_layer2(cmd, screen);
862 ✗ const float hud_size = 100.0f;
863 ✗ eu::render::Quad{ .tint = eu::colors::blue_sky }.draw(layer.batch, layer.viewport_aabb_in_worldspace.get_bottom(hud_size).get_right(hud_size).with_inset(eu::Lrtb{ 5.0f }));
864 ✗ }
865
866 #if FF_HAS(EU_DEBUG_RUNNER)
867 ✗ ImGui::Render();
868 ✗ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
869 #endif
870 ✗ SDL_GL_SwapWindow(window);
871
872 ✗ runner_world.update(runner::UpdateStage::end_frame, dt);
873 }
874
875 #if FF_HAS(EU_DEBUG_RUNNER)
876 ✗ ImGui_ImplOpenGL3_Shutdown();
877 ✗ ImGui_ImplSDL3_Shutdown();
878 ✗ ImGui::DestroyContext();
879 #endif
880
881 ✗ LOG_INFO("Shutting down");
882 ✗ SDL_GL_DestroyContext(glContext);
883 ✗ SDL_DestroyWindow(window);
884 ✗ SDL_Quit();
885
886 ✗ return 0;
887 ✗ }
888