Skip to content

Commit

Permalink
implement more controllable shader command template
Browse files Browse the repository at this point in the history
  • Loading branch information
ZzzhHe committed Jan 23, 2025
1 parent 64620ae commit 8cda18b
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 105 deletions.
60 changes: 59 additions & 1 deletion libopenage/renderer/stages/world/render_stage.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022-2024 the openage authors. See copying.md for legal info.
// Copyright 2022-2025 the openage authors. See copying.md for legal info.

#include "render_stage.h"

Expand All @@ -12,6 +12,7 @@
#include "renderer/resources/texture_info.h"
#include "renderer/shader_program.h"
#include "renderer/stages/world/object.h"
#include "renderer/stages/world/world_shader_commands.h"
#include "renderer/texture.h"
#include "renderer/window.h"
#include "time/clock.h"
Expand Down Expand Up @@ -46,6 +47,30 @@ WorldRenderStage::WorldRenderStage(const std::shared_ptr<Window> &window,
log::log(INFO << "Created render stage 'World'");
}

WorldRenderStage::WorldRenderStage(const std::shared_ptr<Window> &window,
const std::shared_ptr<renderer::Renderer> &renderer,
const std::shared_ptr<renderer::camera::Camera> &camera,
const util::Path &shaderdir,
const util::Path &configdir,
const std::shared_ptr<renderer::resources::AssetManager> &asset_manager,
const std::shared_ptr<time::Clock> clock) :
renderer{renderer},
camera{camera},
asset_manager{asset_manager},
render_objects{},
clock{clock},
default_geometry{this->renderer->add_mesh_geometry(WorldObject::get_mesh())} {
auto size = window->get_size();
this->initialize_render_pass_with_shader_commands(size[0], size[1], shaderdir, configdir);
this->init_uniform_ids();

window->add_resize_callback([this](size_t width, size_t height, double /*scale*/) {
this->resize(width, height);
});

log::log(INFO << "Created render stage 'World' with shader command");
}

std::shared_ptr<renderer::RenderPass> WorldRenderStage::get_render_pass() {
return this->render_pass;
}
Expand Down Expand Up @@ -156,4 +181,37 @@ void WorldRenderStage::init_uniform_ids() {
WorldObject::anchor_offset = this->display_shader->get_uniform_id("anchor_offset");
}

void WorldRenderStage::initialize_render_pass_with_shader_commands(size_t width, size_t height, const util::Path &shaderdir, const util::Path &config_path) {
auto vert_shader_file = (shaderdir / "demo_7_world.vert.glsl").open();
auto vert_shader_src = renderer::resources::ShaderSource(
resources::shader_lang_t::glsl,
resources::shader_stage_t::vertex,
vert_shader_file.read());
vert_shader_file.close();

auto frag_shader_file = (shaderdir / "demo_7_world.frag.glsl").open();
log::log(INFO << "Loading shader commands config from: " << (shaderdir / "demo_7_display.frag.glsl"));
this->shader_template = std::make_shared<ShaderCommandTemplate>(frag_shader_file.read());
if (not this->shader_template->load_commands(config_path / "world_commands.config")) {
log::log(ERR << "Failed to load shader commands configuration for world stage");
return;
}

auto frag_shader_src = renderer::resources::ShaderSource(
resources::shader_lang_t::glsl,
resources::shader_stage_t::fragment,
this->shader_template->generate_source());
frag_shader_file.close();

this->output_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::rgba8));
this->depth_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::depth24));
this->id_texture = renderer->add_texture(resources::Texture2dInfo(width, height, resources::pixel_format::r32ui));

this->display_shader = this->renderer->add_shader({vert_shader_src, frag_shader_src});
this->display_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer());

auto fbo = this->renderer->create_texture_target({this->output_texture, this->depth_texture, this->id_texture});
this->render_pass = this->renderer->add_render_pass({}, fbo);
}

} // namespace openage::renderer::world
39 changes: 38 additions & 1 deletion libopenage/renderer/stages/world/render_stage.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022-2024 the openage authors. See copying.md for legal info.
// Copyright 2022-2025 the openage authors. See copying.md for legal info.

#pragma once

