Imported from yhyu13/Engine2021 (
AGENTS.md). Install upstream withnpx skills add yhyu13/Engine2021. Copyright stays with the author.
LongMarch Engine - AGENTS.md Knowledge Base
Project Overview
LongMarch Engine is a C++20 game engine with OpenGL 4.5 and Vulkan support, developed for Windows 10 x64 using VS2019 16.8+.
Key Capabilities
- Rendering: Deferred and forward rendering pipelines
- Effects: SSR (Screen Space Reflections), SSDO (Screen Space Directional Occlusion), Motion Blur
- Tools: Editor with real-time preview, Remotery profiling integration
- Samples: Asteroids game demo, various rendering technique showcases
Repository Structure
Engine2021/
├── engine/
│ ├── source/engine/ # Core engine source
│ │ ├── renderer/ # Rendering subsystem
│ │ ├── mesh/ # Mesh & MeshData
│ │ ├── material/ # Material system
│ │ └── camera/ # Camera systems
│ └── external/ # Third-party libraries (ImGui, GLFW, etc.)
├── samples/ # Sample applications
├── DOC/ # Documentation (AI-generated)
│ ├── 1/ # Core architecture docs
│ ├── 2/ # Renderer subsystem docs
│ └── 3/ # Resource & scene management docs
└── assets/ # Game assets (via Git LFS)
Renderer Architecture
Core Components
graph TD
subgraph Renderer Core
RAPI[RendererAPI]
RC[RenderCommand]
R2[Renderer2D]
R3[Renderer3D]
end
subgraph Resource Interfaces
VA[VertexArray]
VB[VertexBuffer]
IB[IndexBuffer]
UB[UniformBuffer]
Sh[Shader]
Tex[Texture]
MeshH[Mesh]
Mat[Material]
end
subgraph Platform Backends
GL[OpenGLRendererAPI]
VK[VulkanRendererAPI]
end
RAPI --> GL
RAPI --> VK
RC --> RAPI
VA --> VB
VA --> IB
MeshH --> Mat
R2 --> VA
R2 --> Sh
R3 --> VA
R3 --> Sh
RendererAPI (Abstract Base)
Location: engine/source/engine/renderer/RendererAPI.h
Purpose: Cross-API renderer interface defining all core rendering commands.
Key Methods:
Init(),SetViewport(),SetClearColor(),Clear()DrawTriangleIndexed(),DrawTriangleIndexedInstanced()DispatchCompute()- Compute shader dispatchDepthTest(),StencilTest(),CullFace(),Blend()PushDebugMarker(),PopDebugMarker()- Profiling support
Enums:
API: None, OpenGL, VulkanCompareEnum: Depth/stencil comparison modesBlendFuncEnum: Blend modes (Addition, Multiplication, ALPHA_BLEND_1/2)
Static Selection:
RendererAPI::WhichAPI() // Returns current API
RendererAPI::s_eAPI // Active API type
RenderCommand (Static Facade)
Location: engine/source/engine/renderer/RenderCommand.h
Purpose: Thin, type-safe facade wrapping RendererAPI calls. All rendering goes through this class.
Initialization:
RenderCommand::SetAPI(OpenGLRendererAPI::GetInstance());
RenderCommand::Init();
Debug Markers (for profiling):
GPU_NAMED_SCOPE(function_name) // Stringified scope name
GPU_DEBUG_SCOPE("dynamic_name") // Runtime string
Clear Operations:
RenderCommand::Clear() // Color + depth + stencil
RenderCommand::ClearColorOnly()
RenderCommand::ClearDepthOnly()
RenderCommand::SetClearColor(color)
Draw Calls:
RenderCommand::DrawTriangleIndexed(vertexArray)
RenderCommand::DrawTriangleIndexedInstanced(vertexArray, count)
RenderCommand::DrawLineIndexed(vertexArray)
RenderCommand::DispatchCompute(numX, numY, numZ)
State Management:
RenderCommand::DepthTest(enabled, write)
RenderCommand::CullFace(enabled, front)
RenderCommand::Blend(enabled)
RenderCommand::PolyModeFill() // Solid
RenderCommand::PolyModeLine() // Wireframe
Buffer System
Location: engine/source/engine/renderer/Buffer.h
VertexDataType
Supported vertex attribute types:
- Float: Float, Float2, Float3, Float4 (4-16 bytes)
- Int: Int, Int2, Int3, Int4 (4-16 bytes)
- Half-float: HFloat, HFloat2, HFloat3, HFloat4 (2-8 bytes, fp16)
- Bool: 1 byte flags
VertexBufferLayout
Defines vertex attribute layout with automatic offset/stride calculation:
VertexBufferLayout layout;
layout.AddElement(VertexDataType::Float3, "a_Position"); // 12 bytes
layout.AddElement(VertexDataType::Float3, "a_Normal"); // 12 bytes
layout.AddElement(VertexDataType::Float2, "a_TexCoord"); // 8 bytes
// Total stride: 32 bytes, offsets auto-calculated
VertexBuffer
GPU-managed vertex attribute stream:
auto vb = VertexBuffer::Create(data, count);
vb->SetLayout(layout);
vb->Bind();
vb->UpdateBufferData(newData, count); // Full update
vb->UpdateBufferSubData(data, count, offset); // Partial update
vb->AppendBufferData(data, count); // Append to tail
IndexBuffer
Triangle index lists (16-bit or 32-bit):
auto ib = IndexBuffer::Create(indices, size, elementSize); // elementSize: 2 or 4
ib->Bind();
UniformBuffer (UBO)
Constant shader parameters with zero-copy mapping:
struct PerObjectBlock {
alignas(16) glm::mat4 model;
alignas(16) glm::mat4 normalMat;
};
auto ubo = UniformBuffer::Create(nullptr, sizeof(PerObjectBlock));
// Zero-copy update pattern
auto* mapped = UniformBuffer::GetUniformBufferMapping(size, offset);
memcpy(mapped, &data, size);
UniformBuffer::EndUniformBufferMapping();
ubo->Bind(slot); // Bind to UBO slot N
ShaderStorageBuffer (SSBO)
Read/write storage for compute shaders:
auto ssbo = ShaderStorageBuffer::Create(data, size);
ssbo->Bind(slot);
// Compute shader can read/write
Draw Indirect Commands
Batch draw calls stored on GPU:
struct DrawIndexedIndirectCommand {
uint32_t indexCount;
uint32_t instanceCount;
uint32_t firstIndex;
uint32_t baseVertex;
uint32_t baseInstance;
};
auto indirectBuf = IndexedIndirectCommandBuffer::Create(cmds, count);
RendererAPI::MultiDrawTriangleIndexedIndirect(va, indirectBuf);
Shader System
Location: engine/source/engine/renderer/Shader.h
Factory Methods
// Vertex + Fragment (+ optional Geometry)
auto shader = Shader::Create("shader.vert.glsl", "shader.frag.glsl");
auto shaderWithGS = Shader::Create("shader.vert", "shader.geom", "shader.frag");
// Compute shader only
auto computeShader = Shader::Create("kernel.comp.spv");
Uniform Setters
shader->Bind();
shader->SetInt("enabled", 1);
shader->SetFloat("exposure", 1.5f);
shader->SetFloat2("uvOffset", glm::vec2(0.5f));
shader->SetFloat3("color", glm::vec3(1.0f, 0.0f, 0.0f));
shader->SetFloat4("fogColor", glm::vec4(1.0f));
shader->SetMat3("normalMatrix", mat3);
shader->SetMat4("modelMatrix", mat4);
// Array uniforms
shader->SetIntV("lightIndices", count, lightIndices);
shader->SetFloat3V("lightPositions", count, positions);
Path Resolution
// Asset protocol prefix
auto s = Shader::Create("$assets:shaders/lighting.vert", "$assets:shaders/lighting.frag");
// Relative path
auto s = Shader::Create("shaders/postprocess.vs.glsl", "shaders/postprocess.fs.glsl");
Camera System
Location: engine/source/engine/renderer/camera/
PerspectiveCamera
3D perspective projection with FPS or LookAt modes:
PerspectiveCamera cam;
cam.SetProjection(fovy_rad, aspectRatio, nearZ, farZ);
cam.SetWorldPosition(Vec3f(0, 10, 0));
cam.SetLookAt(eye, at);
// Matrix access for shaders
auto proj = cam.GetProjectionMatrix();
auto viewProj = cam.GetViewProjectionMatrix();
auto prevViewProj = cam.GetPrevViewProjectionMatrix(); // Motion blur
Ray Casting (mouse interaction):
Vec3f rayOrigin, rayDir;
cam.GenerateRayFromCursorSpace(cursorPos, clipViewport, invertY, rayOrigin, rayDir);
// Intersect with plane
Vec4f plane(0, 1, 0, 0); // y = 0
Vec3f hitPoint;
cam.CursorSpaceToWorldSpace(cursorPos, plane, true, false, hitPoint);
OrthographicCamera
Parallel projection for 2D/top-down views:
OrthographicCamera cam(left, right, bottom, top);
cam.SetPosition(Vec3f(x, y, z));
cam.SetRotation(yawRadians);
auto viewProj = cam.GetViewProjectionMatrix();
2D Rendering
Sprite
Textured quad with transform state:
auto tex = Texture2D::LoadFromFile("$assets:sprites/player.png");
Sprite player(tex.get());
player.SetSpritePosition(Vec3f(x, y, 0));
player.SetSpriteScale(glm::vec2(2.0f, 2.0f));
player.SetSpriteRotation(3.14159f / 4); // Radians
player.SetSpriteAlpha(0.8f);
player.Update(dt);
player.Draw();
Renderer2D
Batch 2D primitives:
Renderer2D::Init();
Renderer2D::BeginScene(camera);
Renderer2D::DrawQuad(position, scale, rotation, color);
Renderer2D::DrawSprite(sprite);
Renderer2D::EndScene();
// Batching API
Renderer2D::BeginBatch();
Renderer2D::AddBatch(sprite);
Renderer2D::DrawBatch();
Mesh & Material System
Mesh
Geometry + material container:
struct Mesh {
std::string meshName;
std::shared_ptr<MeshData> meshData;
std::shared_ptr<Material> material;
void Draw(); // Submit draw call
std::shared_ptr<Mesh> Copy(); // Deep copy material, shallow copy meshData
};
MeshData
Vertex/index buffer wrapper:
auto meshData = std::make_shared<MeshData>();
// Fill meshData->vertices, meshData->indices
meshData->Init(); // Upload to GPU
meshData->Draw();
Material (PBR)
Physically-based rendering properties:
auto mat = std::make_shared<Material>();
mat->Kd = Vec3f(1.0f, 0.0f, 0.0f); // Diffuse color
mat->metallic = 1.0f; // Full metal
mat->roughness = 0.1f; // Polished
mat->alpha = 0.8f; // Transparency
mat->emissive = true; // Self-illuminated
// PBR textures
mat->SetTexture("albedo", "$assets:tex/albedo.png", MAT_TEXTURE_TYPE::ALBEDO);
mat->SetTexture("normal", "$assets:tex/normal.png", MAT_TEXTURE_TYPE::NORMAL);
mat->SetTexture("metallic", "$assets:tex/metallic.png", MAT_TEXTURE_TYPE::METALLIC);
mat->SetTexture("roughness", "$assets:tex/roughness.png", MAT_TEXTURE_TYPE::ROUGHNESS);
mat->SetTexture("ao", "$assets:tex/ao.png", MAT_TEXTURE_TYPE::BACKEDAO);
// Bind textures before draw
mat->BindAllTexture({
{0, MAT_TEXTURE_TYPE::ALBEDO},
{1, MAT_TEXTURE_TYPE::NORMAL},
{2, MAT_TEXTURE_TYPE::METALLIC},
{3, MAT_TEXTURE_TYPE::ROUGHNESS},
{4, MAT_TEXTURE_TYPE::BACKEDAO}
});
Texture Types:
ALBEDO: Diffuse/color map (sRGB)NORMAL: Tangent-space normal map (linear)METALLIC: Metallic factor (grayscale)ROUGHNESS: Roughness factor (grayscale)BACKEDAO: Baked ambient occlusion
Usage Patterns
Basic 3D Rendering
// Initialization
RenderCommand::SetAPI(OpenGLRendererAPI::GetInstance());
RenderCommand::Init();
Renderer2D::Init();
// Load resources
auto shader = Shader::Create("shaders/pbr.vert", "shaders/pbr.frag");
auto mesh = Mesh::LoadFromFile("$assets:models/character.mesh");
auto texture = Texture2D::LoadFromFile("$assets:textures/albedo.png");
// Camera setup
PerspectiveCamera cam;
cam.SetProjection(glm::radians(90.0f), 16.0f/9.0f, 0.1f, 2000.0f);
cam.SetWorldPosition(Vec3f(0, 10, 5));
cam.SetLookAt(Vec3f(0,0,0), Vec3f(0,1,0));
// Render loop
while (running) {
// Setup
RenderCommand::SetViewport(0, 0, width, height);
RenderCommand::SetClearColor(glm::vec4(0.1f, 0.15f, 0.2f, 1.0f));
RenderCommand::Clear();
RenderCommand::DepthTest(true, true);
RenderCommand::CullFace(true);
// Draw
shader->Bind();
shader->SetMat4("u_ViewProjection", cam.GetViewProjectionMatrix());
mesh->material->BindAllTexture({{0, ALBEDO}});
mesh->Draw();
// Present
RenderCommand::EndFrame();
}
Compute Shader Workflow
// Load compute shader
auto computeShader = Shader::Create("kernels/particle_sim.comp.spv");
// Create SSBO for particle data
struct ParticleData {
Vec3f positions[MAX_PARTICLES];
Vec3f velocities[MAX_PARTICLES];
};
auto ssbo = ShaderStorageBuffer::Create(nullptr, sizeof(ParticleData));
ssbo->Bind(0);
// Dispatch compute
computeShader->Bind();
RenderCommand::DispatchCompute(
(MAX_PARTICLES + 63) / 64, // Work groups X (64 threads per group)
1, 1
);
// Memory barrier before fragment shader reads
RenderCommand::PlaceMemoryBarrier(SHADER_STORAGE_BUFFER_BARRIER);
Post-Processing Chain
// G-buffer pass
GPU_NAMED_SCOPE(gbuffer_pass)
{
gBuffer->Bind();
RenderCommand::Clear();
RenderGeometryPass(scene);
RenderCommand::BindDefaultFrameBuffer();
}
// Bloom extraction
GPU_NAMED_SCOPE(bloom_extract)
{
bloomExtractShader->Bind();
bloomExtractShader->SetTexture("u_GBuffer", 0);
RenderCommand::DrawQuadIndexed(fullscreenQuadVA);
}
// Gaussian blur (horizontal + vertical)
GPU_NAMED_SCOPE(bloom_blur)
{
blurShader->Bind();
// Horizontal pass
RenderCommand::TransferColorBit(src, w, h, temp, w, h);
// Vertical pass
RenderCommand::TransferColorBit(temp, w, h, dst, w, h);
}
// Composite
GPU_NAMED_SCOPE(bloom_composite)
{
compositeShader->Bind();
RenderCommand::TransferAllBit(mainFB, width, height, finalFB, width, height);
}
Development Workflow
Setup
- Download large files:
git lfs pull - Unzip assets:
./unzip-assets.bat - Generate project:
./generate-project.batin sample folder - Profile: Open
engine/external/Remotery/vis/index.html
Build Configuration
- C++ Standard: C++20
- Graphics API: OpenGL 4.5 (Vulkan WIP)
- Platform: Windows 10 x64
- IDE: Visual Studio 2019 16.8+
Profiling
- Remotery: Built-in profiler (vis/index.html)
- Nvidia Nsight: Compatible (may conflict with Remotery)
- RenderDoc: Supported via debug markers
Key Design Patterns
1. Static Facade (RenderCommand)
All rendering calls go through static methods, delegating to backend:
// No conditionals in render loop
RenderCommand::DrawTriangleIndexed(va); // Always works, regardless of backend
2. Smart Pointer Resource Management
std::shared_ptr<VertexBuffer> vb = VertexBuffer::Create(data, count);
std::shared_ptr<Shader> shader = Shader::Create("vert.glsl", "frag.glsl");
3. Zero-Copy Buffer Mapping
// Direct CPU write to GPU-mapped memory
void* mapped = UniformBuffer::GetUniformBufferMapping(size, offset);
// Modify directly
UniformBuffer::EndUniformBufferMapping(); // Commit
4. Scoped Debug Markers
void RenderFrame() {
GPU_NAMED_SCOPE(frame_render) // Auto push/pop markers
{
// ... rendering code
}
}
5. Asset Protocol
// Path resolution with protocol prefix
"$assets:path/to/file.ext" // Resolved via engine filesystem
Common Pitfalls
1. Forgetting to Initialize
// WRONG: Calling render commands before Init()
RenderCommand::Clear(); // Crash: s_RendererAPI is nullptr
// CORRECT:
RenderCommand::SetAPI(OpenGLRendererAPI::GetInstance());
RenderCommand::Init();
RenderCommand::Clear();
2. Shader Uniform Name Mismatch
// GLSL: uniform mat4 u_Model;
shader->SetMat4("u_Model", matrix); // Exact match required
// WRONG: shader->SetMat4("model", matrix); // Won't work
3. Texture Binding Order
// Must bind textures BEFORE draw call
material->BindAllTexture({{0, ALBEDO}});
shader->Bind();
RenderCommand::DrawTriangleIndexed(va); // Textures now active
4. Reverse-Z Depth
Enable for better depth precision in large scenes:
RenderCommand::Reverse_Z(true); // Do this once at startup
5. Memory Barriers for Compute
// WRONG: Using compute output immediately
DispatchCompute();
DrawWithResult(); // May read stale data
// CORRECT:
DispatchCompute();
RenderCommand::PlaceMemoryBarrier(SHADER_STORAGE_BUFFER_BARRIER);
DrawWithResult(); // Now guaranteed fresh
File Conventions
Shader Extensions
.vert.glsl: Vertex shader (GLSL source).frag.glsl: Fragment shader (GLSL source).geom.glsl: Geometry shader (GLSL source).comp.spv: Compute shader (SPIR-V binary).vert,.frag,.comp: Auto-detected format
Asset Paths
// Protocol prefix (recommended)
"$assets:models/character.mesh"
// Relative path (also works)
"models/character.mesh"
Material Files
.mat: Material definition (custom format).json: JSON-based material config
Performance Tips
1. Batch by Material
Group draw calls by shader + material to minimize state changes:
// GOOD: All opaque objects with same material drawn together
for (auto& mesh : opaqueMeshes) {
if (mesh.material == currentMaterial) {
mesh.Draw();
}
}
2. Use UBOs for Per-Object Data
// BETTER: Single UBO update per object
ubo->UpdateBufferData(&transform, sizeof(Transform));
// WORSE: Multiple uniform setters
shader->SetMat4("model", transform.model);
shader->SetMat4("normal", transform.normal);
shader->SetMat4("prevModel", transform.prevModel);
3. Indirect Drawing for Instancing
// For 1000+ instances, use indirect draw
auto indirectBuf = CreateIndirectCommands(instances);
RendererAPI::MultiDrawTriangleIndexedIndirect(va, indirectBuf);
4. Lazy Sprite Initialization
// Sprite VAO/VBO created on first OpenGLInit() call
Sprite sprite(texture);
sprite.OpenGLInit(); // Call once before first Draw()
Testing & Verification
Build Verification
# After git clone
git lfs pull
./unzip-assets.bat
./generate-project.bat
# Open .sln in VS2019, build Release x64
RenderDoc Capture
- Launch app via RenderDoc
- Capture frame during rendering
- Inspect draw calls, uniforms, textures
Profiling with Remotery
// Built-in scopes via GPU_NAMED_SCOPE
// View in browser: engine/external/Remotery/vis/index.html
Related Documentation
/DOC/1/: Core architecture, API reference/DOC/2/: Renderer subsystem (this doc)/DOC/3/: Resource management, scene graph/DOC/*/Output/: Detailed markdown per subsystem
Last Updated: 2026-03-21
Engine Version: LongMarch Engine v1.0 (Engine2021)
