png支持

This commit is contained in:
ArchZer0
2026-08-17 10:26:27 +08:00
parent 07aeced2e4
commit 63d97f058a
25 changed files with 893 additions and 261 deletions
+1 -1
View File
@@ -3,4 +3,4 @@ BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
AllowShortIfStatementsOnASingleLine: false
AlignTrailingComments: true
AlignTrailingComments: true
+26 -1
View File
@@ -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)
+4 -5
View File
@@ -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 @@
}
}
]
}
}
+1 -1
View File
@@ -1 +1 @@
out/build/Clang/compile_commands.json
out/build/Boundard/compile_commands.json
+7 -15
View File
@@ -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" }
]
}
+7 -14
View File
@@ -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" }
]
}
+41
View File
@@ -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");
}
});
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+21
View File
@@ -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"
}
}
]
}
+15
View File
@@ -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 }
}
}
]
}
+1
View File
@@ -6,4 +6,5 @@
#include <behavior.hpp>
#include <render.hpp>
#include <config.hpp>
#include <scene.hpp>
#include <engine.hpp>
+11
View File
@@ -15,6 +15,7 @@ struct WindowConfig {
struct MeshConfig {
std::string shape = "quad";
std::string texture;
std::array<float, 4> color{0.5F, 0.5F, 0.5F, 1.0F};
};
@@ -33,9 +34,19 @@ struct InputBindingConfig {
std::vector<int> mouse_buttons;
};
struct SceneConfig {
std::string name;
std::string path;
std::vector<ActorConfig> actors;
};
struct GameConfig {
WindowConfig window;
std::vector<InputBindingConfig> input_bindings;
std::vector<SceneConfig> scenes;
std::string start_scene;
// 兼容旧的单场景写法:直接写在游戏配置里的 actors。
std::vector<ActorConfig> actors;
};
+26
View File
@@ -1,10 +1,21 @@
#pragma once
#include <cstddef>
#include <memory>
#include <string>
#include <unordered_map>
namespace boundard {
class Scene;
class Game {
private:
size_t id_cnt;
std::string current_scene_name_;
std::string pending_scene_;
std::unordered_map<std::string, std::shared_ptr<Scene>> scenes_;
std::shared_ptr<Scene> 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> scene);
std::shared_ptr<Scene> scene(const std::string& name) const;
std::shared_ptr<Scene> 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> scene);
// 供行为函数调用的切换 API。场景会在主循环的安全时机切换。
bool switch_scene(const std::string& name);
void request_scene(const std::string& name);
std::string take_scene_request();
};
} // namespace boundard
+7 -6
View File
@@ -9,17 +9,18 @@
namespace boundard {
// 渲染组件:描述 Actor 的几何形状、颜色和纹理占位
// 渲染组件:描述 Actor 的几何形状、颜色和可选 PNG 纹理。
class Mesh {
public:
struct Vertex {
std::array<float, 2> position;
std::array<float, 4> color;
std::array<float, 2> uv;
};
std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
std::string texture; // 纹理路径占位,暂未采样
std::string texture; // PNG 纹理路径;为空时使用白色纹理。
std::array<float, 4> color() const {
std::lock_guard<std::mutex> 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;
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
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> actor);
void set_actors(std::vector<std::shared_ptr<Actor>> actors);
void clear();
const std::vector<Actor*>& actors() const;
std::vector<Actor*>& actors();
void initialize();
void process(float dt, const render::InputMap& input);
private:
std::string name_;
std::vector<std::shared_ptr<Actor>> actor_storage_;
std::vector<Actor*> actors_;
};
} // namespace boundard
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"actors": [
{
"name": "square",
"position": {
"x": 0,
"y": 0
},
"facing": {
"x": 1,
"y": 0
},
"mesh": {
"shape": "quad",
"texture": "Dank.png"
}
}
]
}
+19
View File
@@ -0,0 +1,19 @@
{
"actors": [
{
"name": "square",
"position": {
"x": 0,
"y": 0
},
"facing": {
"x": 0,
"y": 1
},
"mesh": {
"shape": "quad",
"texture": "assets/player.png"
}
}
]
}
+11
View File
@@ -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;
}
+19
View File
@@ -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;
}
+109 -16
View File
@@ -4,6 +4,7 @@
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
@@ -154,6 +155,20 @@ void add_binding_code(const Json::Value& value, int type, std::vector<int>& 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<ActorConfig>& actors) {
void extract_actors(const Json::Value& root, std::vector<ActorConfig>& 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<ActorConfig>& actors) {
}
}
void resolve_texture_paths(std::vector<ActorConfig>& 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;
}
+111 -44
View File
@@ -10,6 +10,70 @@
#include <boundard.hpp>
namespace boundard {
namespace {
std::shared_ptr<Actor> create_actor(const ActorConfig& actor_config, Game& game,
BehaviorRegistry& behaviors) {
auto mesh = std::make_shared<Mesh>();
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>(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<Scene> build_scene(const SceneConfig& scene, Game& game,
BehaviorRegistry& behaviors) {
auto runtime_scene = std::make_shared<Scene>(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<SceneConfig>& 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<std::shared_ptr<Actor>> actor_storage;
std::vector<Actor*> actors;
for (const ActorConfig& actor_config : config.actors) {
auto mesh = std::make_shared<Mesh>();
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>(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<SceneConfig> 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<Scene> 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> 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<float>(now - last).count();
last = now;
for (auto& actor : actor_storage) {
actor->process(dt, render_loop.input());
std::shared_ptr<Scene> scene = game.current_scene();
if (scene != nullptr) {
scene->process(dt, render_loop.input());
}
render_loop.input().end_frame();
+52
View File
@@ -1,5 +1,9 @@
#include <game.hpp>
#include <scene.hpp>
#include <utility>
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> scene) {
if (scene == nullptr) {
return;
}
scenes_[scene->name()] = std::move(scene);
}
std::shared_ptr<Scene> 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<Scene> 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> 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
+279 -158
View File
@@ -9,7 +9,10 @@
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <png.h>
#include <stdexcept>
#include <unordered_map>
#include <vector>
namespace boundard::render {
@@ -133,6 +136,41 @@ VkShaderModule create_shader_module(VkDevice device, const uint32_t* code, size_
return module;
}
std::vector<uint32_t> 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<std::streamsize>(sizeof(uint32_t)) != 0) {
throw std::runtime_error("invalid SPIR-V shader: " + path);
}
std::vector<uint32_t> code(static_cast<size_t>(size) / sizeof(uint32_t));
file.seekg(0);
file.read(reinterpret_cast<char*>(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<unsigned char>& 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<FrameData> frames;
std::vector<VkFence> 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<const Mesh*, MeshResource> 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<uint32_t> vertex_shader = load_shader("textured_quad.vert.spv");
const std::vector<uint32_t> 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<VkVertexInputAttributeDescription, 2> vertex_attributes{};
std::array<VkVertexInputAttributeDescription, 3> 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<unsigned char> 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<VkDeviceSize>(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, &region);
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<uint32_t>(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<uint32_t>(mesh.vertices.size());
cached_index_count = static_cast<uint32_t>(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<float, 16> 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<float, 4> 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<float, 20> 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<float, 4> 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);
+61
View File
@@ -0,0 +1,61 @@
#include <scene.hpp>
#include <actor.hpp>
#include <utility>
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> actor) {
if (actor == nullptr) {
return;
}
actor_storage_.push_back(actor);
actors_.push_back(actor.get());
}
void Scene::set_actors(std::vector<std::shared_ptr<Actor>> actors) {
clear();
for (std::shared_ptr<Actor>& actor : actors) {
add_actor(std::move(actor));
}
}
void Scene::clear() {
actors_.clear();
actor_storage_.clear();
}
const std::vector<Actor*>& Scene::actors() const {
return actors_;
}
std::vector<Actor*>& 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