Expand Down Expand Up @@ -33,6 +33,7 @@ class AssetManager;
namespace world {
class RenderEntity;
class WorldObject;
class ShaderCommandTemplate;

/**
* Renderer for drawing and displaying entities in the game world (units, buildings, etc.)
Expand Down Expand Up @@ -60,6 +61,26 @@ class WorldRenderStage {
const util::Path &shaderdir,
const std::shared_ptr<renderer::resources::AssetManager> &asset_manager,
const std::shared_ptr<time::Clock> clock);

/**
* Create a new render stage for the game world with shader command.
*
* @param window openage window targeted for rendering.
* @param renderer openage low-level renderer.
* @param camera Camera used for the rendered scene.
* @param shaderdir Directory containing the shader source files.
* @param configdir Directory containing the config for shader command.
* @param asset_manager Asset manager for loading resources.
* @param clock Simulation clock for timing animations.
*/
WorldRenderStage(const std::shared_ptr<Window> &window,
const std::shared_ptr<renderer::Renderer> &renderer,
const std::shared_ptr<renderer::camera::Camera> &camera,
const util::Path &shaderdir,
const util::Path &configdir,
const std::shared_ptr<renderer::resources::AssetManager> &asset_manager,
const std::shared_ptr<time::Clock> clock);

~WorldRenderStage() = default;

/**
Expand Down Expand Up @@ -111,6 +132,17 @@ class WorldRenderStage {
*/
void init_uniform_ids();

/**
* Initialize render pass with shader commands.
* This is an alternative to initialize_render_pass() that uses configurable shader commands.
*
* @param width Width of the FBO.
* @param height Height of the FBO.
* @param shaderdir Directory containing shader files.
* @param configdir Directory containing configuration file.
*/
void initialize_render_pass_with_shader_commands(size_t width, size_t height, const util::Path &shaderdir, const util::Path &config_path);

/**
* Reference to the openage renderer.
*/
Expand All @@ -131,6 +163,11 @@ class WorldRenderStage {
*/
std::shared_ptr<renderer::RenderPass> render_pass;

/**
* Template for the world shader program.
*/
std::shared_ptr<ShaderCommandTemplate> shader_template;

/**
* Render entities requested by the game world.
*/
Expand Down
173 changes: 132 additions & 41 deletions libopenage/renderer/stages/world/world_shader_commands.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2024-2024 the openage authors. See copying.md for legal info.
// Copyright 2024-2025 the openage authors. See copying.md for legal info.

#include "world_shader_commands.h"

Expand All @@ -9,67 +9,158 @@

namespace openage::renderer::world {

bool WorldShaderCommands::add_command(uint8_t alpha, const std::string &code, const std::string &description) {
if (!validate_alpha(alpha)) {
log::log(ERR << "Invalid alpha value: " << int(alpha));
ShaderCommandTemplate::ShaderCommandTemplate(const std::string &template_code) :
template_code{template_code} {}

bool ShaderCommandTemplate::load_commands(const util::Path &config_path) {
try {
log::log(INFO << "Loading shader commands config from: " << config_path);
auto config_file = config_path.open();
std::string line;
std::stringstream ss(config_file.read());

ShaderCommandConfig current_command;
// if true, we are reading the code block for the current command.
bool reading_code = false;
std::string code_block;

while (std::getline(ss, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
// Trim whitespace from line
line = trim(line);
log::log(INFO << "Parsing line: " << line);

// Skip empty lines and comments
if (line.empty() || line[0] == '#') {
continue;
}

if (reading_code) {
if (line == "}") {
reading_code = false;
current_command.code = code_block;

// Generate and add snippet
std::string snippet = generate_snippet(current_command);
add_snippet(current_command.placeholder_id, snippet);
commands.push_back(current_command);

// Reset for next command
code_block.clear();
}
else {
code_block += line + "\n";
}
continue;
}

if (line == "[COMMAND]") {
current_command = ShaderCommandConfig{};
continue;
}

// Parse key-value pairs
size_t pos = line.find('=');
if (pos != std::string::npos) {
std::string key = trim(line.substr(0, pos));
std::string value = trim(line.substr(pos + 1));

if (key == "placeholder") {
current_command.placeholder_id = value;
}
else if (key == "alpha") {
uint8_t alpha = static_cast<uint8_t>(std::stoi(value));
if (alpha % 2 == 0 && alpha >= 0 && alpha <= 254) {
current_command.alpha = alpha;
}
else {
log::log(ERR << "Invalid alpha value for command: " << alpha);
return false;
}
}
else if (key == "description") {
current_command.description = value;
}
else if (key == "code") {
if (value == "{") {
reading_code = true;
code_block.clear();
}
}
}
}

return true;
}
catch (const std::exception &e) {
log::log(ERR << "Failed to load shader commands: " << e.what());
return false;
}
if (!validate_code(code)) {
log::log(ERR << "Invalid command code");
}

bool ShaderCommandTemplate::add_snippet(const std::string &placeholder_id, const std::string &snippet) {
if (snippet.empty()) {
log::log(ERR << "Empty snippet for placeholder: " << placeholder_id);
return false;
}

commands_map[alpha] = {alpha, code, description};
return true;
}
if (placeholder_id.empty()) {
log::log(ERR << "Empty placeholder ID for snippet");
return false;
}

bool WorldShaderCommands::remove_command(uint8_t alpha) {
if (!validate_alpha(alpha)) {
// Check if the placeholder exists in the template
std::string placeholder = "//@" + placeholder_id + "@";
if (template_code.find(placeholder) == std::string::npos) {
log::log(ERR << "Placeholder not found in template: " << placeholder_id);
return false;
}
commands_map.erase(alpha);

// Store the snippet
snippets[placeholder_id].push_back(snippet);
return true;
}

bool WorldShaderCommands::has_command(uint8_t alpha) const {
return commands_map.contains(alpha);
std::string ShaderCommandTemplate::generate_snippet(const ShaderCommandConfig &command) {
return "case " + std::to_string(command.alpha) + ":\n"
+ "\t\t// " + command.description + "\n"
+ "\t\t" + command.code + "\t\tbreak;\n";
}

std::string WorldShaderCommands::integrate_command(const std::string &base_shader) {
std::string final_shader = base_shader;
std::string commands_code = generate_command_code();
std::string ShaderCommandTemplate::generate_source() const {
std::string result = template_code;

// Find the insertion point
size_t insert_point = final_shader.find(COMMAND_MARKER);
if (insert_point == std::string::npos) {
throw Error(MSG(err) << "Failed to find command insertion point in shader.");
}

// Replace the insertion point with the generated command code
final_shader.replace(insert_point, std::strlen(COMMAND_MARKER), commands_code);
// Process each placeholder
for (const auto &[placeholder_id, snippet_list] : snippets) {
std::string combined_snippets;

return final_shader;
}
// Combine all snippets for this placeholder
for (const auto &snippet : snippet_list) {
combined_snippets += snippet;
}

std::string WorldShaderCommands::generate_command_code() const {
std::string result = "";
// Find and replace the placeholder
std::string placeholder = "//@" + placeholder_id + "@";
size_t pos = result.find(placeholder);
if (pos == std::string::npos) {
throw Error(MSG(err) << "Placeholder disappeared from template: " << placeholder_id);
}

for (const auto &[alpha, command] : commands_map) {
result += " case " + std::to_string(alpha) + ":\n";
result += " // " + command.description + "\n";
result += " " + command.code + "\n";
result += " break;\n\n";
// Replace placeholder with combined snippets
result.replace(pos, placeholder.length(), combined_snippets);
}

return result;
}

bool WorldShaderCommands::validate_alpha(uint8_t alpha) const {
return alpha % 2 == 0 && alpha >= 0 && alpha <= 254;
}

bool WorldShaderCommands::validate_code(const std::string &code) const {
return !code.empty();
std::string ShaderCommandTemplate::trim(const std::string &str) const {
size_t first = str.find_first_not_of(" \t");
if (first == std::string::npos) {
return "";
}
size_t last = str.find_last_not_of(" \t");
return str.substr(first, (last - first + 1));
}

} // namespace openage::renderer::world
Loading

0 comments on commit 8cda18b

Please sign in to comment.