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 0000000..498182d Binary files /dev/null and b/examples/square/scenes/Dank.png differ diff --git a/examples/square/scenes/square.json b/examples/square/scenes/square.json new file mode 100644 index 0000000..1d70b83 --- /dev/null +++ b/examples/square/scenes/square.json @@ -0,0 +1,21 @@ +{ + "actors": [ + { + "name": "square", + "init": "square_init", + "tick": "square_tick", + "position": { + "x": 0, + "y": 0 + }, + "facing": { + "x": 1, + "y": 0 + }, + "mesh": { + "shape": "quad", + "texture": "Dank.png" + } + } + ] +} diff --git a/examples/square/scenes/square_alt.json b/examples/square/scenes/square_alt.json new file mode 100644 index 0000000..e3c5189 --- /dev/null +++ b/examples/square/scenes/square_alt.json @@ -0,0 +1,15 @@ +{ + "actors": [ + { + "name": "square_alt", + "init": "square_alt_init", + "tick": "square_alt_tick", + "position": { "x": 0, "y": 0 }, + "facing": { "x": 0, "y": 1 }, + "mesh": { + "shape": "quad", + "color": { "r": 0.9, "g": 0.3, "b": 0.2, "a": 1.0 } + } + } + ] +} diff --git a/include/boundard.hpp b/include/boundard.hpp index 9fffa89..db879cf 100644 --- a/include/boundard.hpp +++ b/include/boundard.hpp @@ -6,4 +6,5 @@ #include #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 0000000..498182d Binary files /dev/null and b/scenes/Dank.png differ diff --git a/scenes/main.json b/scenes/main.json new file mode 100644 index 0000000..0188937 --- /dev/null +++ b/scenes/main.json @@ -0,0 +1,19 @@ +{ + "actors": [ + { + "name": "square", + "position": { + "x": 0, + "y": 0 + }, + "facing": { + "x": 1, + "y": 0 + }, + "mesh": { + "shape": "quad", + "texture": "Dank.png" + } + } + ] +} diff --git a/scenes/second.json b/scenes/second.json new file mode 100644 index 0000000..b7b0ccd --- /dev/null +++ b/scenes/second.json @@ -0,0 +1,19 @@ +{ + "actors": [ + { + "name": "square", + "position": { + "x": 0, + "y": 0 + }, + "facing": { + "x": 0, + "y": 1 + }, + "mesh": { + "shape": "quad", + "texture": "assets/player.png" + } + } + ] +} \ No newline at end of file diff --git a/shaders/textured_quad.frag b/shaders/textured_quad.frag new file mode 100644 index 0000000..0a4f81d --- /dev/null +++ b/shaders/textured_quad.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(binding = 0) uniform sampler2D u_texture; + +layout(location = 0) in vec4 v_color; +layout(location = 1) in vec2 v_uv; +layout(location = 0) out vec4 out_color; + +void main() { + out_color = texture(u_texture, v_uv) * v_color; +} diff --git a/shaders/textured_quad.vert b/shaders/textured_quad.vert new file mode 100644 index 0000000..37a7fc1 --- /dev/null +++ b/shaders/textured_quad.vert @@ -0,0 +1,19 @@ +#version 450 + +layout(push_constant) uniform PushConstants { + mat4 model; + vec4 color; +} pc; + +layout(location = 0) in vec2 a_position; +layout(location = 1) in vec4 a_color; +layout(location = 2) in vec2 a_uv; + +layout(location = 0) out vec4 v_color; +layout(location = 1) out vec2 v_uv; + +void main() { + gl_Position = pc.model * vec4(a_position, 0.0, 1.0); + v_color = a_color * pc.color; + v_uv = a_uv; +} diff --git a/src/config.cpp b/src/config.cpp index 3617a49..6dc5975 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -4,6 +4,7 @@ #include #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