From 63d97f058a2bc8ea01e351d3e8d9c54ddd997d84 Mon Sep 17 00:00:00 2001 From: ArchZer0 Date: Mon, 17 Aug 2026 10:26:27 +0800 Subject: [PATCH] =?UTF-8?q?png=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format | 2 +- CMakeLists.txt | 27 +- CMakePresets.json | 9 +- compile_commands.json | 2 +- config.ceg | 22 +- examples/square/config.ceg | 21 +- examples/square/main.cpp | 41 +++ examples/square/scenes/Dank.png | Bin 0 -> 45114 bytes examples/square/scenes/square.json | 21 ++ examples/square/scenes/square_alt.json | 15 + include/boundard.hpp | 1 + include/config.hpp | 11 + include/game.hpp | 26 ++ include/mesh.hpp | 13 +- include/scene.hpp | 45 +++ scenes/Dank.png | Bin 0 -> 45114 bytes scenes/main.json | 19 ++ scenes/second.json | 19 ++ shaders/textured_quad.frag | 11 + shaders/textured_quad.vert | 19 ++ src/config.cpp | 125 ++++++- src/engine.cpp | 155 ++++++--- src/game.cpp | 52 +++ src/render.cpp | 437 ++++++++++++++++--------- src/scene.cpp | 61 ++++ 25 files changed, 893 insertions(+), 261 deletions(-) create mode 100644 examples/square/scenes/Dank.png create mode 100644 examples/square/scenes/square.json create mode 100644 examples/square/scenes/square_alt.json create mode 100644 include/scene.hpp create mode 100644 scenes/Dank.png create mode 100644 scenes/main.json create mode 100644 scenes/second.json create mode 100644 shaders/textured_quad.frag create mode 100644 shaders/textured_quad.vert create mode 100644 src/scene.cpp diff --git a/.clang-format b/.clang-format index 9026068..67f33c1 100644 --- a/.clang-format +++ b/.clang-format @@ -3,4 +3,4 @@ BasedOnStyle: Google IndentWidth: 4 ColumnLimit: 100 AllowShortIfStatementsOnASingleLine: false -AlignTrailingComments: true \ No newline at end of file +AlignTrailingComments: true \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1361459..d2baa48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_library(boundard STATIC src/boundard.cpp src/game.cpp + src/scene.cpp src/render.cpp src/config.cpp src/engine.cpp @@ -14,9 +15,33 @@ target_compile_features(boundard PUBLIC cxx_std_17) find_package(glfw3 REQUIRED) find_package(Vulkan REQUIRED) +find_package(PNG REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(JSONCPP REQUIRED IMPORTED_TARGET jsoncpp) -target_link_libraries(boundard PUBLIC glfw Vulkan::Vulkan PkgConfig::JSONCPP) +find_program(GLSLC_EXECUTABLE glslc REQUIRED) + +set(BOUNDARD_SHADER_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders") +set(BOUNDARD_SHADER_SOURCES + shaders/textured_quad.vert + shaders/textured_quad.frag +) +set(BOUNDARD_SHADER_BINARIES) +foreach(shader IN LISTS BOUNDARD_SHADER_SOURCES) + get_filename_component(shader_name "${shader}" NAME) + set(shader_output "${BOUNDARD_SHADER_DIR}/${shader_name}.spv") + add_custom_command( + OUTPUT "${shader_output}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${BOUNDARD_SHADER_DIR}" + COMMAND ${GLSLC_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/${shader}" -o "${shader_output}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${shader}" + VERBATIM + ) + list(APPEND BOUNDARD_SHADER_BINARIES "${shader_output}") +endforeach() +add_custom_target(boundard_shaders DEPENDS ${BOUNDARD_SHADER_BINARIES}) +add_dependencies(boundard boundard_shaders) +target_compile_definitions(boundard PRIVATE BOUNDARD_SHADER_DIR="${BOUNDARD_SHADER_DIR}") +target_link_libraries(boundard PUBLIC glfw Vulkan::Vulkan PNG::PNG PkgConfig::JSONCPP) add_executable(square_example examples/square/main.cpp) target_link_libraries(square_example PRIVATE boundard) diff --git a/CMakePresets.json b/CMakePresets.json index a44d178..7bcf328 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -2,10 +2,9 @@ "version": 8, "configurePresets": [ { - "name": "Clang", - "displayName": "使用工具链文件配置预设", - "description": "设置 Ninja 生成器、版本和安装目录", - "generator": "Ninja", + "name": "Boundard", + "displayName": "Boundard", + "description": "使用工具链文件配置预设", "binaryDir": "${sourceDir}/out/build/${presetName}", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", @@ -14,4 +13,4 @@ } } ] -} \ No newline at end of file +} diff --git a/compile_commands.json b/compile_commands.json index 3d1c45e..af53501 120000 --- a/compile_commands.json +++ b/compile_commands.json @@ -1 +1 @@ -out/build/Clang/compile_commands.json \ No newline at end of file +out/build/Boundard/compile_commands.json \ No newline at end of file diff --git a/config.ceg b/config.ceg index 140ee50..c2122c0 100644 --- a/config.ceg +++ b/config.ceg @@ -2,25 +2,17 @@ "window": { "width": 800, "height": 600, - "title": "OpenCGE - config.ceg" + "title": "Boundard - config.ceg" }, "input": [ - { - "action": "quit", - "keys": ["Escape"] - } + { "action": "quit", "keys": ["Escape"] } ], - "actors": [ - { - "name": "square", - "position": { "x": 0, "y": 0 }, - "facing": { "x": 1, "y": 0 }, - "mesh": { - "shape": "quad", - "color": { "r": 0.5, "g": 0.5, "b": 0.5, "a": 1.0 } - } - } + "start_scene": "main", + + "scenes": [ + { "name": "main", "path": "scenes/main.json" }, + { "name": "second", "path": "scenes/second.json" } ] } diff --git a/examples/square/config.ceg b/examples/square/config.ceg index 859a666..2f99edf 100644 --- a/examples/square/config.ceg +++ b/examples/square/config.ceg @@ -2,29 +2,22 @@ "window": { "width": 800, "height": 600, - "title": "OpenCGE - Square Example" + "title": "Boundard - Square Example" }, "input": [ { "action": "quit", "keys": ["Escape"] }, - { "action": "reset", "mouse_buttons": ["Left"] }, + { "action": "switch_scene", "keys": ["Tab"] }, { "action": "move_left", "keys": ["Left"] }, { "action": "move_right", "keys": ["Right"] }, { "action": "move_up", "keys": ["Up"] }, { "action": "move_down", "keys": ["Down"] } ], - "actors": [ - { - "name": "square", - "init": "square_init", - "tick": "square_tick", - "position": { "x": 0, "y": 0 }, - "facing": { "x": 1, "y": 0 }, - "mesh": { - "shape": "quad", - "color": { "r": 0.2, "g": 0.8, "b": 0.9, "a": 1.0 } - } - } + "start_scene": "square", + + "scenes": [ + { "name": "square", "path": "scenes/square.json" }, + { "name": "square_alt", "path": "scenes/square_alt.json" } ] } diff --git a/examples/square/main.cpp b/examples/square/main.cpp index 5a627e2..ee0ae0d 100644 --- a/examples/square/main.cpp +++ b/examples/square/main.cpp @@ -38,6 +38,47 @@ void register_square_behaviors() { position[0] += input.get_axis("move_left", "move_right") * move_speed * dt; position[1] += input.get_axis("move_up", "move_down") * move_speed * dt; actor.set_position(position); + + if (input.is_action_just_pressed("switch_scene")) { + boundard::Game::getInstance().switch_scene("square_alt"); + } + }); + + behaviors.register_init("square_alt_init", [](boundard::Actor& actor) { + actor.set_position({0.0F, 0.0F}); + actor.set_facing({0.0F, 1.0F}); + if (actor.mesh() != nullptr) { + actor.mesh()->set_color({0.9F, 0.3F, 0.2F, 1.0F}); + } + }); + + behaviors.register_tick( + "square_alt_tick", + [](boundard::Actor& actor, float dt, const boundard::render::InputMap& input) { + const float t = actor.age(); + actor.set_facing({ + std::sin(t * 1.3F), + std::cos(t * 1.3F), + }); + + if (actor.mesh() != nullptr) { + actor.mesh()->set_color({ + 0.5F + 0.5F * std::sin(t * 2.0F + 4.189F), + 0.5F + 0.5F * std::sin(t * 2.0F), + 0.5F + 0.5F * std::sin(t * 2.0F + 2.094F), + 1.0F, + }); + } + + auto position = actor.position(); + const float move_speed = 0.7F; + position[0] += input.get_axis("move_left", "move_right") * move_speed * dt; + position[1] += input.get_axis("move_up", "move_down") * move_speed * dt; + actor.set_position(position); + + if (input.is_action_just_pressed("switch_scene")) { + boundard::Game::getInstance().switch_scene("square"); + } }); } diff --git a/examples/square/scenes/Dank.png b/examples/square/scenes/Dank.png new file mode 100644 index 0000000000000000000000000000000000000000..498182d722cc3acb02e98cbf3e3cebc6c6be7f90 GIT binary patch literal 45114 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
    ngM0esJ{8^bo6SX+2}j=G|KDnzt@oQ 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 a#${6W7?(gk)dUeg #include #include +#include #include diff --git a/include/config.hpp b/include/config.hpp index 943a64a..eb6e75a 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -15,6 +15,7 @@ struct WindowConfig { struct MeshConfig { std::string shape = "quad"; + std::string texture; std::array color{0.5F, 0.5F, 0.5F, 1.0F}; }; @@ -33,9 +34,19 @@ struct InputBindingConfig { std::vector mouse_buttons; }; +struct SceneConfig { + std::string name; + std::string path; + std::vector actors; +}; + struct GameConfig { WindowConfig window; std::vector input_bindings; + std::vector scenes; + std::string start_scene; + + // 兼容旧的单场景写法:直接写在游戏配置里的 actors。 std::vector actors; }; diff --git a/include/game.hpp b/include/game.hpp index 2a83f64..764a492 100644 --- a/include/game.hpp +++ b/include/game.hpp @@ -1,10 +1,21 @@ #pragma once + #include +#include +#include +#include namespace boundard { + +class Scene; + class Game { private: size_t id_cnt; + std::string current_scene_name_; + std::string pending_scene_; + std::unordered_map> scenes_; + std::shared_ptr current_scene_; public: Game(); @@ -17,5 +28,20 @@ class Game { static Game& getInstance(); void launch(); size_t new_id(); + + // 场景注册与查询。 + void add_scene(std::shared_ptr scene); + std::shared_ptr scene(const std::string& name) const; + std::shared_ptr current_scene() const; + const std::string& current_scene_name() const; + bool has_scene(const std::string& name) const; + + // 引擎在安全时机调用,把目标场景设为当前场景。 + void set_current_scene(std::shared_ptr scene); + + // 供行为函数调用的切换 API。场景会在主循环的安全时机切换。 + bool switch_scene(const std::string& name); + void request_scene(const std::string& name); + std::string take_scene_request(); }; } // namespace boundard diff --git a/include/mesh.hpp b/include/mesh.hpp index 28f3eb7..341c632 100644 --- a/include/mesh.hpp +++ b/include/mesh.hpp @@ -9,17 +9,18 @@ namespace boundard { -// 渲染组件:描述 Actor 的几何形状、颜色和纹理占位。 +// 渲染组件:描述 Actor 的几何形状、颜色和可选 PNG 纹理。 class Mesh { public: struct Vertex { std::array position; std::array color; + std::array uv; }; std::vector vertices; std::vector indices; - std::string texture; // 纹理路径占位,暂未采样。 + std::string texture; // PNG 纹理路径;为空时使用白色纹理。 std::array color() const { std::lock_guard lock(*color_mutex_); @@ -34,10 +35,10 @@ class Mesh { static Mesh quad() { Mesh mesh; mesh.vertices = { - {{-1.0F, -1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}}, - {{1.0F, -1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}}, - {{1.0F, 1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}}, - {{-1.0F, 1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}}, + {{-1.0F, -1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}, {0.0F, 0.0F}}, + {{1.0F, -1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}, {1.0F, 0.0F}}, + {{1.0F, 1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}, {1.0F, 1.0F}}, + {{-1.0F, 1.0F}, {1.0F, 1.0F, 1.0F, 1.0F}, {0.0F, 1.0F}}, }; mesh.indices = {0, 1, 2, 2, 3, 0}; return mesh; diff --git a/include/scene.hpp b/include/scene.hpp new file mode 100644 index 0000000..79bcd94 --- /dev/null +++ b/include/scene.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +namespace boundard::render { +class InputMap; +} + +namespace boundard { + +class Actor; + +// 运行时场景:持有场景名和其中的 Actor,并统一驱动 Actor 的 init/tick。 +class Scene { + public: + explicit Scene(std::string name = {}); + ~Scene(); + + Scene(const Scene&) = delete; + Scene& operator=(const Scene&) = delete; + Scene(Scene&&) noexcept = delete; + Scene& operator=(Scene&&) noexcept = delete; + + const std::string& name() const; + void set_name(std::string name); + + void add_actor(std::shared_ptr actor); + void set_actors(std::vector> actors); + void clear(); + + const std::vector& actors() const; + std::vector& actors(); + + void initialize(); + void process(float dt, const render::InputMap& input); + + private: + std::string name_; + std::vector> actor_storage_; + std::vector actors_; +}; + +} // namespace boundard diff --git a/scenes/Dank.png b/scenes/Dank.png new file mode 100644 index 0000000000000000000000000000000000000000..498182d722cc3acb02e98cbf3e3cebc6c6be7f90 GIT binary patch literal 45114 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
      ngM0esJ{8^bo6SX+2}j=G|KDnzt@oQ z1!>y6;$D@B37~oP_IEkbs9xbC+)V^gRs}I7o}E$;72=5Zo0HqD`K5GzdrC8lw+Gx- zy!)v>mUPHIv8Dozk`xcP&CT4Vm(|W6UTWN2J@(=2Xhd0$Ndd^ocGZ+9+;p(bGp0V< z{r|84@;dWDE!WXk=cIhp4Ey6Qyoc?(KlDcJK0jH(;NF_t8o6ca0yXo6itGl}94nxA zru++S0nn;nsFkK&X{G`L@F@*RU&aXcO{)lQLj<*f4%2w!k4)Fdeuq~qlw3qjc4c{m z)lA=yJOl8RtxmpnG>SU}cza`vuGFLG5b#l$PX_{Crz_4$mWs=!>v?gi;*2Mc@@<|p zm!N^U-78-)sn@L*?USzgkb%t3t561NiMlnyQ6Ln>h;+Mb`E1?O^~0i?Weg!plnJZo ziplAmij?LbeMpPTZz4L`1@ndvKvXi65viRfJd3jhUdk_c=l=Cu?;jrc7z zN1hGLta9}y!C|?7t2UsI_$RTBK!?{gtYbh^Ga-9pRF;r^OvbFFw93k68I2vtwoUJe zL%qwA)}-|!G5=J(lO;#jEjap-8=OR)<_2XyqFZi;==(?F!V@4di z3#^k?`*Z?+g1&#OqY|_~6Q+qB3=}+{4MSdnrB8+0=P76Qsj^Q-A%`}M`-0X5>EhxW zYbCT&!s>1gc0|gKG^!j<8t%6UWeqD7@YL_358A>LH-8FK#22L$4i`Z(2*M?#mvy;T z4i-EGw2~G6B>>eDn?LkRm%lit(2-L(4LPVu@Ohqq7HN4rpVRynC}9n?`@_|_#BdLP ztJ0Es!@)%1Cy04XnH?P=1H1VgF%Uw`%vOnH$ehmCqM_{n#mD68KbN01WQI4dUY^i! zj{=e`pmTQc=VTDCWHzJS6&mhJaf|ZZlaKWk=cP*&%ks0L)xn+DWPs(OjWlN3OGJ?5 z-ya+fd&<*liCUANMNnM>(Po&Elkcz7U5{x|e^jZdevPpH$8IEn$Ys{0d>Cn{z38yA z+G3EvdHm2?xV3o_4?JfOyA4m>fu;Xp;!&rip^=fT6;h)m`ro|708M?7Z4V|fCyj$5 zh_|2vc2k9k))~r?zAP(NyQ>e`?c+bb1t0!Ac>b_X6~S@ik7izH)0m37^2(iV-g|9) zt!h%3*Mz?b?wRmuqCD{A2i0x=H2N>%PoiOhn>gHucWkO0PaV%aS$~2RKNShDct3ZL zZ|~Y!TU$;9u~WvE7_ps}?BxURSn{NWYUe(pcup$8iF}-h5!L;3rxI^4&h$$x(gL6&go)SeJbD-=FSj6S!t?HWvgyo-&!4j0gLwd{BIhNLJtw^c^^%ki zNq$hQ`sgF|YOASY6`BQv_!XCFm$tLIp>}&J<_3vRvFks3t^0rFrV8KC(#!;6|D2m1 zS1FB8dh2b#8hqrxdLH2~9D3Z_7{9iYl-x)w8%T+K3@hZsD(S^h(6PzlRF7sBY<+=M z5(~Bu2d0;@Pxv%%p%Xu@Ic)amS`xKtwuYN?O(vJLfkti3jg@O zeGW=;wbsm&5^{gPq<&9?%}n4mLn6syQ{n$AgJmqdrtz*TqBCGgk3;Gr0=uMFo2L=$ zz*fi;<*X_<<)RUZv(5X1Qm8btgSGWA%SjK)h0QFKpsm~^7dA;S8h!WOY{+AS5swi`9E4ud@xbhX$B34nzxCZLwKI$bAs@ z67?}sh=Qg7qscpd$H0Ck4 zi)7Y$0f!}bA-dnZaEIf!h?Mjd%<=8Y8PmBpQ7oS+f^VbMhI7mm_!>E1=wAObLFru+ zRLixl=A{Jxp`WX!Z|Ch75x-a{#F z!{X0q<{(Ou8E@|uK4v6*67ysU_tthjAY9t>OhSv(0H|H21d7u@xVqzSaFgZaBeRMM zdK8ICcRlW3R`Aci^}L}Xt`|P_D0l0USxe(*B_ZHL#&|MRzx(B{OCjn}=CuS{e?D&HH(rr~6w*&Kk9m5DZD^+TI zxUwn{=3PWqF98Hc{ALvmrGZ&%4C_`W$@hHu#M&{8FR^5_{f9}5u8J=k2A$K>3D|xf z+@Xl6K0$)Q@j%>PcEIWe~+vXr^f2m!2 z_TLnauy5%Jm>NjR1qP+?)s~eNO}(>Id?I^kS(;Id2||^$@HrkGDnX|Yc4@G`NQsNy zXq%fz{>}N@g1881kNJo*|J_bcPlDYtvo-sB(t*24I0|r6H`xRT!k1nZaMuS=x(UXN-Q&Nu5Wx?8iP zWntDLvSG#c?}M`%jkt$%JLJ+oazt2k**mSQXoW#03>``%@wp!jY9|`5N_ZvM{|#T6 zBS=hAKlxCCb(&7oW(uqeE3f3heZ(WNUeA`!kI4(j=r4X^L|857enuN4Vdc@!}94agSa$8?V-=37=4j}KrS^3h+Am#I46P1We} zC2C!5xLa^Ck-f@4Bv0wV%us3o+UrKsCC{#nCcMNf3nPEaZJ;J`*kIi+4kM{3AUjI_ zTP}ljhVuM&vf_2#;COK{Aihk+m80lJlw;Rw(5n6dieuxPIj9*Z(>>lBKhW0ssO-?P z{aIme{#p39NE0z|qFlPssxXR+Z&Rl`YEfDJp^pF_4MZUC>L`A>ECTqVZ765;5{76v zL7b8drq(T6S_RlhezZRelk{IzFc|@XHV{*2vNtTvT$tBdA3Be_EbUtWPx)Ld1)k|4 z@g0z2bKV+;Jok(H=UNpFcn&7{JbOgOA!EYC{;YL)8T#*)9AxDGmL$3yB~C6LAMOcn zkTg9;VMubJPP6byTtmru<)G`xf$eHgeHA9<^$v|mii1`2-@R+hOh33`#(w4Xi6305 zpYY97i@7PloW!oAQ3bA}7AN_QhP*15y0uPf~Dz(#rtao}EoE5h$Q^*7|nF{DOynOS$IEWFdg zEoMp31gdwbJy@(C6B{P()DZ`d!u6Br+;iN~2szS%ek3vag|x9neJtN=?O>l=5pLe- z98mlwZsP5;ftws4bqP9vFkvaGFy-WK)edt;|2t;a!ayyoRGoG-3~bOn;R7C!7X2bT zTTz>j$DSBCZ!e zj9#Rnu4z*OYDw+<+t+HFZ{EDOiCiH5q>0_Q{j9o2UTWt1n!y6~u z!bD~~6{tC75I}Ee5D-G4Mnl4`LO+9v%bn5ma9gn};1j^#I|F?k{b30k8~Xe>n1m92 zKllumzs;wNr~N0~sd-;45?I)J=)BU@PU)P_`=G_$cOG$5qqGW!7{@2z^YcJS9!SiZ zaK0r0O%O-5>V_|BG?!^yNZE?nF*U9e;%ivmE{p8H*{SUhM!AwY?Oo_`%4oKT^i z*gr?285iR&qZ9wzh~%eHkK}PW6)4V3El|~hdfG}iWfiX8tG(gIoi4{KEWM_gy3`lD zEW|{K9)cEF=a<(}X$-OdO`vb{!hO)?zT(ESYIqNLhY~-xvXQ!gS|6SCpwKL0kHili z7-s8Q#4q<96Ul$@-vysw#ZS!y@ki_4+u0nWv6s!5ngAWYN=n?1W+gp4I>;sx znXWsgxY0&!znA)s1@*P{lO- zf!_4rMwrzM^wzxMA@3rA>h`DY=J@)SlXs1~B2KD3fxXr(G_n+S!26ZmuNW;ibI5z$ z2(;3|Zm*fL9rsjHx2J;fjq}H|KHi|5vFE+n6sW$L`War^!X13CB)?!_+uZL~*N4?8uPu(+dn1EpV*xxSCbu>fJ#>7B;Pb~Y9H zmq@vT1490(WWsbR{AdgFU9Zu<{!E_Bnlsf-h-tx;ijh0dxp~C=X!2UpF*OjCCl9ap zHFCot-kT&2x+qDiKGI}nYiN3%DFig@x$^ZMvuX`_p~BwL`Oe9^9pwD?{qo4}y6kY< z#u)C}jp(LQ!k(Vs`#^~YGFgi7Fx|V3+A7Q(tp$f->#lr^igdL{TLB{mqR2-^AeSkx zrZElZ#W-OnYQ-23qB2iE)vpRCA>@b)Fh|V(N$nLL^EGtTh1gNESupnqj&WeL;m!fb ztZoyl&J`#2l`5AA2h7 z<8F)1yX=0jtFFqv?=M@r`!sM{4X~#-^hiBeUaO-BpMm0wtK!?d*v59b-*7BqcwzArp{>BMW9 zT2aSf;NcSGarSoG$V@NJL^t511kV*0WZquiWHJl$stA`>0gt>xgUsGyEiibF_HhVo z(5FP$jK0#lZ_K$;_anAI%@E~K`nJ4i6-+~U2Rx@dw0T1xWN>^iPZ2jPQNkx{@$uZ; z+nZMCbR_VM%JKWJr5AIPjd3eT_#DxE@%Jt}5tmZ^#)nsDVJ!-G{|@M`7AKNHj1=ER z#VpHQ+5f#eqq58dU(;=n2<~Y~r{qB5b=VDo2LMB{n=^GAACH9<6pDtu(>5Lqm za?l^zWC2JEK5f3KHbgh?_psE@D&$)a!J; zaY-+OiZ;tBF(vxGWrZqm$cIRN-BxpUJwfW=#>ZxFb!;8{o|wmqce!RQG;ZakH!4@e zk_4!7vI4IiCr?jGsQj(xpHg~Z|GMCCE{JJ3C*(x;-=qK{^~10Gh-8!ft9cw;wNe{2 z(Qf-OyMpsTZUy1}4<|#CKY*INpBo`IQw7GdFxbdxFiE)BM`zIQT{-jrqv@OD>U{tI zYpr^>v}##fUbeNkY}>ZoTIRC7Y}+UEWVdXetl!<|_xS$bIj8%&FT7q)T?u|KSNDG# zUiP4KzMnMb9FAW1^U!KeEaZuu>y`LAQ}l45%g7jnj4P=1g2vA7yftF@PEjDe1mJu=5)MiUXnx-Tp`v0M zPexr3I$<*-DzV)J!QW0rOFE&vJLhv;2M2>1cm!(hqTSz3uVjQlX79$?mna#e*5hIC z%k%8qjbG&T>SLk#A{f|P5@_PurOzmN1|RfiV0(c5T^gPZP~-`36@(uN@orBh`n*b4 zPfmTGw_Y`f$SH5wZ=j*0!%AFEV8BL^rdkb!RCu2If0+U@iRs1QM!5cQ9C0hl6<3Sh+)Y8%WQdE zN<#z3+-dQRDE#Oryxrp!*xut`@nIr+?%W>(tCMsYJW&LRDOw#JKgVR*Iy|*>9OT4m z=o<7oT<3dJ(p~L>P*^tDUJ*){GSEFYoE!j2c!pZb>Du$74e_9LiBrHv ziTTeWJ_X%-lEu{o^kX$LZ2iPudv}9A$**lbt-s-ymHV{8aUAKdRwx~+w@32!e)y|? z9`>;IxH<(EFM$4Io8?Y5o-8P$++6bWA!KDs|HA7RRkASe+`zG`nu3SCK~(W$Ndeek zoSbtCzr=Q8V2OuO(1Fs+Smfp9t|E({-r{kXpDtIA==~F7iy8jjBLl{ z>|*5HEu8#F!JaL|lm>z1kCkL)PxML7FUV?*8LJiu$buX0j9(Q-V+EChbK7I< z0OSd>#B|^XK%Eimx<{wDv0IxW5?)k~OW99b<-gbya53vFPf1koDyedBBs`>XbfiMokBI=xODXBytJv zN-Nk{%(``S)kySQtF8p0&C?Sg(+EZ2 zJDg`Qz|cy`6yc_of{FQKKI92QNrcz4MSwWGoJ;R^H)1!A)K#9Ou82qOdDQEtn;)sL zd!Ly*(=+MV8rJlQSO!68-sbBlMJWI}CPvANMo?vZ|Kx}ze`^NCaT?8ixGU^F`2#`M zi=L0iMDuarlww&b}wmfoL$rbeNUG)r%gPg9mmCJEKd? zA%qnzDq8C!70azmfdbZ++EbY^AMJx9Io!w@oc(^RWTEqz3b@u<&tTj)E})59oOar= z2w``v>fLfZa*@NWz3ha=-4QYE-;7&=oIiySZ}PnI^BN(7x_oB!7I3;9t1Wy#qw#zd z?j=^&a)a(dBbB%6(ZN=Y%8G4u=wiDq)Qm4pGV02rU55Mxa#k3poWNLPJaKx<)|QH- z)R`V%&l|j)J%h#)7MK|L*5>Gn-17S(&_1Vr@w`(~=md(owgNIw&W!lWrIs=ozZdt) z=Q8YSsB&;dl^wl2@=^QpDKT<{`^EU{fLH_tU5onVxF$s2sPZ^{?tMA)2T;1`Zd9mj zE{&n6qZG58rrbMhiRTzg)IfUmV7O2dwN_m>b0a0fs_euiz9&ta(Q(h+>;iDTcYEbc z?`uz|nCt3HHUh69i_Dy3`TZIu%rsUFG}@%6m(*8=(=zR^Pn~W?%&kohH+@k zUaCrfd0tMM+gJ?jPL0`7we|4Ic{6J2vlb)xhUbR*Kg@ChFjsdfUZ>BT>D9yX9^~1p z#igrg>VKGrcS)2$?7C|KScMMmDJO;GR2Gx$@=8mtP9Np5`0V@YzIeK?;FFw+pDh3) zp|kGnoL+*E)AL+zp`q}?5py0B2e3<7bT7R-AHL4R*$4;`c1T*@dE)|wq=Y}PlZhO}D}ZY83+mppsm5lijagEWC?X}T zG@=$Hj>`_TCsYP#{Syh6HJ2|IP+^uZR zlhL_a7>Sd(fMIVKvW1(j)^_USq>X{Yt9z1T4pf9mu&0=r3HR9>zHJxs!Rt8+C}%33 zo|5=?etOI9{|4oIE>bU{>xj?*8X29-B#IBDHrJ%buej!IN!jX=UPt-|2;Z=DJbKiz zwJl5x6Tn#&&G9Phz3VCT!^Wq&yZuJq>5)c`TV?Il%P+Nbo;P+gWT1 zd?v~1aCBL9e>3aO`=%J_4kCSSZ3+?4gwT7+&F#P1jEuAj{IN?<^QU3V%f>tKue7iK zae@zl-nHaI%#bo_D^qC2$84%3oe2A$SrAwb^g-6Yk*igWn*!-~#4GR$J zjg%{Naej!n8W`Zaiz>k^y0UESa4S5iV~a*d*g#QcIu5^YLLJ4s^~*+Y{P!U8#ubyk z{R>HS$<5T)nYYUGc;I?huS8&Cg|KR>eohk5^0amc!mk}vEHj{wvDao*pocm3Z%^X2tW&Y`m$aXs=! zLfgM@xY|wNoEf|wMdMD*3L!CPovr$6Q3lgsfeY>|5|O4pV2$@6-CIEH4P|YBq@Xd^ z_pmfXOE+I`R>P&%pIw)CX;)4nISob$4JV@#cn)9fPM>>D-q_&_7KO#c?rU9?nP>8= z91e0>)c>$`00}`M-xpYxLcsxSSq^FdCIn`}lU(7j<`#f*k^QC#uP^iSZ>&QZy9@q} z4j>w&?err~rCI(|;A~M+2Wfyq43ajBiq?3scuy-((uFs9CZbpGDqaqIDxqORzs!Ah zK@2~By*E@oQObTtdI~!!mQnrgE~=bRce`r*Lq+IWlcj$HX1)KP9f;us?4DSxChHrE z?qJAnARf~ZI20JqNV>|mYb&Kn*c5mMF~KiD8$|Lowsu?9Z&TL539!AUwX+RoZF-0l zti|46$q|?e{{3F{n|blLCD6On$&2Z zCp#Bomr*6icU+kx%l|!=ezh1HA=g20`N|=?Iun&X2ll} zR}y%OwHgJ?ODfPEj}(%22}t>}8)PTCb}}Wp4Cpgo6y&>}u3sZ@(CE@`yMw}sb&0KK`OQgDcFd$fN?(BjKp}~Pc%6Ik z7-}y__OPw(zkdW@jnX)tSDrIQh}VZoUOn>j+4XaQqz&Qxq^bxkSq;nRmq}v|JSXpI z8UYzrro2wvSsbB-3I%iL%v0`6p=FZ;>+@-v0z4l|NlU9i{-n|yz+AVo{&%{8t(5gt zW{cI8*XKXR#%BgA{hz z(R;L`Pm~eZ9s0*QE=K?9{w>NL_iy&C|9YCeE+U1MzRYLHLOljc+@S$cK-I|dmyeM{ zyT`pX|2>FYdZFgC@#nFnB2uUJL|X6lg`yL&2mc1nToCArt!i*s8r>&69NfL_}7*-7OLJkkziw3ym6Tc z3^&1;-%VCpQk`Fl(td^DC`YlN)Vo6#07Ab+w+)txNmI<=^aO0OTtw29u3YcxS3;}9 zU0Zj<6Cfn)hTGcSoF=30E8BR~>3Tjeg45s-GY4*DAlWbO-c#ivgbiI+E>q47+_uRD zkpOGJ#G-1RZpl51jHzg1V-Zr=s9g9t*)UkgW^Jb^D{D1PZymEqkDK$`Wg^`R_GmMo z`NH7N&J9n@PFKrcAjHkosk9w)H_GoNbTITc5Q&sm;q`mC<6hwQ45o6}$_C1v9f9mu zVD+7t2rM+VtwVjZWkciK^73tC$6WM^Ph01ijaJ)PgV#c<=ACY{=ogbw6rRYBae_j7 z$@$4iibGH6aT$^OZ%_AMINQ&Ss`iGt&bcOy6iA{WnT*-eF|sAKt^eI{H|!8~v-6r~ z^&M4w(#F>079pm~Bjy(xTkI9jz)VJHHZ5S#IOzWHBj zQ*M4j^R@%U!T6`NMBVh%+p^zrIhC^uk1 z^=F&ar@{W!m(qgCv(w~AqIb5)^lBng?D4$%hwisbwfAsE*>GvFb zH|X5n`_vpOaogV2_OBME7c8D)_js~ig&XOasr;QjHZ9IEqMf8lMj>f|f}tytnnKeR zJO9#f7uD`3rb>`Te%2e>IM79ecf?)J4ZLi`OMn#e!VpuD8W-f#)sVQ*#Gm9egiQNB zXs*|aseRJ7nOI3e{`Wha1_VFZf#H)g2^PyO1U%w4S7q2diBXuDd{HWnCc@;)OIA@& zHL2d;xDCl6kC`T>vE;qKu4De?0S=#MUamWj#f=yl(@+!A(k z2l%Ng%7ac^6hv!n01v`i%Htl2lp337e9e6E84;lF?8(WaJDA*?l>5oSfr7~tc9Cgs z6tr`nl&kGG6voN_XSLPWV+O1j;%TD~FZL6pe?LW$`?E^|aY}rnDt|lt>MWQ#v9PW{ zyy#%ca_&=x)co7FK?*?PLb#3TwaD))MbGacXM<(45|liOSUauSRRoXbi5A zEyAPnGHL5BJ!!Gc_NXZkK^rgg<&b<5G>e;0rV_bVbLiXeU0UB z;6oyI8!h-%{AEzsgx6Ly%)goR424Y`NQ-WfLKD>}eclc&6Zt++sxQ5c6HG~tOk{`` zP0D$h$@$A&kx89Js9Nq9wKq5&x4;h!SDk)ps=OK?#it}M*AUNVPT3m}eU#0JA==F2 z-Z8G?`zsQ;e)Adj7pu#cjNUmm7$g)zo_vMFz&9Y$!M`B~E37zAW24m6e13H?D|?Nq z&AI)D>qD~;d5I|IfUB;z<5@tx%M)FN|A47S#9l#rqs_`vL+U*JX6dwRJ)n``jV$#} z#SeLHN9)AcOO_hKuEkR%iAGNIWJz_9ml$QLDXuocgYw^K&~P%hXI=g;lwX`>QS5Nn zdS>u4zW*~0x73ugrIZBy^u8F=vWW{- zSg95y-bzb1chgZ(VuujA3epxntfv8fewb{GFa4+p;gU!#25gV*W+^vKU>8uu{+)_s zw8jmZh04ldrczYQTk^q>O0oUwYA&r@ye!C|7edPnzk6(g9h38&cj@@UQw<~(Fz_$N zvz6w_$oXD5g|#1)v!#m^&ra_pO>8-dOxA#}oSW?iv}@BFe^PG!_a=cj99d?I_h?|M zPfUa)mx2ZZwfaGFPic!-x9fZ)LMlu=UX7fqA#*Iama*&dx;!JaMA=c4H&V=DWr=U6 z65%AO$KMPH>AFM16U5k1ggPLwQkg((p^IgkGn z-KzUJ$|_z>TP%}(NE>63IKC$cmkFx7LR!{+Cts$*EvjBMOm(&o|3(3)d`XNUI>heJ zA8fu)a|;x$6eS`A-_sbg$C}erQ;>O>v3P+DyJp;9YU^K510lua1q zhGe~N&&cyqpqJ8pB(c>GVT-XIQ~%z7>E&<#>W!fWK<`wr(Nx-UHSp2xP3(sa1Vs!? zj+Q;_66QGshAqaQ;*)I+#*pKMg!fAA6mW@bL-8X4Qft=IqV8~k zJyDgO?SPHCLt!N!T+)UtmMK=G=5iRtT_Ibfn732F1}T=d+o|gC%~?R>PMpY@Nu}jZ zJPqm`Fm?1fa7IN58NM@NYWLwH^43$< zHLp;8o{m1YEUo{Oic*8noOZr)6UXwC3*H>oo&L2Cm$aQ85f5YB=q2iY?Q^fZjsp=7 zXTTsr=mq36T+|oBJBuI_ zS%5*%F&=>SR(D=3hxMoV;gYeEV3JIh*}tD|+`qq!x$8l=7Nt^XTsBCk#V~sTo!KQy ztSIUN7Bn*d!rd3YjeRtVAa}7DMCs|3=DQnA{$G19#<2Al3Q~~-{=o?HPc^C!yTP{+ z;-TnF=w}?ZXN(w3iFgN6g=tB$D^posDdq&-5yQrPkvLG4!MaxIJmC}bP5VTp@w&Xz z)NN^2m36Uof}h`ofXM zwAGc{jDE8j+A_Y}ao{0)&OQvUHrsC?aG>jqT9aVelZv_5euNgmwn)OvP(-aG!>#6{ zz(^{Dwr4M1HIf+}CRB-w@0ychAe=i=mRx08!l1@jnpW_H_K|;08C5LtmX)t3W}xra zV>Hyxahnzq@HON<>NY;pjO_&GEY*Gk^~iOoCaL3+Z?2H8GPC6!=ORvB7^gaCuswU^ zY?Cg(m?`kQGW!VjgHTXk3uo?%OEqi#%FOv&ODf6la2P7d1OO72) z(4JVQgE3ObUm`OOCgsvTlM!St`LU?v*zb_lin0%xTerCfO2=$58Vp!RU^t}69XTuZ zIO}rDzW+}D)zttY43xe-Vd-*N(3TE90B-nHfvJt(>kjiUT@r@jAH;=n0z!JilYua> z!x+NS@gtvqcqaK{XB}+gqOX8;^lYQN@&;kPgK4t9;bEV!SYBaX?xDgzb*73)pFJ=x z(|(Oj)cuMFe_^E5@+TD9UeM*VCFH8q*8`r8Ps^$ij_ON2Q<rD!eO`U{mp~rrhke)Ms=5qBG=P9i@P_Ft39&+aWJO<@~cz8oCF1}P?{3sKg&I7;OUQ- zH@yPfY@nc?%wzS&`E;I#y8td(b~_ptNiGvIjRqW*9}&e&P`J=S9zQckkrs-0XN+?30L5Fz$hmF{$?K_-IQKCkt=dMKm$COrM-@gC;+NTM1yvgbhGH^QHh}(OpvS`_^PO!tbS6ktpRb_@TT=#g`46u!e+tf#4vM z_rPfj8iQ$BWxx~>ChUXxAuT`r%26iFNeUbksnE+M3){YuOTnktCaxxyswbX<0@$nat&JT01`7VNeB0raicBB3=8iw5pTHmV}c5{=a zs^WyM?Z4IEk~CI_M@gX_yf7qVQP=yFraAe4Jv&rq?vDMWKp!TnKQimkX>vE5}4KarW|?)eAaPl&odGs+vuS*g^%4 zWeyZEYGKT0HzcTLyoSQMQ{_X-m`$KeD=LB%F9WVy-)Hx_K|j&<6ee>e)*wShi0>&w zn0hy|oc{EZt_X*9eBZT!*>(>k02mfk*S!1RY-^gSAKeJLnSk(rT@p!kl~Q`kC6cfD-65YuTJh(OR1qqjH|pjco`6XAudyD zME&sQLv@`2coX=;`GBOqF_@KJP{7yM4DGOq8V?|Ffl14#D1DD#o~xv0vd~C>u1SBV zNWA%Fq|dmRH5Gvabv#V}YEMTi@%wcdNM{2+;l3&1BXBN2DofTX7^6zBV!@*sf<@Fl z{Cgm1i4jUwQM>E7Ometp92mi9@e<1laZE~$kK{qH?ZsWZaA)yARYP1=?Vg7(AyN4Y zX0Yehf4>h(T{t~&Yh*Z2j;Mza-dCu(TWqqhvix?T-;Pmh7HG{1E)+0Fbji`Z{5iPZ>qa>XWYQah|nvRc719y#Ol(&~i5pP-~x0`4J z#GS4fb2r2vCw}(_0)-dSV1Mbk9!694N(2wezx%&BJLok{WQcMUlLPRC@j7U&{K%TWV-Ws2Qku!L+EfWV!YNik=qk zef0-6?>?`S!rE;_VHE+3KjXL1oMO?O3rz}=Tvm$%fBRI|WN1f+kRcq=Yth76u;Zux z9Ae#+?seXjbw*M+{ZL?qF%ZzYAI`65RzUqRVs$M|vl3}PP}mc@Ap(a05_rF#sP45>8OBqG^uSfw$8b-9JBPq0n zK!Gtz@=Z$$^t!_rr)dTI>0~e-8Ca5E%j()Xy~WRr>XbD#5P^?7DVAy;hYsXqjPBkC zji>;oatx*wfylA`wpqGBZ20I9iQi5js6XG7W|Pjwd}zz0X66NX@%(>%|0%24lHq?L zfL?<=%8)SIuTpW-hk){Lv*&$PhyZ~K9&L`<8HnWGJ@(({cF>t(Tzt^I>CmpZc1%H% z$iR9>!=C__yK2O<<=NRP<$g)SmFsE^)PB)PT1e>k7q1y;P7-8>`QV&$RjKq4Y@%uo z_V{zDf@yPLVt1qMMl|_)mqO|WH`OVsDGEv}!1}Wm7RqoltDHoGRdOU0HLPoMGwc8D zA7-GYpKgcnz(ElpQ_CjkJiiJ^;otg%@L>H5NAgT8?EsImeC=JOl0#Kse7x}_x&0~& zl3Xm_P2iON34($Y99h^cdmRR)1EOgsU~*SVt^ve}C zzeG*~7l9+}UbupFzZ)FTbbhAV#IYF-+uUen7KYF`?5KWllPYQA&gk)hI#G2MQ4PDC{fyJ~`@+SDh>{%O zN%&Khb2JTF^ZNpVZJhUr~(x(N|_xMvLTt43$xrkfC_Z1E14m>wA@7{S5VJ3x-`uZPV zAhaxAMTRoTt-hlV4gp1r$SRD~jBMCAx*!Q0yV8O1LCN^WZl?!B}m7NgS z>UVceJ+RJt;{mfzT`IY?BDdIgFt~U5^esqMqB}O#(KG~X&iHxm(Tu6(L-64nn(H3; zpmkUN%i2SA>biX}PIDmp!(7C}mJM+-8_|+^&Ov?iHne}rkE$6L7iNT8gBl!d{=Rrn zWHY?0G!J<(3$pevWep|ggOOtzleGplV0Y=3FLoY5$rBmN@o;A@M#4%zFx2eOwCI@f z@38-OfNl>hWV2yZ?AAsYS$rgPcZ*hpjh z7x5#9;goqIp@_iw?zc}_*_Z3DR|CQVrpeybs`_;f!|5c#e_=5lC&O2Z*D-d|#CO2S_IQtZ$&X!WRK$30dozgVr4pfqZf% z(z8)u{TnI(2`}OJu6`b^kUGkGZcJ-hSQKILZ!DyREI6!qnf7AkEWM_5W;>hEVK|G) zhkR|VfYCgm`DX0wy&TdxY8=RYZr5C4`zzv2n3(pZ_Tp9E;PGTT$ZkUkxbVXJU;(Q8 z5s8eVMvOYj7N{wD$2F^2PcxdW6&TNO{Ey!Y=d}4cuTtPq7bGXoiDhSCqY6j2KKTN@ z(`;Vkjd;~$w15vmngG-)ge(hfrgA*A1T+~M1$>Ef3KB}P#J;`5AhdS8B3(&^&E%5a zjH9QUj@KDS5XHi+17)pixUf3C4Rr%xtWaV~{y`<2X2 zn_6wa`2U)slI8~E{I4|`0F(d(`dtN>d;QVImF2>cG z#dy^!#m4W{RrRh(x$yVY#% zH$lE^v1CCZ!DouKZ&@>@pfhRO<|+M$Cm{a~jR#*njY7EW<(rpAV2qi122e*_Qhk5A zHXQt~BTw8x``-a}=|;IcN5t;PBeR8@jD(f=LjjhFMdcWzg_LaV*P>?;OZ>#GWK7Fb zK}vV|%4CA~V@mVOs;<^LYBL9QkXTA+l)0ESBYKsAQk3&gkZh17@`Y?xD9=Dd)o@R;E)N=N47!ITe%U%v0#{U~z5^(&UwRR3e9$RM+qVp+ksqKZyx zds%0DepzLCep{!s>Erc~3Nj{VQgVoJkx&UcQgoe?Wd!ag2)1BY%FD4~t?Pb433 zji~dpoA!74c+})Oh;K{heJG;CP!6JePhm6mE7V$B59B9H$Za=QDxsxVC^}c;#mVB$ zL?hR;nY=ND7bAT*M%#Ay(3}5KL>mUlwm0&>JNRCl_;o3J?x2;s|CH-#^yv1@-?w2w zu@OZHE&9K=OOBkF*4tI#mc)&YWilGhAX1cnn&!q?XnHH{*jT?F!oW~|cX>8PWM z65E-2@aP~5t*^mjD-1zW_lD+zh~1TGwCCB=PG|utAXM4cg-EYyE}TsYf{*0~@wlTT zRwD<31H9u{ek;bYfYbj3EYlrJ;obTolC$*4$7%#k1LqMi5q}xMWg>?yLc7<9_6+B8 zR7jxcJ%9Cl4do$H`!xYB5?g&Vc0GJ-Xg0z{VMwCdDXhOk)nq}q+XZ^rE|cDUF0NfY z{%p0n2N)}cg1hNM6`f`b2L=d*vEx>LwlQe+Mbh9jdJ^#nP;m1l0i2pWiZsO~5pW;G z+V~VsXu2hfIjwry0F@x4L3IJ@ z(-WELzv#iaT=KtQG!6mB&aGo?`$1-9X}A?D-yPfta2aq%nQIPSAW|83yVoVRMjL}DIqm0?h< zX0@SQNY2LQVe`aMGarmrh0gVfJ99WK{c*Vw>c{j)uZ?$d zK_}zV;kHsLW>U3O>$t*2C$q6Aee0u@{jc5xivyUKr3kC0x29s>NwtaAybsner~BXV zW{eJ0nd>z%4S>S)`DPKenjMc$I)&TG7?k%!`a;1FylcigwQ>FTk0`1()>|uz8|}`N zIg2>on{c!Js+R%lG9UU=-CTH zc)uceZ8F+GdWY_f`ZJJ4ihJ}~PRe@+!@2!GH-du}$krvX+4+!%v!}S5&x3uH86)S} zbUoLB3-hwyBi(^8a7$7)!|Q20HBfC#sIDQY!&4K=m?{)fkCs{@N)e{xgdRnp-+_AxF z`7LAzxb|w=@a$1w)d3Qs>W&rd^Y(8dBc!M_1gxfRCeJkoJhOSWxaZgr_DtTnx9qB` zt=^CE`Iz_UfbD1?c{u%4_{c>!UnzH7bBDdK`jEHhqSsMCNOyK<(chv8U_y0*bpP59 z!VLXDhBkaIqAjr=UJvF;dff`BQhX-lJ9QdqDINK zbd{;2hz>a{pdbHyx0H&&ceNHQqn9TGxzwcYP-ydMdhiHWkl#%t_6Hzq8QTNr<|Q;s z(jeSq3rq*qW;DDy1)F6*OLS5wsW;^+C(XmsIW3)*1;ab!swBq$czBuTa;I^1jmP7n z-wSJFKl)ymdyeV6sjW8eAAh*KeWOwtXKUX6FyC)_9jBJX-N7l6B1e_@)^V^@~ zuc9uqy_KB*MQR0ji^&G;cTS*YuAyK?XKDa7+7~z4|2+3NDQ^R@yWD*4rNssdecN;U^)6Dq zKrF0ToZ=fxN}u!H7CNhE&Q}ZXdMYP{K)pQ@Xa>WNXQBQ!LI1Jr-Cu4GHja$aZ-)Px zLbZ<|b=Ovzs~E8>0Yi$;_iCvDp)W#N9swbc?PSM!px$kV8` z2!7U)=sRPW(`@NiUo?t|FM1>IP#^7kF-|=6v;=7oOJYZfC(D)~ z{Hd3o9bz`eDH#Z9?U=7` z)}kYo1`!lak!PdjXWD%0t=B-yi{#49gDi=>5&C=m7?f2swDn>J;B&v_yVzvouV!w zKP*T)u*ib4*|Q%8)%p6LBNuXh7g&&x_zEr8?-X^WBsAZ?I4UvIf3bVB1F%izv*zyX zcHZaz%s@JPHpmAH$6?Jynf9cx@e`Z1DZIZ}&UP@|#UlT^mdYkPOJ}0|nZk_xqG3Py zF#|qrE($^Gap>J2-*B2)JPQalUb(65kH!yJek)Wt&2xld0`rWg?Rd0l|Jlmaa;G9D zCZc6urM|~Od-W(e^p%HOIMYtN$JqTUP6%r~hCj`u-z?(T&0BZf-I@gg+l`2MEjalQ zHt=0JP7`8fM?!HUn>~7q8qfWM#55H+S02;QtISuCH}epjTC4?K;|2C^Na2iP?$0oS68RV`g90JjRAaujxUu*m;m1*_iG!zLcy`~`Zir$;k5 zAKmeRJIiI+^E9rj)T%*|wOiu?GOzR&vkcD;IP@CWsSOMW6ZvH>suUjTvwu>h$=VkB zszVE@9Qx>VXzjk|)09=1dS%?~pNDV!lwY+=i^rNcx$%X@!TF*}$z;Lf8kl51GdF32 zQ2m_*(~HD{*CCVbfM}|w|3-;ajdyz+6R+#IciZXcZ*nx`jB!2vZY)3)mzdD4klJQ< ze6SHxHA4p=#{U>56gC{!bihUQb=iBLz zl#MI3&@8i?u6r?@lA_-rfAh0V)Jn-4#y`SRRo>9DEAb$j+Rle?civ3If$?+V3OhCu zIEdEGN5$#RA}5ifzp==c*gjn2)+o+t+A$v0i;T9cuYOj~ZH6Fz{2dPT>8}43IUb znK3$l?wUPV$qwA_g2J99VH)w9?mbFU;yLlLE6HoMms_tx#4hOY3;ki{wfyvItjtcd z!nyID^}pW^}yol0pV9E@m{aM#t0g&!(*W-m4HWJawxg*!e!Wc7U|Y)uJ-n30C+=fu|Sx z`Tl|OTMNhWq)54Cd7=FD*y~qrzvE#nxaFd1XrY4WF!jeF(7j*hu{i^Gw+}2r9A!R7 zNOSn*Lc`vPK+4;QjniOzE3C?EFIu1ANP6_MWz_M(4fkSs5SpcP06#^Fu3I`!#H1=4 z@roh^SF1z;#X`yCnn#WI?_p=j`0vM+x38};BIPHx6b%NWAsFaZF(Q#8&n5-Dcf#)_ zdt)aylyD-t!!ZWD@mt90xhbH1Kc|#j5MZ?dZNK$n3i~C-Z36tQCn>EA&xhG8rXfl$ z3?FGp^{39})78#^)(wF&t8qT_b3XJ5P`cE!H%_bz>Yvc6Q)99opik>mNb++$*o+Nn zomlhH#1*aH~80IK4(M}=7Th2-kKm-8v(#Ia^s6B`YH()kIRwsl}CNw+7B4<^F% zph936yT1r!!DNIwgYRIB3e$dF9FDs|-Haq{dMx75AmaTY39^yfip+1{Oi@WHK;wf= zDkQ{RreWo}1j{LssPQ@#>@BziZ4u&s{q`o&9U)14CWf+x1)k!6j)l&BGuFXPR7^X;bIpdu z);yMpCWpsJaLiSrPz-=%g_Y%U^G3oTVj+W8_fD__a^G=8cn7a>en4KX4MB3f=Xr=8d0*u+O=+ERKv>uF2duX*nWo=i^+De&KMzYFL9Nb#@XVIu zfOjjmUCk~#`*I>neseMPw%rZT@%@n0)I`C&@u#<#2$YmT%5-xprlLC*T#pzfyNV-N zMced$T!}C7T>z`-&EH*jkr@IDn{(NZdvn~oC6+YZ7`7-FZ%1D-UyXNW3Q8Gv7E7jYaz_G!YFS;K$wPO@kF#9Eoz?jHx74! z;>`-XuZzMkj;H9w-N9%$gq@Q+=#a^0R!NWB>e>nFGqJ4uhsxSym>=nbt~#EE6Vs8C zvK}6AB8ykLGK$9)kvAqeg76e($#@-C8-?Z40$Qz`768B)QKg1dGQ$ttvo-1?@Fw-) z0` zS60^r`fE0~Z+f=M%xU8&`zJU@c?ozfgI#nl9#90s4YkS7e-Y3VA>^7h5Y^L~D2@GNmVnH{3r#^dxkTmDuE&Y7C(O7}!G?K?gpRPGS%+Wd%30 zi_Rs!zTmw!lC73DrvZ!^_<{eMfF&6<3O~fYO1R)*s_vtd9}$#zJi5u!zfjc1@Ztr1rf%IoNy(Hs*39m) zoA|2tnug=H3^7EQGw<5i3vBy zrJ3U4iuVrn@s<6A>jJuj_U@x`Y0z9?tSFDLd#exV~X#NIjQGt~C~ZqTB+O9u%J z9>d*goVaTL#mNln3~eiR)_yvN|-o*XEfW)vi!9hQEu(;cB5CG)Q= zhpCIt^+hg<_QQ><`xc%E$hAygIp^rl8Z&Qea)-n5u5;>>Bn^d6#(Bnl!Wax0 z>olU;hPGGDCskM)UDM+hT9Q(JTS!_!#V{S?ms-+ai#Rf{SXRmHiYe}i5m9xCVKgNjLFvhPA2fF8%-YfT>-or^Fg-9LJHt7fXVFvL>e z761@o1jhJzf&{Mb^`|kj4~FWb5~uE0I_<0^&>Ob@2<>h6bq76 zGB!?Rv<%0dPKSLw3nBhkFCdB2P3$?;(PX(Xc5UzO6Q64p;-)1Z#dZgA=U9~#OAIHYh{bk1qab@ zb}jQboe2s+#{1_hSG+U{3byIk|GpRDOQE#z>3>>)$l+h)@4-f!ZFn;2lq8dJ5$oAQ z{kkMXVV~3AGA;Q)U4ZUJ+lP4|d`49z!tVaC@&kiu=}quJ+P>3$WpHo6ez8|jkEo^c z3siF)lYta5*X-p0wf3F(AT=kgU2MfU2DN0i&G_-*_iByNIUs$js~;2Hq1t{-nRy$} z#A6WtX+0;bSw+_Y5RIQ)=-c~@=@6($<^oy?=U4Y?CbZs2F7tFRExDO1t=>^>sH z0Mm8P#;wu)gwiB#g1h~im%h_WyBI)VaR&%Z#G@$69r&E8D7)E2p5)pdjE5;>xbMy^ z@CbJW^AxC(8;3@(;Q-kle>a>@UHyS{;M+a^_!^Xp%a2|9bQ75D7@>f`?=ztvU~fN4 zEb2}z^(y2>Ec~=j)BPR67zqv_a`hTbd6H zyM`wh{_bo~ryT`adf_x+K|}|{yviv|E7~L!Gx=yxYKa7m2ftR2h&QGHX{Fajx?RYA zUx$#0McpR=a~{K62+*84`!?ctWoRY~+Wg=cV|-NLF*xS4;Z2g1ODjt&oxso2bhV-e z_ehl4Ylvtwbps1!)?au-Y16W7gNx;&Jva(A~Q0>DO>}w6S%w&|F5R6 z4yd|$zP^Zplt@W~lF}*N5=sh4cY}a*_k+NtyGx|I8>G8IO1itd-@Sf+@BjOW-JRW? z**WJ-4!wZR$puQ?TG`jU_x!I5I_FvGk^2*~A92PL# zddanh;t6BFhm;J@v9UA40tuhT&>76|1Cv^O7ECH4&N+fW)l%Of^+Phr7?)@ojFw>D zunc6(khGw0#aN-{5x*A3Hc;3UQF&x}DgS$v^jB$EMaZh#yl5#Rw{>BE~a0+pMM z%FFl1ZD(ai751gzLa%6rN}r!d$Ci#IXMROGkSl22qORtGZ^Z)dT|897_A*y={*q!0 z#tj7$50A&LOQnE&qQiOxX(gViY(<3t!n^6Ptw?wg` z<8qi^$V(mDz`C5!c|rw+z`A_4r!0_>W!)hvskh0KM2=r0z1(W_ppETX#&pbvw5C${ zF*FI*-Udup^Ekva9QpRk{yn|*#jqt8cLg~gkeL^kDuttDTtIR4CD`QjO#46>FoPVh zvxd7I%@t;vqFfk}XnsD5n=fkNYFChN)ojfSyW826P{^16ofzzjf;+l{N(Ito(e;Rs z!)LLbQm9RO)x{|8$1DsonbSV6S7y|J6r)$wIWpL?9z+qO)%H;Xx!S!}-C13Vb3SZA zW>-0~Fj*AFlA1%gFcja*`zXG7I(e2EK!MsSv@gF5KOKS>cudUc#uYeY3UF?R-RDl5 z2*A_K$Tf8Xm-Vt1t?cbS>omoKnGz9z%Q<`VxI-#dAH3pv?8Lf zh0F|m#HL>tqxy_9DHeC%)bl?BWftiGCr|Cylo?jB|CFKX>80`3zZd&7kc=_Lk z7sX?skqOKAwZBBA>ld%zK$sz4mpj7KBc^CuT*v-S8?)VrP<2F`9PGV%vDWUr?XE%dRa&N$hu>|^a6hh z`z4=u{%8=+`YVetn-#yt$%fq&$7*C8jIt>Q-B*W|+4DC=sxlfj{#<5g1AZ6Y2X1l2Bj$F7`LEwJt)~vu zCqkU_z$#B=PULHYMi44*1$*87QJ?N~vCl7kt>?evu$mn2e`_bsPoeMhW`)yCIKcTV zpl)p&2REYnKWU6`s@YfzsfJHz+!*AhSL`-qIbT-cE0p_zqaQa94{>7U(Z5-{Sv|;W z4N$^-T>@0_G8DYy{=DWGC$gKSFGIKQehh-AT>TL-o5k@pj)SBu4;m^KSf>!xY}pT$ z5TJ6kW>^*r`BGrHWP|`Q2~aOc)OLTPx~Iu9jrC;A@O+32c9i?yZn3tXP}YJ>78}k7 zR1vzeIs=RoeMNVW(huz3Ip;5+?F+PwoA3A~S~TTYqg0zV3vAMoW=s{v-Jt~CUr;S< zMFeN9TZQ|5F-G)9YaM>%BFvAQi=T}(tJnX$3N4ZP-UlN_ zeu7D5^ik;hn3$$y!v^x_Gg8>3gXlVCMKi$3l4GCC7d`S=;9` z!GhO#EWCW8c25jIVrjppo`*Ifi5d!Rl6^J&VVWXIZk})6Y&|-Dr)@(`-4FKW+~zkj^OK*trLJv6VghjGRAEZ zwc>Q-9gCqy;Hxa!8?ITqnxd0~eUK8M%w$5fPeRBUeBqV{i{zI(*vTF1M2Q%sM+hXz zIJWi72ofF_pG&Jl=>XESZLIA-_V&t--buZxdr|*bC~w1Ff0ChCuJ7}{=m0xnNNc*k z#{0PeI{nSIp46P#tDfG`7gzH6+ebw%mO(*u z9hUQ~MWP`w?E5!wz=1u*Hvf!NGyH9URHsE;?I(~av~(S7kT&L{(Dw!yf_I+xb$^sy zW+~kk+)x|$MYuv4RcbdzilUc z55(Wi2$0T(5@a|qI>y|58n48s<`UC98H6NVt8Ltf94zk{H*g=Cp7@WKs(&7C~VLfzCUF#ruVMI60I z#><;!s*VKGTJemOkEth*h5t3ef-e>Y5qG(^>(2WzRoE1Gh~CJi=zMv)Gr6prhJn7K z@B`uo04>6<*ha39LZ3pX=^`_+?}^Gw_BBMvH1CA~(Fv-J&b#(OYjpM*clhMtx*^SRI!|+NI_txq4 zYBEc%f~`em@u5$@8CAf%XhD>6h8af)2d&25qOw%yCr}~|EnnX0K6#KA83N%Z?|8H5 zm2rDF-Fx40WgBrB;4AzBQ^YTyZS*sW-5M%S0)}r(5OP9QZ(JZ3BZc{kL>w(}Of%$F z9i?BYA0ZC*Qa~)nP-QA&Q-wiPn%T+ox1S>8d ziqoL5a%X4~K1{zB^LtVTxbUM|_3b;6VdI1nt2^;nv&FGB_}`DAq|X!Dp< zJ}9FJAUCfFUHGk_@n0)ud2NzuP^|6^1w%hLV5h|aQCf|=M`bxMj0aQU0CcRYMKd-) z@*y6;<9l@C4skY<1sd}f?@!P($4q}Jcbcw+ zw#ePjz<_wZp)6m}ToYdTK9W6S!`)DRI?wq~@_}(PO{F*FV`Ek+2kCVi!4(m72$O|& z-?RL7+KHOFgD~`q6~R4mLCawmrk}mp zUjl!-)VD|Si5b7m)C{|SO$Y(D5nIY#qCur-2zFqt)u+&g3mPVL`7(whcyC@9&csst zAjjcB2d}9q#RRw+FDEJ(l^@TibeD2Pn$4Ks(ta4XY7Z4dYYdpYG1Z(G)TU;WEsm{? zXWzg5#&t!eKOtI_Yx*a?kh6F|TJz55-)n`fEHdG980f6{?xmJW-6~T|rr9w8)s8i~ z?UJBKKOs$@y%k=cIRffEDAw`$-z}h_+@&Ul)yZ!)Qa0f!RdR)Ai{x9!J`@t`p)m!=e!p*T^! zNe6+Jib>=(XR?z=g$vo#&Yy+&PQs<+n6gU`jU^O_)4NsbtPJ9GSh8u`j9U9-6G?F;C0{4^;2hS$fuURVE;6zSyf>9RrFt* zP<7(XX$vi@&lVqz;zdJ@yW$HywK!=`+OGC(0FqePaQRQN%Ld#Ea%|RAVi@BER zW|^N)o}Tp6kvv)iv~8K}o|^tAcA{YLqK-mu=oR$ZCf~4oQ~BXEjNiBess-YjKV=Us z_%=>0gISf$`wnstP5e+vlMrOBCWH&xO*^7S^poYhKVOdi;BV}b6W)1A$oZvp>RJ=U zh3iCI^&-JUlKMgDT7YDGgF#iwdg7l^T#*Oc8BuplZ| zE!thBcKrXPumcA|@0U&G0Hv2)r+B7i0Uq2m27W?4)Pjrz%VrMC5Jg(6=yt_H<0$5E z;Cmn*xAOU;ul(ag+;|>)<039ZVEIV*dAHI?Uu!-|v==GEjnI5#_&iTgh1wephp6zae!MV;KV^8Dsde zZtGL$WcXyu{{Il49xdNT0Yf)|dfAWq*4e)c2Ea`u{#4;c#(`(wkaB9JofK1t;P?d|Qi6n)W3&N0@v5Wg z5OHEa>=223#KKLI>P$p3pOVjC|7(^;^R$no;h@TjI8ga_U{z+XTfEra|6qlo-Np;z zZGiM9EcJ6Ha{@w;?A+alwdyDM)+V8J`DiX_Qo-1o%<88rfU{yiJ0rd?a@DyQmy+lL zTz)k{5&ans8Dbh|^NaiQTBo%JK}f3&GkzMMm$1ILRd$(qh&Ujai#Wu2-jT04*kbn_DB~#A_UR`NyoraMP>ReK26zj`2rrii5uqi?#6RS&AU3Hs~4s5 ztd@_Mezldj1=6smjUmZ=Xrf!8019VBm&}D&_Ha9{t+q^Kx*t4`cqDsbW$RYmw5p* z39{^$hIK`yA4K9P1AqEUEHQYPrN!M-zg=*N@{TQ6QqVQaiJOpmADs1H&-NPoFa}6A zD;|Kz?K4R07L$a`&rV9bXI~{;jmH&rtW}?Rv91-Whgr2bXyywBeCq6Nqhp$PKf4|o zN%q{c9~y##Oubtcr^?`5mvk_1h8xE4eHVnB0wP$3sv-M1?_)c&4N7comXr zX-_Z#??exQ@PSZAvh#}?%FKUX)wE)m7H=^Qxo&OqL_;{f1Le`v8ziLlf(!^mQ!as- zA7#iQ1c6HewP?tcPyT)->38;5VxNzc`s)fqIagR6xbs^bNOG;jD3I3284JCi5xCsr zF+qCbs6_=?dP}vT6?D|rJ!9z|l=_Ae!)uj?DGh4UH3kTf*6JTfY!%X2`oM4tvmZ6unXq}pmqaMj?Nnj<@Ud>~Z3hjejhl*$-uKyBCKHPJJK7Ud(BhD> zbT|I0p_&!xkgL}^NHjzo~EgK7sWbIoCMPjf!e0fIdMSci8BnVenL5@Lks$RUkmux&Vl3yjQac6O7P9s4& z4Fa<#$g!dskH*zji!1LT7yU|1;Ee9*4oKqK9uBQ>rclql1fBLaX9ZdaRRJ%J5a-KP z+i9>^#^>c-SEP5g#&`Orj{`S@n|e(&SXNoHh;Z*8U$C(u(@#TC%E$Ps$ zWE&gxP<6kJ0h48%j0rIMPNfDmLtPFyk0W5+NNhBdn{e8Lntrh<|K;?L<^pXGtbrlcK?9GZ~XU;GEXR^3UR( z{^aD*&bS}`F0Wf_?JLG&;V}Z`Oq4#;Q9o%88zK6yg&{y7^MSt_%WVsjsr5RCuvq;5 zdlmc?H!HXi?L`3c1Ho;>wr z!#H=y8pxP7q-`}jKlC$#|0h=O1qFD88s3vA; zNvk*ibC4()%e-U31e;WEy3rlCugS7Y{0$m(v0(nkgvdy|0jjQc=X_Vb*4R^{HOuZT zK0KtA8OBPI$vyfE)MpB8dRC!tG0>kq_}y^3i4FGC2*xodYQh~l1IxtEFli%{TT1-V zu)Gr1B`-|I0nd6w6+U+grq_{U1UWzc3RWYBlbp&YJa{9z00hjF*Z8PB3#GY7lG?0?Yth30u|(UzW-09F?}PENR9w>F7+UeG-AQ_U2jFTwckhRxJ}J~eLIYddBRM@=)A{RUy#kg zZ#;>Djf~?23S{V7b*BVbX%83QKPt`paIt;r?1)i3xd^sZGMEgBl=Bk%M*;_{(~X%R z)SvvuYEfy*{g`^boQwBUr}7t2ZbqZNp#GcuZNo=)THk-2D5Lb%E?r2zI=}x52vcB8 zUPM5Pv*eTFn>Wsz4Xo12R!~7|4#4;!5DQ_j*#+cqY6o` z0~Y)<<6(7$xL$5hSS|oq$G5i_b3exF?f~^aEV%iq`aD5tBsq%3&;a6X$BGwDm5{suy$Y^PV@DQS~^192bCn6@b zr`Lx<^Ptzfyh@L=Yt`c@cUE|J-LgEP8$Pgq6go$+9088wk}@dO4Zc2)Mxl@TZO-o1 zqrI)z;P}ZCe~;0f-zpgOt+MI>Ry0fGf{vLn4tSH;*lk-f&u2}Zu zvOXbyjhN#K;!`Iv(lwAwL5$XF@U*O7pUENY`CT3Tf4fWT z2UB~k)J7)$%Aab=)*Vx%jb7yt}w=HaJ?Q9(|vi5 z<;L{nmLWdP&_j&&79F!Zn&kmr@jmXZ_x$}+UvQ;(%P~1X)USpiLA>KZTqH4BKAK4n z`jr9xH0z7rWobC$>p_B>!CIR(J7oo3S5$9a)+u%qt0kS|U0Yvaa1NlBp;G8m`#-J_ z(>fa{2|t9PafW$X(j_yXb`oPjS_NWury3UG)3o0zecgcR5}^aw%qS2#dXTw{`e}1g zs6#?nzsn6IWBxT44{_D{K7?+y9^-MvdicfL0d(-)HtA+;K{4qs1k88f3JbQ~s^aoQ zRdF8ObbTTJTMe49@begLg#jNeAV|S@kmTaD6tEIWounb&E5ZY#0jJvPixf;eKtn>; zBLb@R)njhMV2MP7DB?}!>Fjy${d3o;H z4sBlLQuCjPxsNs}eWi`{=cR%`Ffz*vXx#QPeoY^aCReZ=ntEFblR3hJ%Qd^K<%3BR zsTODB#e{f{2QRlP()o3*9=Q9@x9chs_ZH(EGF}t9yab-85uXP%=~!)ehUeZn=WahH za4yK?F6K*fZ&^BVHJ|<94(nryFc2iF89+4)oE3mK) zw-@r4SL5ir^S)Z>uZBoRDsOk3&=F@@^vBb8$y@h?Rh1B8(0KD)Yu7p7u7?<5=rqoa zpruwWGmmXz_;~~qW}j)^!0r6-@001Y?9>8iP&DZP)^bz=9I*+Yh9d#*kmP8COI3Jb~<&gy1899@gJ)yg^WL9bTHK4 ztMJHOdpW1e%D31m4P72F(U>n#VOQw?t%tCpk31*Cni^j55G->UUcqK}r+BY_-JA1q zY9JRO?e7?Olh;b7vBLwxBis-oBlj2&mX{v9zTUZiIl%$NV z!V-1_9pn2n6j>}}xUQdSqc(|4qON?CZ8gyOjsL(9a^|;n^XJdt7nQBvN7Bdx`>Hdc zLm`ZgCbHV%#-f1q?egDcjlYW=%oQ)6VpY=bJ7k8UGr~oQe_muAdyW%ig%7ZEWR+&fS{=cegM0e0s=J zoT41bdIjI>|0GL$d4qW1^;gcH=vZCfbnY9i<$`j7nPw03Gq?lyh`CKnP3QI{ryPJk z8Cht{U<4!Ik^%MMv288%x{qCopoxl|1Q>yuNka9OA{EBwXh)7-96l^L@&DGV&Wz>; zTH5nXdVyW^zOR&@#&h8#!uKnxDyYf2dHm*a_8@-H@Al)FvM4X9A+GJ}EX(Air_KI0 zlY55Ko!J)L^;__qYDV7NqTu7MRUcHXzChh;dXqrwEUqBRAw9ZG?Aza)w`a%hv^YU^ zJ>AVud}ExB?sCk#MA@lG%gSFmBXD{<{zyHHU66fn!LB&0?7GXa1{oT>xsm9|I0eb+94! z#mKKZpLyTWr(mZ`KPQW>F<(5qd@NscgO|^Z1slW@Vh~;hiCT@|?xC|EstZ&6Za-Q< z&L;9hQYb=fy_n+Hh3ghM{j;4m;R9)hOlK{Q+LojJpKC))D=}>A1lTO=Da@SwdK$`q_S1(nfS9?sat&nd|{4xLSm3T_#w0pBxE!U&_C?QFs z2rpKvhzQ2VVR9(~@%pQh1V2;4f;&w{%-Cq{($Hm#%W?gEZs$ac407%!{#}=8Oq+E zy4VmahDP4cR8G!gcl|JwfHg%~on8U`C_bhTX2!^QKWjN<6M+Y^t!~UR3+PdNcLQZYJ=eR zokCO@DSx^W4b zH`K*gH1b{}p~R9YtH0OZkVK+W#D3X3t#EUJrvEJCi~AWBONP)}I_D?j^7GhltduEI z70&{zy!FIR>P4jogN#J{aZsM4j+|)z2u9$GOqj+Al#}yc(>6&+Zir)CoOO9Tfm=4X a#${6W7?(gk)dUeg #include +#include #include #include #include @@ -154,6 +155,20 @@ void add_binding_code(const Json::Value& value, int type, std::vector& outp } } +bool parse_json_file(const std::string& path, Json::Value& root, std::string& error) { + Json::CharReaderBuilder builder; + std::ifstream file(path); + if (!file.is_open()) { + error = "无法打开 JSON 文件: " + path; + return false; + } + if (!Json::parseFromStream(builder, file, &root, &error)) { + error = "无法解析 JSON 文件: " + path + " - " + error; + return false; + } + return true; +} + void parse_window(const Json::Value& root, WindowConfig& window) { if (!root.isMember("window") || !root["window"].isObject()) { return; @@ -252,23 +267,30 @@ ActorConfig parse_actor(const Json::Value& value) { if (mesh.isMember("shape")) { actor.mesh.shape = json_string(mesh["shape"], actor.mesh.shape); } + if (mesh.isMember("texture")) { + actor.mesh.texture = json_string(mesh["texture"], actor.mesh.texture); + } parse_color(mesh, actor.mesh); } return actor; } -void parse_actors(const Json::Value& root, std::vector& actors) { +void extract_actors(const Json::Value& root, std::vector& actors) { const Json::Value* list = nullptr; - if (root.isMember("actors") && root["actors"].isArray()) { - list = &root["actors"]; - } else if (root.isMember("scene")) { - const Json::Value& scene = root["scene"]; - if (scene.isArray()) { - list = &scene; - } else if (scene.isObject() && scene.isMember("actors") && scene["actors"].isArray()) { - list = &scene["actors"]; + if (root.isArray()) { + list = &root; + } else if (root.isObject()) { + if (root.isMember("actors") && root["actors"].isArray()) { + list = &root["actors"]; + } else if (root.isMember("scene")) { + const Json::Value& scene = root["scene"]; + if (scene.isArray()) { + list = &scene; + } else if (scene.isObject() && scene.isMember("actors") && scene["actors"].isArray()) { + list = &scene["actors"]; + } } } @@ -287,16 +309,65 @@ void parse_actors(const Json::Value& root, std::vector& actors) { } } +void resolve_texture_paths(std::vector& actors, + const std::filesystem::path& base_dir) { + for (ActorConfig& actor : actors) { + if (actor.mesh.texture.empty()) continue; + std::filesystem::path texture_path(actor.mesh.texture); + if (texture_path.is_relative()) { + actor.mesh.texture = (base_dir / texture_path).lexically_normal().string(); + } + } +} + +bool parse_scene_entry(const Json::Value& item, SceneConfig& scene, + const std::filesystem::path& base_dir, std::string& error) { + if (!item.isObject()) { + error = "scenes 数组项必须是对象"; + return false; + } + + if (item.isMember("name")) { + scene.name = json_string(item["name"], ""); + } + if (scene.name.empty()) { + error = "场景缺少 name"; + return false; + } + + std::string path; + if (item.isMember("path")) { + path = json_string(item["path"], ""); + } else if (item.isMember("file")) { + path = json_string(item["file"], ""); + } + + if (!path.empty()) { + std::filesystem::path scene_path(path); + if (scene_path.is_relative()) { + scene_path = base_dir / scene_path; + } + scene.path = scene_path.string(); + Json::Value root; + if (!parse_json_file(scene_path.string(), root, error)) { + return false; + } + extract_actors(root, scene.actors); + resolve_texture_paths(scene.actors, scene_path.parent_path()); + } else { + // 允许场景直接内联在游戏配置中。 + extract_actors(item, scene.actors); + } + return true; +} + } // namespace bool ConfigLoader::load(const std::string& path) { Json::Value root; - Json::CharReaderBuilder builder; - std::ifstream file(path); - std::string errors; - - if (!Json::parseFromStream(builder, file, &root, &errors)) { - last_error_ = "无法解析 JSON 配置文件: " + path + " - " + errors; + std::string error; + if (!parse_json_file(path, root, error)) { + last_error_ = error; return false; } @@ -306,9 +377,31 @@ bool ConfigLoader::load(const std::string& path) { } config_ = GameConfig{}; + const std::filesystem::path config_path(path); + const std::filesystem::path base_dir = config_path.parent_path(); + parse_window(root, config_.window); parse_input(root, config_.input_bindings); - parse_actors(root, config_.actors); + + // 兼容旧式配置:根节点直接写 actors。 + extract_actors(root, config_.actors); + resolve_texture_paths(config_.actors, base_dir); + + if (root.isMember("start_scene")) { + config_.start_scene = json_string(root["start_scene"], ""); + } + + if (root.isMember("scenes") && root["scenes"].isArray()) { + for (const Json::Value& item : root["scenes"]) { + SceneConfig scene; + if (!parse_scene_entry(item, scene, base_dir, error)) { + last_error_ = error; + return false; + } + config_.scenes.push_back(std::move(scene)); + } + } + return true; } diff --git a/src/engine.cpp b/src/engine.cpp index 7503756..689111c 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -10,6 +10,70 @@ #include namespace boundard { +namespace { + +std::shared_ptr create_actor(const ActorConfig& actor_config, Game& game, + BehaviorRegistry& behaviors) { + auto mesh = std::make_shared(); + if (actor_config.mesh.shape == "quad") { + *mesh = Mesh::quad(); + } else { + // 当前渲染器只提供 quad;未知形状先按 quad 创建,便于后续扩展。 + *mesh = Mesh::quad(); + } + mesh->set_color(actor_config.mesh.color); + mesh->texture = actor_config.mesh.texture; + + auto actor = std::make_shared(actor_config.name, game, mesh); + actor->set_position(actor_config.position); + actor->set_facing(actor_config.facing); + + if (!actor_config.init_function.empty()) { + auto handler = behaviors.init(actor_config.init_function); + if (handler) { + actor->set_init_handler(std::move(handler)); + } else { + std::fprintf(stderr, "未找到 init 处理函数: %s\n", + actor_config.init_function.c_str()); + } + } + + if (!actor_config.tick_function.empty()) { + auto handler = behaviors.tick(actor_config.tick_function); + if (handler) { + actor->set_tick_handler(std::move(handler)); + } else { + std::fprintf(stderr, "未找到 tick 处理函数: %s\n", + actor_config.tick_function.c_str()); + } + } + + return actor; +} + +std::shared_ptr build_scene(const SceneConfig& scene, Game& game, + BehaviorRegistry& behaviors) { + auto runtime_scene = std::make_shared(scene.name); + + for (const ActorConfig& actor_config : scene.actors) { + auto actor = create_actor(actor_config, game, behaviors); + runtime_scene->add_actor(std::move(actor)); + } + + return runtime_scene; +} + +const SceneConfig* find_scene(const std::vector& scenes, + const std::string& name) { + for (const SceneConfig& scene : scenes) { + if (scene.name == name) { + return &scene; + } + } + return nullptr; +} + +} // namespace int run_engine(int argc, char** argv, const char* default_config_path) { std::string config_path = default_config_path != nullptr ? default_config_path : "config.ceg"; @@ -27,49 +91,31 @@ int run_engine(int argc, char** argv, const char* default_config_path) { Game& game = Game::getInstance(); BehaviorRegistry& behaviors = BehaviorRegistry::getInstance(); - std::vector> actor_storage; - std::vector actors; - - for (const ActorConfig& actor_config : config.actors) { - auto mesh = std::make_shared(); - if (actor_config.mesh.shape == "quad") { - *mesh = Mesh::quad(); - } else { - // 当前渲染器只提供 quad;未知形状先按 quad 创建,便于后续扩展。 - *mesh = Mesh::quad(); - } - mesh->set_color(actor_config.mesh.color); - - auto actor = std::make_shared(actor_config.name, game, mesh); - actor->set_position(actor_config.position); - actor->set_facing(actor_config.facing); - - if (!actor_config.init_function.empty()) { - auto handler = behaviors.init(actor_config.init_function); - if (handler) { - actor->set_init_handler(std::move(handler)); - } else { - std::fprintf(stderr, "未找到 init 处理函数: %s\n", - actor_config.init_function.c_str()); - } - } - - if (!actor_config.tick_function.empty()) { - auto handler = behaviors.tick(actor_config.tick_function); - if (handler) { - actor->set_tick_handler(std::move(handler)); - } else { - std::fprintf(stderr, "未找到 tick 处理函数: %s\n", - actor_config.tick_function.c_str()); - } - } - - actors.push_back(actor.get()); - actor_storage.push_back(std::move(actor)); + std::vector scenes = config.scenes; + if (scenes.empty() && !config.actors.empty()) { + SceneConfig legacy; + legacy.name = "default"; + legacy.path = config_path; + legacy.actors = config.actors; + scenes.push_back(std::move(legacy)); } - for (auto& actor : actor_storage) { - actor->initialize(); + std::string start_scene = config.start_scene; + if (start_scene.empty() && !scenes.empty()) { + start_scene = scenes.front().name; + } + + const SceneConfig* active_scene = find_scene(scenes, start_scene); + if (active_scene == nullptr && !scenes.empty()) { + active_scene = &scenes.front(); + std::fprintf(stderr, "未找到起始场景 %s,已回退到 %s\n", start_scene.c_str(), + active_scene->name.c_str()); + } + + // 把配置文件中的场景包装为运行时 Scene,并注册到 Game。 + for (const SceneConfig& scene_config : scenes) { + std::shared_ptr runtime_scene = build_scene(scene_config, game, behaviors); + game.add_scene(std::move(runtime_scene)); } render::RenderLoop render_loop; @@ -87,7 +133,22 @@ int run_engine(int argc, char** argv, const char* default_config_path) { render_loop.input().add_action("quit"); render_loop.input().bind_key("quit", 256); - render_loop.set_actors(std::move(actors)); + auto apply_scene = [&](const std::string& name) { + std::shared_ptr scene = game.scene(name); + if (scene == nullptr) { + std::fprintf(stderr, "未找到场景: %s\n", name.c_str()); + return; + } + + scene->initialize(); + game.set_current_scene(scene); + render_loop.set_actors(scene->actors()); + }; + + if (active_scene != nullptr) { + apply_scene(active_scene->name); + } + render_loop.start(config.window.width, config.window.height, config.window.title.c_str()); @@ -98,12 +159,18 @@ int run_engine(int argc, char** argv, const char* default_config_path) { break; } + const std::string requested_scene = game.take_scene_request(); + if (!requested_scene.empty() && requested_scene != game.current_scene_name()) { + apply_scene(requested_scene); + } + const auto now = std::chrono::steady_clock::now(); const float dt = std::chrono::duration(now - last).count(); last = now; - for (auto& actor : actor_storage) { - actor->process(dt, render_loop.input()); + std::shared_ptr scene = game.current_scene(); + if (scene != nullptr) { + scene->process(dt, render_loop.input()); } render_loop.input().end_frame(); diff --git a/src/game.cpp b/src/game.cpp index 8595d47..ef1c4c4 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,5 +1,9 @@ #include +#include + +#include + namespace boundard { Game::Game() { id_cnt = 0; } @@ -14,4 +18,52 @@ void Game::launch() {} size_t Game::new_id() { return id_cnt++; } +void Game::add_scene(std::shared_ptr scene) { + if (scene == nullptr) { + return; + } + scenes_[scene->name()] = std::move(scene); } + +std::shared_ptr Game::scene(const std::string& name) const { + const auto it = scenes_.find(name); + if (it == scenes_.end()) { + return nullptr; + } + return it->second; +} + +std::shared_ptr Game::current_scene() const { + return current_scene_; +} + +const std::string& Game::current_scene_name() const { + return current_scene_name_; +} + +bool Game::has_scene(const std::string& name) const { + return scenes_.find(name) != scenes_.end(); +} + +void Game::set_current_scene(std::shared_ptr scene) { + current_scene_ = std::move(scene); + current_scene_name_ = current_scene_ != nullptr ? current_scene_->name() : ""; +} + +bool Game::switch_scene(const std::string& name) { + const bool exists = has_scene(name); + pending_scene_ = name; + return exists; +} + +void Game::request_scene(const std::string& name) { + switch_scene(name); +} + +std::string Game::take_scene_request() { + std::string request = std::move(pending_scene_); + pending_scene_.clear(); + return request; +} + +} // namespace boundard diff --git a/src/render.cpp b/src/render.cpp index 1c39128..1633c0f 100644 --- a/src/render.cpp +++ b/src/render.cpp @@ -9,7 +9,10 @@ #include #include #include +#include +#include #include +#include #include namespace boundard::render { @@ -133,6 +136,41 @@ VkShaderModule create_shader_module(VkDevice device, const uint32_t* code, size_ return module; } +std::vector load_shader(const char* name) { + const std::string path = std::string(BOUNDARD_SHADER_DIR) + "/" + name; + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + throw std::runtime_error("failed to open shader: " + path); + } + const std::streamsize size = file.tellg(); + if (size <= 0 || size % static_cast(sizeof(uint32_t)) != 0) { + throw std::runtime_error("invalid SPIR-V shader: " + path); + } + std::vector code(static_cast(size) / sizeof(uint32_t)); + file.seekg(0); + file.read(reinterpret_cast(code.data()), size); + if (!file) { + throw std::runtime_error("failed to read shader: " + path); + } + return code; +} + +bool load_png_rgba(const std::string& path, uint32_t& width, uint32_t& height, + std::vector& pixels) { + png_image image{}; + image.version = PNG_IMAGE_VERSION; + if (!png_image_begin_read_from_file(&image, path.c_str())) { + return false; + } + image.format = PNG_FORMAT_RGBA; + width = image.width; + height = image.height; + pixels.resize(PNG_IMAGE_SIZE(image)); + const bool loaded = png_image_finish_read(&image, nullptr, pixels.data(), 0, nullptr) != 0; + png_image_free(&image); + return loaded; +} + } // namespace struct Renderer::Impl { @@ -157,7 +195,6 @@ struct Renderer::Impl { VkRenderPass render_pass = VK_NULL_HANDLE; VkDescriptorSetLayout descriptor_set_layout = VK_NULL_HANDLE; VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; - VkDescriptorSet descriptor_set = VK_NULL_HANDLE; VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; VkPipeline pipeline = VK_NULL_HANDLE; @@ -165,18 +202,19 @@ struct Renderer::Impl { std::vector frames; std::vector images_in_flight; - VkBuffer vertex_buffer = VK_NULL_HANDLE; - VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE; - VkBuffer index_buffer = VK_NULL_HANDLE; - VkDeviceMemory index_buffer_memory = VK_NULL_HANDLE; - uint32_t cached_index_count = 0; - uint32_t cached_vertex_count = 0; - const Mesh* cached_mesh = nullptr; - bool mesh_uploaded = false; - VkBuffer uniform_buffer = VK_NULL_HANDLE; - VkDeviceMemory uniform_buffer_memory = VK_NULL_HANDLE; - VkDeviceSize uniform_buffer_size = sizeof(float) * 16; - void* uniform_mapped = nullptr; + struct MeshResource { + VkBuffer vertex_buffer = VK_NULL_HANDLE; + VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE; + VkBuffer index_buffer = VK_NULL_HANDLE; + VkDeviceMemory index_buffer_memory = VK_NULL_HANDLE; + uint32_t index_count = 0; + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory image_memory = VK_NULL_HANDLE; + VkImageView image_view = VK_NULL_HANDLE; + VkSampler sampler = VK_NULL_HANDLE; + VkDescriptorSet descriptor_set = VK_NULL_HANDLE; + }; + std::unordered_map mesh_resources; size_t current_frame = 0; bool initialized = false; @@ -185,6 +223,9 @@ struct Renderer::Impl { void init(int width, int height, const char* title); void draw(const Actor* const* actors, size_t actor_count); void upload_mesh(const Mesh& mesh); + void create_texture(MeshResource& resource, const std::string& path); + VkCommandBuffer begin_transfer(); + void end_transfer(VkCommandBuffer command_buffer); void destroy(); }; @@ -856,9 +897,9 @@ void Renderer::Impl::init(int width, int height, const char* title) { VkDescriptorSetLayoutBinding layout_binding{}; layout_binding.binding = 0; - layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; layout_binding.descriptorCount = 1; - layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutCreateInfo descriptor_layout_info{}; descriptor_layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; @@ -871,7 +912,7 @@ void Renderer::Impl::init(int width, int height, const char* title) { VkPushConstantRange push_constant_range{}; push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; push_constant_range.offset = 0; - push_constant_range.size = sizeof(float) * 4; + push_constant_range.size = sizeof(float) * 20; VkPipelineLayoutCreateInfo pipeline_layout_info{}; pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; @@ -883,50 +924,23 @@ void Renderer::Impl::init(int width, int height, const char* title) { "vkCreatePipelineLayout"); VkDescriptorPoolSize pool_size{}; - pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - pool_size.descriptorCount = 1; + pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + pool_size.descriptorCount = 1024; VkDescriptorPoolCreateInfo pool_info{}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - pool_info.maxSets = 1; + pool_info.maxSets = 1024; pool_info.poolSizeCount = 1; pool_info.pPoolSizes = &pool_size; check(vkCreateDescriptorPool(device, &pool_info, nullptr, &descriptor_pool), "vkCreateDescriptorPool"); - VkDescriptorSetAllocateInfo set_allocate_info{}; - set_allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - set_allocate_info.descriptorPool = descriptor_pool; - set_allocate_info.descriptorSetCount = 1; - set_allocate_info.pSetLayouts = &descriptor_set_layout; - check(vkAllocateDescriptorSets(device, &set_allocate_info, &descriptor_set), - "vkAllocateDescriptorSets"); - - create_buffer(physical_device, device, uniform_buffer_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - uniform_buffer, uniform_buffer_memory); - check(vkMapMemory(device, uniform_buffer_memory, 0, uniform_buffer_size, 0, &uniform_mapped), - "vkMapMemory"); - - VkDescriptorBufferInfo buffer_info{}; - buffer_info.buffer = uniform_buffer; - buffer_info.offset = 0; - buffer_info.range = uniform_buffer_size; - - VkWriteDescriptorSet descriptor_write{}; - descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - descriptor_write.dstSet = descriptor_set; - descriptor_write.dstBinding = 0; - descriptor_write.dstArrayElement = 0; - descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - descriptor_write.descriptorCount = 1; - descriptor_write.pBufferInfo = &buffer_info; - vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, nullptr); - + const std::vector vertex_shader = load_shader("textured_quad.vert.spv"); + const std::vector fragment_shader = load_shader("textured_quad.frag.spv"); VkShaderModule vertex_module = create_shader_module( - device, kQuadVertShader.data(), kQuadVertShader.size() * sizeof(kQuadVertShader[0])); + device, vertex_shader.data(), vertex_shader.size() * sizeof(uint32_t)); VkShaderModule fragment_module = create_shader_module( - device, kQuadFragShader.data(), kQuadFragShader.size() * sizeof(kQuadFragShader[0])); + device, fragment_shader.data(), fragment_shader.size() * sizeof(uint32_t)); VkPipelineShaderStageCreateInfo vertex_stage{}; vertex_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -947,7 +961,7 @@ void Renderer::Impl::init(int width, int height, const char* title) { vertex_binding.stride = sizeof(Mesh::Vertex); vertex_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - std::array vertex_attributes{}; + std::array vertex_attributes{}; vertex_attributes[0].binding = 0; vertex_attributes[0].location = 0; vertex_attributes[0].format = VK_FORMAT_R32G32_SFLOAT; @@ -956,6 +970,10 @@ void Renderer::Impl::init(int width, int height, const char* title) { vertex_attributes[1].location = 1; vertex_attributes[1].format = VK_FORMAT_R32G32B32A32_SFLOAT; vertex_attributes[1].offset = offsetof(Mesh::Vertex, color); + vertex_attributes[2].binding = 0; + vertex_attributes[2].location = 2; + vertex_attributes[2].format = VK_FORMAT_R32G32_SFLOAT; + vertex_attributes[2].offset = offsetof(Mesh::Vertex, uv); VkPipelineVertexInputStateCreateInfo vertex_input{}; vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; @@ -1005,7 +1023,13 @@ void Renderer::Impl::init(int width, int height, const char* title) { multisampling.sampleShadingEnable = VK_FALSE; VkPipelineColorBlendAttachmentState color_blend_attachment{}; - color_blend_attachment.blendEnable = VK_FALSE; + color_blend_attachment.blendEnable = VK_TRUE; + color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; + color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; @@ -1040,58 +1064,172 @@ void Renderer::Impl::init(int width, int height, const char* title) { initialized = true; } +VkCommandBuffer Renderer::Impl::begin_transfer() { + VkCommandBufferAllocateInfo allocate_info{}; + allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocate_info.commandPool = command_pool; + allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocate_info.commandBufferCount = 1; + VkCommandBuffer command_buffer = VK_NULL_HANDLE; + check(vkAllocateCommandBuffers(device, &allocate_info, &command_buffer), "vkAllocateCommandBuffers"); + VkCommandBufferBeginInfo begin_info{}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + check(vkBeginCommandBuffer(command_buffer, &begin_info), "vkBeginCommandBuffer"); + return command_buffer; +} + +void Renderer::Impl::end_transfer(VkCommandBuffer command_buffer) { + check(vkEndCommandBuffer(command_buffer), "vkEndCommandBuffer"); + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &command_buffer; + check(vkQueueSubmit(graphics_queue, 1, &submit_info, VK_NULL_HANDLE), "vkQueueSubmit"); + check(vkQueueWaitIdle(graphics_queue), "vkQueueWaitIdle"); + vkFreeCommandBuffers(device, command_pool, 1, &command_buffer); +} + +void Renderer::Impl::create_texture(MeshResource& resource, const std::string& path) { + uint32_t width = 1; + uint32_t height = 1; + std::vector pixels = {255, 255, 255, 255}; + if (!path.empty() && !load_png_rgba(path, width, height, pixels)) { + std::fprintf(stderr, "无法加载 PNG 纹理 %s,已使用白色纹理\n", path.c_str()); + width = 1; + height = 1; + pixels = {255, 255, 255, 255}; + } + + VkBuffer staging_buffer = VK_NULL_HANDLE; + VkDeviceMemory staging_memory = VK_NULL_HANDLE; + const VkDeviceSize size = static_cast(pixels.size()); + create_buffer(physical_device, device, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + staging_buffer, staging_memory); + void* mapped = nullptr; + check(vkMapMemory(device, staging_memory, 0, size, 0, &mapped), "vkMapMemory"); + std::memcpy(mapped, pixels.data(), pixels.size()); + vkUnmapMemory(device, staging_memory); + + VkImageCreateInfo image_info{}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.extent = {width, height, 1}; + image_info.mipLevels = 1; + image_info.arrayLayers = 1; + image_info.format = VK_FORMAT_R8G8B8A8_UNORM; + image_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + check(vkCreateImage(device, &image_info, nullptr, &resource.image), "vkCreateImage"); + VkMemoryRequirements requirements{}; + vkGetImageMemoryRequirements(device, resource.image, &requirements); + VkMemoryAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = requirements.size; + alloc_info.memoryTypeIndex = find_memory_type(physical_device, requirements.memoryTypeBits, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + check(vkAllocateMemory(device, &alloc_info, nullptr, &resource.image_memory), "vkAllocateMemory"); + check(vkBindImageMemory(device, resource.image, resource.image_memory, 0), "vkBindImageMemory"); + + VkCommandBuffer command_buffer = begin_transfer(); + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = resource.image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + VkBufferImageCopy region{}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent = {width, height, 1}; + vkCmdCopyBufferToImage(command_buffer, staging_buffer, resource.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + end_transfer(command_buffer); + vkDestroyBuffer(device, staging_buffer, nullptr); + vkFreeMemory(device, staging_memory, nullptr); + + VkImageViewCreateInfo view_info{}; + view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_info.image = resource.image; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = VK_FORMAT_R8G8B8A8_UNORM; + view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.layerCount = 1; + check(vkCreateImageView(device, &view_info, nullptr, &resource.image_view), "vkCreateImageView"); + VkSamplerCreateInfo sampler_info{}; + sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + sampler_info.magFilter = VK_FILTER_LINEAR; + sampler_info.minFilter = VK_FILTER_LINEAR; + sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + sampler_info.maxLod = 0.0F; + check(vkCreateSampler(device, &sampler_info, nullptr, &resource.sampler), "vkCreateSampler"); + + VkDescriptorSetAllocateInfo set_info{}; + set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + set_info.descriptorPool = descriptor_pool; + set_info.descriptorSetCount = 1; + set_info.pSetLayouts = &descriptor_set_layout; + check(vkAllocateDescriptorSets(device, &set_info, &resource.descriptor_set), "vkAllocateDescriptorSets"); + VkDescriptorImageInfo image_descriptor{}; + image_descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image_descriptor.imageView = resource.image_view; + image_descriptor.sampler = resource.sampler; + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = resource.descriptor_set; + write.dstBinding = 0; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write.pImageInfo = &image_descriptor; + vkUpdateDescriptorSets(device, 1, &write, 0, nullptr); +} + void Renderer::Impl::upload_mesh(const Mesh& mesh) { - if (cached_mesh == &mesh && mesh_uploaded) { - return; - } + if (mesh_resources.find(&mesh) != mesh_resources.end()) return; + MeshResource& resource = mesh_resources[&mesh]; + resource.index_count = static_cast(mesh.indices.size()); + if (mesh.vertices.empty() || resource.index_count == 0) return; - if (vertex_buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(device, vertex_buffer, nullptr); - vertex_buffer = VK_NULL_HANDLE; - } - if (vertex_buffer_memory != VK_NULL_HANDLE) { - vkFreeMemory(device, vertex_buffer_memory, nullptr); - vertex_buffer_memory = VK_NULL_HANDLE; - } - if (index_buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(device, index_buffer, nullptr); - index_buffer = VK_NULL_HANDLE; - } - if (index_buffer_memory != VK_NULL_HANDLE) { - vkFreeMemory(device, index_buffer_memory, nullptr); - index_buffer_memory = VK_NULL_HANDLE; - } - - cached_vertex_count = static_cast(mesh.vertices.size()); - cached_index_count = static_cast(mesh.indices.size()); - cached_mesh = &mesh; - mesh_uploaded = false; - - if (cached_vertex_count == 0 || cached_index_count == 0) { - return; - } - - const VkDeviceSize vertex_size = sizeof(Mesh::Vertex) * cached_vertex_count; + const VkDeviceSize vertex_size = sizeof(Mesh::Vertex) * mesh.vertices.size(); create_buffer(physical_device, device, vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - vertex_buffer, vertex_buffer_memory); + resource.vertex_buffer, resource.vertex_buffer_memory); void* vertex_mapped = nullptr; - check(vkMapMemory(device, vertex_buffer_memory, 0, vertex_size, 0, &vertex_mapped), + check(vkMapMemory(device, resource.vertex_buffer_memory, 0, vertex_size, 0, &vertex_mapped), "vkMapMemory"); std::memcpy(vertex_mapped, mesh.vertices.data(), vertex_size); - vkUnmapMemory(device, vertex_buffer_memory); + vkUnmapMemory(device, resource.vertex_buffer_memory); - const VkDeviceSize index_size = sizeof(uint32_t) * cached_index_count; + const VkDeviceSize index_size = sizeof(uint32_t) * resource.index_count; create_buffer(physical_device, device, index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - index_buffer, index_buffer_memory); + resource.index_buffer, resource.index_buffer_memory); void* index_mapped = nullptr; - check(vkMapMemory(device, index_buffer_memory, 0, index_size, 0, &index_mapped), + check(vkMapMemory(device, resource.index_buffer_memory, 0, index_size, 0, &index_mapped), "vkMapMemory"); std::memcpy(index_mapped, mesh.indices.data(), index_size); - vkUnmapMemory(device, index_buffer_memory); - - mesh_uploaded = true; + vkUnmapMemory(device, resource.index_buffer_memory); + create_texture(resource, mesh.texture); } void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) { @@ -1120,7 +1258,6 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) { images_in_flight[image_index] = frame.in_flight; check(vkResetFences(device, 1, &frame.in_flight), "vkResetFences"); - // 示例渲染器一次上传一个 Mesh;最后一个 Actor 的网格用于绑定。 for (size_t i = 0; i < actor_count; ++i) { const Actor& actor = *actors[i]; const auto& mesh = actor.mesh(); @@ -1149,57 +1286,52 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) { vkCmdBeginRenderPass(frame.command_buffer, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - vkCmdBindDescriptorSets(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, - 0, 1, &descriptor_set, 0, nullptr); + for (size_t i = 0; i < actor_count; ++i) { + const Actor& actor = *actors[i]; + if (actor.mesh() == nullptr) continue; + const auto resource_it = mesh_resources.find(actor.mesh().get()); + if (resource_it == mesh_resources.end()) continue; + const MeshResource& resource = resource_it->second; + if (resource.vertex_buffer == VK_NULL_HANDLE || resource.index_buffer == VK_NULL_HANDLE || + resource.descriptor_set == VK_NULL_HANDLE || resource.index_count == 0) continue; - if (vertex_buffer == VK_NULL_HANDLE || index_buffer == VK_NULL_HANDLE || - cached_index_count == 0 || cached_vertex_count == 0) { - vkCmdEndRenderPass(frame.command_buffer); - check(vkEndCommandBuffer(frame.command_buffer), "vkEndCommandBuffer"); - } else { VkDeviceSize offset = 0; - vkCmdBindVertexBuffers(frame.command_buffer, 0, 1, &vertex_buffer, &offset); - vkCmdBindIndexBuffer(frame.command_buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindVertexBuffers(frame.command_buffer, 0, 1, &resource.vertex_buffer, &offset); + vkCmdBindIndexBuffer(frame.command_buffer, resource.index_buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindDescriptorSets(frame.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, + 0, 1, &resource.descriptor_set, 0, nullptr); - for (size_t i = 0; i < actor_count; ++i) { - const Actor& actor = *actors[i]; - if (actor.mesh() == nullptr) { - continue; - } - - const auto position = actor.position(); - const auto facing = actor.facing(); - const float scale = 0.35F; - float facing_x = facing[0]; - float facing_y = facing[1]; - const float facing_length = std::sqrt(facing_x * facing_x + facing_y * facing_y); - if (facing_length > 0.0001F) { - facing_x /= facing_length; - facing_y /= facing_length; - } else { - facing_x = 1.0F; - facing_y = 0.0F; - } - - // 朝向向量作为局部 X 轴,旋转 + 平移,把单位四边形放到 Actor 的屏幕坐标。 - std::array model = {{ - scale * facing_x, scale * facing_y, 0.0F, 0.0F, - -scale * facing_y, scale * facing_x, 0.0F, 0.0F, - 0.0F, 0.0F, 1.0F, 0.0F, - position[0], position[1], 0.0F, 1.0F, - }}; - std::memcpy(uniform_mapped, model.data(), uniform_buffer_size); - - const std::array color = actor.mesh()->color(); - vkCmdPushConstants(frame.command_buffer, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, - sizeof(float) * 4, color.data()); - vkCmdDrawIndexed(frame.command_buffer, cached_index_count, 1, 0, 0, 0); + const auto position = actor.position(); + const auto facing = actor.facing(); + const float scale = 0.35F; + float facing_x = facing[0]; + float facing_y = facing[1]; + const float facing_length = std::sqrt(facing_x * facing_x + facing_y * facing_y); + if (facing_length > 0.0001F) { + facing_x /= facing_length; + facing_y /= facing_length; + } else { + facing_x = 1.0F; + facing_y = 0.0F; } - vkCmdEndRenderPass(frame.command_buffer); - check(vkEndCommandBuffer(frame.command_buffer), "vkEndCommandBuffer"); + std::array constants = {{ + scale * facing_x, scale * facing_y, 0.0F, 0.0F, + -scale * facing_y, scale * facing_x, 0.0F, 0.0F, + 0.0F, 0.0F, 1.0F, 0.0F, + position[0], position[1], 0.0F, 1.0F, + 0.0F, 0.0F, 0.0F, 0.0F, + }}; + const std::array color = actor.mesh()->color(); + std::copy(color.begin(), color.end(), constants.begin() + 16); + vkCmdPushConstants(frame.command_buffer, pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, + sizeof(constants), constants.data()); + vkCmdDrawIndexed(frame.command_buffer, resource.index_count, 1, 0, 0, 0); } + vkCmdEndRenderPass(frame.command_buffer); + check(vkEndCommandBuffer(frame.command_buffer), "vkEndCommandBuffer"); + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkSubmitInfo submit_info{}; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; @@ -1208,6 +1340,7 @@ void Renderer::Impl::draw(const Actor* const* actors, size_t actor_count) { submit_info.pWaitDstStageMask = &wait_stage; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &frame.command_buffer; + // Present 不提供 fence;同一交换链图像再次被获取前,不能重用其等待的信号量。 FrameData& image_frame = frames[image_index]; submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &image_frame.render_finished; @@ -1238,11 +1371,6 @@ void Renderer::Impl::destroy() { vkDeviceWaitIdle(device); } - if (uniform_mapped != nullptr && uniform_buffer_memory != VK_NULL_HANDLE) { - vkUnmapMemory(device, uniform_buffer_memory); - uniform_mapped = nullptr; - } - for (FrameData& frame : frames) { if (frame.in_flight != VK_NULL_HANDLE) { vkDestroyFence(device, frame.in_flight, nullptr); @@ -1258,24 +1386,17 @@ void Renderer::Impl::destroy() { } } - if (uniform_buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(device, uniform_buffer, nullptr); - } - if (uniform_buffer_memory != VK_NULL_HANDLE) { - vkFreeMemory(device, uniform_buffer_memory, nullptr); - } - if (vertex_buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(device, vertex_buffer, nullptr); - } - if (vertex_buffer_memory != VK_NULL_HANDLE) { - vkFreeMemory(device, vertex_buffer_memory, nullptr); - } - if (index_buffer != VK_NULL_HANDLE) { - vkDestroyBuffer(device, index_buffer, nullptr); - } - if (index_buffer_memory != VK_NULL_HANDLE) { - vkFreeMemory(device, index_buffer_memory, nullptr); + for (auto& [mesh, resource] : mesh_resources) { + if (resource.sampler != VK_NULL_HANDLE) vkDestroySampler(device, resource.sampler, nullptr); + if (resource.image_view != VK_NULL_HANDLE) vkDestroyImageView(device, resource.image_view, nullptr); + if (resource.image != VK_NULL_HANDLE) vkDestroyImage(device, resource.image, nullptr); + if (resource.image_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.image_memory, nullptr); + if (resource.vertex_buffer != VK_NULL_HANDLE) vkDestroyBuffer(device, resource.vertex_buffer, nullptr); + if (resource.vertex_buffer_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.vertex_buffer_memory, nullptr); + if (resource.index_buffer != VK_NULL_HANDLE) vkDestroyBuffer(device, resource.index_buffer, nullptr); + if (resource.index_buffer_memory != VK_NULL_HANDLE) vkFreeMemory(device, resource.index_buffer_memory, nullptr); } + mesh_resources.clear(); if (descriptor_pool != VK_NULL_HANDLE) { vkDestroyDescriptorPool(device, descriptor_pool, nullptr); diff --git a/src/scene.cpp b/src/scene.cpp new file mode 100644 index 0000000..b16988b --- /dev/null +++ b/src/scene.cpp @@ -0,0 +1,61 @@ +#include + +#include + +#include + +namespace boundard { + +Scene::Scene(std::string name) : name_(std::move(name)) {} + +Scene::~Scene() = default; + +const std::string& Scene::name() const { + return name_; +} + +void Scene::set_name(std::string name) { + name_ = std::move(name); +} + +void Scene::add_actor(std::shared_ptr actor) { + if (actor == nullptr) { + return; + } + actor_storage_.push_back(actor); + actors_.push_back(actor.get()); +} + +void Scene::set_actors(std::vector> actors) { + clear(); + for (std::shared_ptr& actor : actors) { + add_actor(std::move(actor)); + } +} + +void Scene::clear() { + actors_.clear(); + actor_storage_.clear(); +} + +const std::vector& Scene::actors() const { + return actors_; +} + +std::vector& Scene::actors() { + return actors_; +} + +void Scene::initialize() { + for (Actor* actor : actors_) { + actor->initialize(); + } +} + +void Scene::process(float dt, const render::InputMap& input) { + for (Actor* actor : actors_) { + actor->process(dt, input); + } +} + +} // namespace boundard