refactor(renderer): 移除实例化渲染功能及相关代码
移除不再需要的实例化渲染功能,包括着色器、材质、渲染命令和测试代码 优化实例缓冲区实现,添加脏标记和增量更新功能
This commit is contained in:
parent
97be1b746a
commit
e0b0a7883d
|
|
@ -1,5 +1,4 @@
|
||||||
#include "game_scene.h"
|
#include "game_scene.h"
|
||||||
#include "instanced_test.h"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
@ -80,9 +79,6 @@ void GameScene::onEnter() {
|
||||||
|
|
||||||
// 创建装饰物
|
// 创建装饰物
|
||||||
createDecorations();
|
createDecorations();
|
||||||
|
|
||||||
// 创建实例化渲染测试(1000个实例)
|
|
||||||
createInstancedTest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::onExit() {
|
void GameScene::onExit() {
|
||||||
|
|
@ -93,6 +89,7 @@ void GameScene::onExit() {
|
||||||
Scene::onExit();
|
Scene::onExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GameScene::update(float dt) {
|
void GameScene::update(float dt) {
|
||||||
Scene::update(dt);
|
Scene::update(dt);
|
||||||
|
|
||||||
|
|
@ -107,11 +104,6 @@ void GameScene::update(float dt) {
|
||||||
for (auto &decoration : decorations_) {
|
for (auto &decoration : decorations_) {
|
||||||
decoration->onUpdate(dt);
|
decoration->onUpdate(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新实例化测试
|
|
||||||
if (instancedTest_) {
|
|
||||||
instancedTest_->update(dt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::createPlayer() {
|
void GameScene::createPlayer() {
|
||||||
|
|
@ -174,12 +166,3 @@ void GameScene::createCamera() {
|
||||||
// 添加相机节点到场景
|
// 添加相机节点到场景
|
||||||
addChild(cameraNode);
|
addChild(cameraNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::createInstancedTest() {
|
|
||||||
// 创建实例化渲染测试节点
|
|
||||||
auto instancedTest = makePtr<InstancedTestNode>();
|
|
||||||
if (instancedTest->initialize(1000)) {
|
|
||||||
instancedTest_ = instancedTest;
|
|
||||||
addChild(instancedTest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d.h>
|
#include <extra2d.h>
|
||||||
#include "instanced_test.h"
|
|
||||||
|
|
||||||
using namespace extra2d;
|
using namespace extra2d;
|
||||||
|
|
||||||
|
|
@ -135,13 +134,7 @@ private:
|
||||||
*/
|
*/
|
||||||
void createCamera();
|
void createCamera();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建实例化渲染测试
|
|
||||||
*/
|
|
||||||
void createInstancedTest();
|
|
||||||
|
|
||||||
Ptr<PlayerNode> player_;
|
Ptr<PlayerNode> player_;
|
||||||
std::vector<Ptr<RotatingDecoration>> decorations_;
|
std::vector<Ptr<RotatingDecoration>> decorations_;
|
||||||
Ptr<InstancedTestNode> instancedTest_;
|
|
||||||
float sceneTime_ = 0.0f;
|
float sceneTime_ = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
#include "instanced_test.h"
|
|
||||||
#include <assets/assets_module.h>
|
|
||||||
#include <event/events.h>
|
|
||||||
#include <module/module_registry.h>
|
|
||||||
#include <renderer/renderer_module.h>
|
|
||||||
#include <utils/logger.h>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
InstancedTestNode::InstancedTestNode() {
|
|
||||||
setName("InstancedTest");
|
|
||||||
}
|
|
||||||
|
|
||||||
InstancedTestNode::~InstancedTestNode() {
|
|
||||||
instanceBuffer_.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InstancedTestNode::initialize(uint32_t instanceCount) {
|
|
||||||
instanceCount_ = instanceCount;
|
|
||||||
|
|
||||||
// 获取资源模块
|
|
||||||
auto* assets = getModule<AssetsModule>();
|
|
||||||
if (!assets) {
|
|
||||||
E2D_LOG_ERROR("InstancedTestNode: AssetsModule not available");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用默认网格(四边形)
|
|
||||||
mesh_ = assets->getDefaultQuad();
|
|
||||||
if (!mesh_.isValid()) {
|
|
||||||
E2D_LOG_ERROR("InstancedTestNode: Failed to get default quad mesh");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用默认纹理
|
|
||||||
texture_ = assets->getDefaultTexture();
|
|
||||||
|
|
||||||
// 获取实例化渲染材质
|
|
||||||
material_ = assets->getInstancedMaterial();
|
|
||||||
if (!material_.isValid()) {
|
|
||||||
E2D_LOG_ERROR("InstancedTestNode: Failed to get instanced material");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化实例缓冲区
|
|
||||||
if (!instanceBuffer_.initialize(instanceCount)) {
|
|
||||||
E2D_LOG_ERROR("InstancedTestNode: Failed to initialize instance buffer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预分配实例数据
|
|
||||||
instanceData_.resize(instanceCount);
|
|
||||||
|
|
||||||
// 初始化实例数据
|
|
||||||
updateInstances();
|
|
||||||
|
|
||||||
E2D_LOG_INFO("InstancedTestNode initialized with {} instances", instanceCount);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InstancedTestNode::update(float dt) {
|
|
||||||
time_ += dt;
|
|
||||||
|
|
||||||
// 更新实例变换
|
|
||||||
updateInstances();
|
|
||||||
|
|
||||||
// 更新GPU缓冲区
|
|
||||||
if (!instanceData_.empty()) {
|
|
||||||
instanceBuffer_.updateInstances(instanceData_.data(), instanceCount_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InstancedTestNode::updateInstances() {
|
|
||||||
// 创建螺旋分布的实例
|
|
||||||
float radius = 200.0f;
|
|
||||||
float centerX = 640.0f;
|
|
||||||
float centerY = 360.0f;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < instanceCount_; ++i) {
|
|
||||||
float t = static_cast<float>(i) / instanceCount_;
|
|
||||||
float angle = t * 6.28318f * 3.0f + time_; // 3圈螺旋
|
|
||||||
float r = radius * (0.2f + 0.8f * t);
|
|
||||||
|
|
||||||
// 位置
|
|
||||||
instanceData_[i].position.x = centerX + r * std::cos(angle);
|
|
||||||
instanceData_[i].position.y = centerY + r * std::sin(angle);
|
|
||||||
|
|
||||||
// 旋转(朝向中心)
|
|
||||||
instanceData_[i].rotation = angle + 1.5708f;
|
|
||||||
|
|
||||||
// 缩放(随距离变化)
|
|
||||||
float scale = 0.5f + 0.5f * t;
|
|
||||||
instanceData_[i].scale.x = scale * 32.0f;
|
|
||||||
instanceData_[i].scale.y = scale * 32.0f;
|
|
||||||
|
|
||||||
// 颜色(彩虹色)
|
|
||||||
float hue = t + time_ * 0.1f;
|
|
||||||
float r_color = std::abs(std::fmod(hue * 6.0f, 2.0f) - 1.0f);
|
|
||||||
float g_color = std::abs(std::fmod(hue * 6.0f + 2.0f, 2.0f) - 1.0f);
|
|
||||||
float b_color = std::abs(std::fmod(hue * 6.0f + 4.0f, 2.0f) - 1.0f);
|
|
||||||
|
|
||||||
instanceData_[i].color.r = r_color;
|
|
||||||
instanceData_[i].color.g = g_color;
|
|
||||||
instanceData_[i].color.b = b_color;
|
|
||||||
instanceData_[i].color.a = 1.0f;
|
|
||||||
|
|
||||||
// UV坐标(使用完整纹理)
|
|
||||||
instanceData_[i].uvX = 0.0f;
|
|
||||||
instanceData_[i].uvY = 0.0f;
|
|
||||||
instanceData_[i].uvWidth = 1.0f;
|
|
||||||
instanceData_[i].uvHeight = 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InstancedTestNode::render() {
|
|
||||||
if (!isVisible() || instanceCount_ == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查资源有效性
|
|
||||||
if (!mesh_.isValid() || !material_.isValid() || !instanceBuffer_.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交实例化渲染命令
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = RenderCommandType::DrawMeshInstanced;
|
|
||||||
cmd.sortKey = 0;
|
|
||||||
cmd.drawInstanced.mesh = mesh_;
|
|
||||||
cmd.drawInstanced.material = material_;
|
|
||||||
cmd.drawInstanced.instanceBuffer = &instanceBuffer_;
|
|
||||||
cmd.drawInstanced.instanceCount = instanceCount_;
|
|
||||||
cmd.drawInstanced.instanceOffset = 0;
|
|
||||||
|
|
||||||
events::OnRenderSubmit::emit(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <scene/node.h>
|
|
||||||
#include <renderer/instance_buffer.h>
|
|
||||||
#include <renderer/material.h>
|
|
||||||
#include <renderer/mesh.h>
|
|
||||||
#include <renderer/texture.h>
|
|
||||||
#include <assets/handle.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 实例化渲染测试节点
|
|
||||||
*
|
|
||||||
* 测试实例化渲染功能,渲染大量相同精灵但使用不同变换
|
|
||||||
*/
|
|
||||||
class InstancedTestNode : public Node {
|
|
||||||
public:
|
|
||||||
InstancedTestNode();
|
|
||||||
~InstancedTestNode() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化实例化渲染资源
|
|
||||||
* @param instanceCount 实例数量
|
|
||||||
* @return 初始化是否成功
|
|
||||||
*/
|
|
||||||
bool initialize(uint32_t instanceCount = 1000);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 每帧更新实例数据
|
|
||||||
* @param dt 时间增量
|
|
||||||
*/
|
|
||||||
void update(float dt);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染实例(收集渲染命令)
|
|
||||||
*/
|
|
||||||
void render() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
InstanceBuffer instanceBuffer_; // 实例缓冲区
|
|
||||||
std::vector<InstanceData> instanceData_; // CPU端实例数据
|
|
||||||
uint32_t instanceCount_ = 0; // 实例数量
|
|
||||||
float time_ = 0.0f; // 时间累积
|
|
||||||
|
|
||||||
// 资源句柄
|
|
||||||
Handle<Material> material_;
|
|
||||||
Handle<Mesh> mesh_;
|
|
||||||
Handle<Texture> texture_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新实例变换
|
|
||||||
*/
|
|
||||||
void updateInstances();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#version 320 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
// 从顶点着色器输入
|
|
||||||
in vec2 vTexCoord;
|
|
||||||
in vec4 vColor;
|
|
||||||
|
|
||||||
// 纹理采样器
|
|
||||||
uniform sampler2D uTexture;
|
|
||||||
|
|
||||||
// 输出颜色
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 片段着色器入口(实例化版本)
|
|
||||||
*
|
|
||||||
* 采样纹理并与顶点颜色混合
|
|
||||||
*/
|
|
||||||
void main() {
|
|
||||||
// 采样纹理
|
|
||||||
vec4 texColor = texture(uTexture, vTexCoord);
|
|
||||||
|
|
||||||
// 如果纹理采样结果是黑色或透明,使用白色作为默认值
|
|
||||||
if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) {
|
|
||||||
texColor = vec4(1.0, 1.0, 1.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 混合:纹理 * 顶点颜色
|
|
||||||
fragColor = texColor * vColor;
|
|
||||||
|
|
||||||
// Alpha 测试:丢弃几乎透明的像素
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
#version 320 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
// 全局 UBO (binding = 0) - 每帧更新一次
|
|
||||||
layout(std140, binding = 0) uniform GlobalUBO {
|
|
||||||
mat4 uViewProjection;
|
|
||||||
vec4 uCameraPosition;
|
|
||||||
float uTime;
|
|
||||||
float uDeltaTime;
|
|
||||||
vec2 uScreenSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 顶点属性
|
|
||||||
layout(location = 0) in vec2 aPosition; // 基础顶点位置
|
|
||||||
layout(location = 1) in vec2 aTexCoord; // 基础 UV
|
|
||||||
layout(location = 2) in vec4 aColor; // 基础颜色
|
|
||||||
|
|
||||||
// 实例属性 (每个实例)
|
|
||||||
layout(location = 3) in vec2 aInstancePos; // 实例位置偏移
|
|
||||||
layout(location = 4) in float aInstanceRot; // 实例旋转
|
|
||||||
layout(location = 5) in vec2 aInstanceScale; // 实例缩放
|
|
||||||
layout(location = 6) in vec4 aInstanceColor; // 实例颜色
|
|
||||||
layout(location = 7) in vec4 aInstanceUV; // 实例 UV 区域
|
|
||||||
|
|
||||||
// 输出到片段着色器
|
|
||||||
out vec2 vTexCoord;
|
|
||||||
out vec4 vColor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 顶点着色器入口(实例化版本)
|
|
||||||
*
|
|
||||||
* 计算顶点在裁剪空间中的位置,应用实例的变换
|
|
||||||
*/
|
|
||||||
void main() {
|
|
||||||
// 构建旋转矩阵
|
|
||||||
float cosRot = cos(aInstanceRot);
|
|
||||||
float sinRot = sin(aInstanceRot);
|
|
||||||
mat2 rotation = mat2(
|
|
||||||
cosRot, -sinRot,
|
|
||||||
sinRot, cosRot
|
|
||||||
);
|
|
||||||
|
|
||||||
// 应用缩放和旋转
|
|
||||||
vec2 scaledPos = aPosition * aInstanceScale;
|
|
||||||
vec2 rotatedPos = rotation * scaledPos;
|
|
||||||
|
|
||||||
// 应用实例位置偏移
|
|
||||||
vec2 worldPos = rotatedPos + aInstancePos;
|
|
||||||
|
|
||||||
// 计算裁剪空间位置
|
|
||||||
gl_Position = uViewProjection * vec4(worldPos, 0.0, 1.0);
|
|
||||||
|
|
||||||
// 计算 UV 坐标(应用实例 UV 区域)
|
|
||||||
vTexCoord = aTexCoord * aInstanceUV.zw + aInstanceUV.xy;
|
|
||||||
|
|
||||||
// 混合顶点颜色和实例颜色
|
|
||||||
vColor = aColor * aInstanceColor;
|
|
||||||
}
|
|
||||||
|
|
@ -10,7 +10,7 @@ local example_dir = os.scriptdir()
|
||||||
-- 可执行文件目标
|
-- 可执行文件目标
|
||||||
target("scene_graph_demo")
|
target("scene_graph_demo")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("main.cpp", "game_scene.cpp", "instanced_test.cpp")
|
add_files("main.cpp", "game_scene.cpp")
|
||||||
add_includedirs("../../include", ".")
|
add_includedirs("../../include", ".")
|
||||||
add_deps("extra2d")
|
add_deps("extra2d")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,31 +176,6 @@ public:
|
||||||
Material *getDefaultMaterialPtr();
|
Material *getDefaultMaterialPtr();
|
||||||
Mesh *getDefaultQuadPtr();
|
Mesh *getDefaultQuadPtr();
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
// 实例化渲染资源
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取实例化渲染着色器
|
|
||||||
* @return 实例化着色器句柄
|
|
||||||
*/
|
|
||||||
Handle<Shader> getInstancedShader();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取实例化渲染材质
|
|
||||||
* @return 实例化材质句柄
|
|
||||||
*/
|
|
||||||
Handle<Material> getInstancedMaterial();
|
|
||||||
|
|
||||||
Shader *getInstancedShaderPtr();
|
|
||||||
Material *getInstancedMaterialPtr();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建实例化渲染资源
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
bool createInstancedResources();
|
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 热重载
|
// 热重载
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
@ -285,10 +260,6 @@ private:
|
||||||
Handle<Material> defaultMaterial_;
|
Handle<Material> defaultMaterial_;
|
||||||
Handle<Mesh> defaultQuad_;
|
Handle<Mesh> defaultQuad_;
|
||||||
|
|
||||||
// 实例化渲染资源
|
|
||||||
Handle<Shader> instancedShader_;
|
|
||||||
Handle<Material> instancedMaterial_;
|
|
||||||
|
|
||||||
// 热重载
|
// 热重载
|
||||||
bool hotReloadEnabled_ = false;
|
bool hotReloadEnabled_ = false;
|
||||||
float hotReloadInterval_ = 1.0f;
|
float hotReloadInterval_ = 1.0f;
|
||||||
|
|
|
||||||
|
|
@ -65,29 +65,23 @@ struct DrawCommand {
|
||||||
BufferHandle indexBuffer; // 索引缓冲区(可选)
|
BufferHandle indexBuffer; // 索引缓冲区(可选)
|
||||||
uint32_t vertexCount; // 顶点数量
|
uint32_t vertexCount; // 顶点数量
|
||||||
uint32_t indexCount; // 索引数量
|
uint32_t indexCount; // 索引数量
|
||||||
uint32_t instanceCount; // 实例数量(1表示非实例化)
|
|
||||||
std::array<TextureHandle, 8> textures; // 纹理数组
|
std::array<TextureHandle, 8> textures; // 纹理数组
|
||||||
uint32_t textureCount; // 纹理数量
|
uint32_t textureCount; // 纹理数量
|
||||||
BufferHandle materialUBO; // 材质 UBO
|
BufferHandle materialUBO; // 材质 UBO
|
||||||
uint32_t materialUBOSize; // 材质 UBO 大小
|
uint32_t materialUBOSize; // 材质 UBO 大小
|
||||||
uint32_t materialUBOOffset; // 材质 UBO 在全局缓冲区中的偏移
|
uint32_t materialUBOOffset; // 材质 UBO 在全局缓冲区中的偏移
|
||||||
BufferHandle instanceBuffer; // 实例数据缓冲区(实例化渲染使用)
|
|
||||||
uint32_t instanceBufferStride; // 实例数据步长
|
|
||||||
|
|
||||||
// 变换和颜色数据(用于设置 shader uniform)
|
// 变换和颜色数据(用于设置 shader uniform)
|
||||||
Mat4 modelMatrix; // 模型矩阵
|
Mat4 modelMatrix; // 模型矩阵
|
||||||
Color color; // 颜色
|
Color color; // 颜色
|
||||||
|
|
||||||
DrawCommand()
|
DrawCommand()
|
||||||
: vertexCount(0), indexCount(0), instanceCount(1), textureCount(0),
|
: vertexCount(0), indexCount(0), textureCount(0),
|
||||||
materialUBOSize(0), materialUBOOffset(0), instanceBufferStride(0),
|
materialUBOSize(0), materialUBOOffset(0),
|
||||||
modelMatrix(glm::identity<Mat4>()), color(Color::White) {}
|
modelMatrix(glm::identity<Mat4>()), color(Color::White) {}
|
||||||
|
|
||||||
// 检查是否使用索引绘制
|
// 检查是否使用索引绘制
|
||||||
bool isIndexed() const { return indexCount > 0; }
|
bool isIndexed() const { return indexCount > 0; }
|
||||||
|
|
||||||
// 检查是否实例化绘制
|
|
||||||
bool isInstanced() const { return instanceCount > 1; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -294,16 +288,6 @@ public:
|
||||||
void submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
void submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
const struct Transform &transform, const Color &color);
|
const struct Transform &transform, const Color &color);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 提交实例化绘制命令
|
|
||||||
* @param material 材质
|
|
||||||
* @param mesh 网格
|
|
||||||
* @param instanceBuffer 实例数据缓冲区
|
|
||||||
* @param instanceCount 实例数量
|
|
||||||
*/
|
|
||||||
void submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
|
||||||
BufferHandle instanceBuffer, uint32_t instanceCount);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 提交清除命令
|
* @brief 提交清除命令
|
||||||
* @param color 清除颜色
|
* @param color 清除颜色
|
||||||
|
|
|
||||||
|
|
@ -15,21 +15,25 @@ namespace extra2d {
|
||||||
*
|
*
|
||||||
* 单个实例的属性数据,用于实例化渲染
|
* 单个实例的属性数据,用于实例化渲染
|
||||||
* 布局遵循 std140 对齐规则
|
* 布局遵循 std140 对齐规则
|
||||||
|
*
|
||||||
|
* 优化说明:
|
||||||
|
* - 旋转使用预计算的 cos/sin 值,避免在着色器中进行三角函数计算
|
||||||
|
* - 属性布局优化,便于GPU访问
|
||||||
*/
|
*/
|
||||||
struct InstanceData {
|
struct InstanceData {
|
||||||
// 第一组 16 字节
|
// 第一组 16 字节 - 变换数据
|
||||||
Vec2 position; // 位置偏移 (8 bytes)
|
Vec2 position; // 位置偏移 (8 bytes)
|
||||||
float rotation; // 旋转角度 (4 bytes)
|
float rotationCos; // 旋转角度余弦值 (4 bytes) - 预计算避免着色器三角函数
|
||||||
float padding0; // 填充到 16 字节对齐 (4 bytes)
|
float rotationSin; // 旋转角度正弦值 (4 bytes) - 预计算避免着色器三角函数
|
||||||
|
|
||||||
// 第二组 16 字节
|
// 第二组 16 字节 - 缩放和预留
|
||||||
Vec2 scale; // 缩放 (8 bytes)
|
Vec2 scale; // 缩放 (8 bytes)
|
||||||
float padding1[2]; // 填充到 16 字节对齐 (8 bytes)
|
float padding1[2]; // 填充到 16 字节对齐 (8 bytes)
|
||||||
|
|
||||||
// 第三组 16 字节
|
// 第三组 16 字节 - 颜色
|
||||||
Color color; // 颜色 (16 bytes) - r, g, b, a
|
Color color; // 颜色 (16 bytes) - r, g, b, a
|
||||||
|
|
||||||
// 第四组 16 字节
|
// 第四组 16 字节 - UV坐标
|
||||||
float uvX; // UV 起始 X (4 bytes)
|
float uvX; // UV 起始 X (4 bytes)
|
||||||
float uvY; // UV 起始 Y (4 bytes)
|
float uvY; // UV 起始 Y (4 bytes)
|
||||||
float uvWidth; // UV 宽度 (4 bytes)
|
float uvWidth; // UV 宽度 (4 bytes)
|
||||||
|
|
@ -37,8 +41,8 @@ struct InstanceData {
|
||||||
|
|
||||||
InstanceData()
|
InstanceData()
|
||||||
: position(0.0f, 0.0f)
|
: position(0.0f, 0.0f)
|
||||||
, rotation(0.0f)
|
, rotationCos(1.0f) // cos(0) = 1
|
||||||
, padding0(0.0f)
|
, rotationSin(0.0f) // sin(0) = 0
|
||||||
, scale(1.0f, 1.0f)
|
, scale(1.0f, 1.0f)
|
||||||
, padding1{0.0f, 0.0f}
|
, padding1{0.0f, 0.0f}
|
||||||
, color(Color::White)
|
, color(Color::White)
|
||||||
|
|
@ -46,15 +50,65 @@ struct InstanceData {
|
||||||
, uvY(0.0f)
|
, uvY(0.0f)
|
||||||
, uvWidth(1.0f)
|
, uvWidth(1.0f)
|
||||||
, uvHeight(1.0f) {}
|
, uvHeight(1.0f) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置旋转角度(自动计算cos/sin)
|
||||||
|
* @param angle 旋转角度(弧度)
|
||||||
|
*/
|
||||||
|
void setRotation(float angle) {
|
||||||
|
rotationCos = std::cos(angle);
|
||||||
|
rotationSin = std::sin(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取旋转角度(通过atan2计算,仅用于调试)
|
||||||
|
* @return 旋转角度(弧度)
|
||||||
|
*/
|
||||||
|
float getRotation() const {
|
||||||
|
return std::atan2(rotationSin, rotationCos);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(InstanceData) == 64, "InstanceData size should be 64 bytes for std140 alignment");
|
static_assert(sizeof(InstanceData) == 64, "InstanceData size should be 64 bytes for std140 alignment");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 脏区域标记
|
||||||
|
*
|
||||||
|
* 标记需要更新的实例数据区域,支持部分更新GPU缓冲区
|
||||||
|
*/
|
||||||
|
struct DirtyRange {
|
||||||
|
uint32_t start; // 起始实例索引
|
||||||
|
uint32_t count; // 实例数量
|
||||||
|
|
||||||
|
DirtyRange(uint32_t s = 0, uint32_t c = 0) : start(s), count(c) {}
|
||||||
|
|
||||||
|
bool isValid() const { return count > 0; }
|
||||||
|
uint32_t end() const { return start + count; }
|
||||||
|
|
||||||
|
// 检查是否与另一个区域重叠或相邻
|
||||||
|
bool canMerge(const DirtyRange& other) const {
|
||||||
|
return (start <= other.end() && other.start <= end()) ||
|
||||||
|
(end() + 1 >= other.start && other.end() + 1 >= start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并两个区域
|
||||||
|
void merge(const DirtyRange& other) {
|
||||||
|
uint32_t newStart = std::min(start, other.start);
|
||||||
|
uint32_t newEnd = std::max(end(), other.end());
|
||||||
|
start = newStart;
|
||||||
|
count = newEnd - newStart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 实例缓冲区
|
* @brief 实例缓冲区
|
||||||
*
|
*
|
||||||
* 管理实例化渲染的实例数据缓冲区
|
* 管理实例化渲染的实例数据缓冲区
|
||||||
* 支持动态更新和双缓冲
|
* 支持动态更新和双缓冲
|
||||||
|
*
|
||||||
|
* 优化特性:
|
||||||
|
* - 脏标记系统:只更新变化的实例数据,减少PCIe带宽占用
|
||||||
|
* - 批量更新:合并相邻的脏区域,减少GPU上传次数
|
||||||
*/
|
*/
|
||||||
class InstanceBuffer {
|
class InstanceBuffer {
|
||||||
public:
|
public:
|
||||||
|
|
@ -89,13 +143,22 @@ public:
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 更新实例数据
|
* @brief 更新实例数据(全量更新,标记整个缓冲区为脏)
|
||||||
* @param instances 实例数据数组
|
* @param instances 实例数据数组
|
||||||
* @param count 实例数量
|
* @param count 实例数量
|
||||||
* @return 更新是否成功
|
* @return 更新是否成功
|
||||||
*/
|
*/
|
||||||
bool updateInstances(const InstanceData* instances, uint32_t count);
|
bool updateInstances(const InstanceData* instances, uint32_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新部分实例数据(增量更新)
|
||||||
|
* @param instances 实例数据数组
|
||||||
|
* @param start 起始实例索引
|
||||||
|
* @param count 实例数量
|
||||||
|
* @return 更新是否成功
|
||||||
|
*/
|
||||||
|
bool updateInstancesRange(const InstanceData* instances, uint32_t start, uint32_t count);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 添加单个实例
|
* @brief 添加单个实例
|
||||||
* @param instance 实例数据
|
* @param instance 实例数据
|
||||||
|
|
@ -103,6 +166,24 @@ public:
|
||||||
*/
|
*/
|
||||||
uint32_t addInstance(const InstanceData& instance);
|
uint32_t addInstance(const InstanceData& instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 标记实例范围为脏(下次updateGPU时更新)
|
||||||
|
* @param start 起始实例索引
|
||||||
|
* @param count 实例数量
|
||||||
|
*/
|
||||||
|
void markDirty(uint32_t start, uint32_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 标记整个缓冲区为脏
|
||||||
|
*/
|
||||||
|
void markAllDirty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将脏数据上传到GPU
|
||||||
|
* @return 上传是否成功
|
||||||
|
*/
|
||||||
|
bool updateGPU();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 清除所有实例
|
* @brief 清除所有实例
|
||||||
*/
|
*/
|
||||||
|
|
@ -138,11 +219,43 @@ public:
|
||||||
*/
|
*/
|
||||||
bool isValid() const { return bufferHandle_.isValid(); }
|
bool isValid() const { return bufferHandle_.isValid(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取脏区域数量
|
||||||
|
* @return 脏区域数量
|
||||||
|
*/
|
||||||
|
uint32_t getDirtyRangeCount() const { return static_cast<uint32_t>(dirtyRanges_.size()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有脏数据
|
||||||
|
* @return 是否有脏数据
|
||||||
|
*/
|
||||||
|
bool hasDirtyData() const { return !dirtyRanges_.empty(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BufferHandle bufferHandle_; // RHI 缓冲区句柄
|
BufferHandle bufferHandle_; // RHI 缓冲区句柄
|
||||||
uint32_t maxInstances_ = 0; // 最大实例数量
|
uint32_t maxInstances_ = 0; // 最大实例数量
|
||||||
uint32_t instanceCount_ = 0; // 当前实例数量
|
uint32_t instanceCount_ = 0; // 当前实例数量
|
||||||
std::vector<InstanceData> cpuBuffer_; // CPU 端缓冲区
|
std::vector<InstanceData> cpuBuffer_; // CPU 端缓冲区
|
||||||
|
std::vector<DirtyRange> dirtyRanges_; // 脏区域列表
|
||||||
|
|
||||||
|
// 最大脏区域数量,超过则合并为全量更新
|
||||||
|
static constexpr uint32_t MAX_DIRTY_RANGES = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加脏区域,自动合并重叠区域
|
||||||
|
* @param range 脏区域
|
||||||
|
*/
|
||||||
|
void addDirtyRange(const DirtyRange& range);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 合并所有脏区域为一个
|
||||||
|
*/
|
||||||
|
void mergeAllDirtyRanges();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空脏区域列表
|
||||||
|
*/
|
||||||
|
void clearDirtyRanges();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,9 @@ class Texture;
|
||||||
* @brief 渲染命令类型
|
* @brief 渲染命令类型
|
||||||
*/
|
*/
|
||||||
enum class RenderCommandType : uint8_t {
|
enum class RenderCommandType : uint8_t {
|
||||||
DrawMesh, // 绘制网格
|
DrawMesh, // 绘制网格
|
||||||
DrawMeshInstanced, // 实例化绘制
|
SetViewport, // 设置视口
|
||||||
SetViewport, // 设置视口
|
Clear // 清除缓冲区
|
||||||
Clear // 清除缓冲区
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,15 +45,6 @@ struct RenderCommand {
|
||||||
Color color; // 顶点颜色
|
Color color; // 顶点颜色
|
||||||
};
|
};
|
||||||
|
|
||||||
// 实例化绘制命令数据
|
|
||||||
struct DrawInstancedData {
|
|
||||||
Handle<Mesh> mesh; // 网格句柄
|
|
||||||
Handle<Material> material; // 材质句柄
|
|
||||||
void* instanceBuffer; // 实例数据缓冲区指针 (InstanceBuffer*)
|
|
||||||
uint32_t instanceCount; // 实例数量
|
|
||||||
uint32_t instanceOffset; // 实例数据偏移
|
|
||||||
};
|
|
||||||
|
|
||||||
// 设置视口命令数据
|
// 设置视口命令数据
|
||||||
struct ViewportData {
|
struct ViewportData {
|
||||||
int32_t x, y; // 视口位置
|
int32_t x, y; // 视口位置
|
||||||
|
|
@ -69,7 +59,6 @@ struct RenderCommand {
|
||||||
|
|
||||||
union {
|
union {
|
||||||
DrawMeshData drawMesh;
|
DrawMeshData drawMesh;
|
||||||
DrawInstancedData drawInstanced;
|
|
||||||
ViewportData viewport;
|
ViewportData viewport;
|
||||||
ClearData clear;
|
ClearData clear;
|
||||||
};
|
};
|
||||||
|
|
@ -123,12 +112,8 @@ constexpr uint32_t CLEAR_ALL_FLAG =
|
||||||
// 最大渲染命令数
|
// 最大渲染命令数
|
||||||
constexpr uint32_t MAX_RENDER_COMMANDS = 10000;
|
constexpr uint32_t MAX_RENDER_COMMANDS = 10000;
|
||||||
|
|
||||||
// 最大实例数
|
|
||||||
constexpr uint32_t MAX_INSTANCES = 256;
|
|
||||||
|
|
||||||
// UBO 绑定槽位(已移至 RHI)
|
// UBO 绑定槽位(已移至 RHI)
|
||||||
constexpr uint32_t GLOBAL_UBO_BINDING = 0; // 全局 UBO
|
constexpr uint32_t GLOBAL_UBO_BINDING = 0; // 全局 UBO
|
||||||
constexpr uint32_t MATERIAL_UBO_BINDING = 1; // 材质 UBO
|
constexpr uint32_t MATERIAL_UBO_BINDING = 1; // 材质 UBO
|
||||||
constexpr uint32_t INSTANCE_UBO_BINDING = 2; // 实例 UBO
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,6 @@ public:
|
||||||
*/
|
*/
|
||||||
bool loadFromSource(const std::string &vsSource, const std::string &fsSource);
|
bool loadFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码加载实例化着色器(支持实例属性)
|
|
||||||
* @param vsSource 顶点着色器源码
|
|
||||||
* @param fsSource 片段着色器源码
|
|
||||||
* @return 加载是否成功
|
|
||||||
*/
|
|
||||||
bool loadInstancedFromSource(const std::string &vsSource, const std::string &fsSource);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取 RHI 着色器句柄
|
* @brief 获取 RHI 着色器句柄
|
||||||
* @return RHI 着色器句柄
|
* @return RHI 着色器句柄
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
#version 320 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
// 全局 UBO (binding = 0) - 每帧更新一次
|
|
||||||
layout(std140, binding = 0) uniform GlobalUBO {
|
|
||||||
mat4 uViewProjection;
|
|
||||||
vec4 uCameraPosition;
|
|
||||||
float uTime;
|
|
||||||
float uDeltaTime;
|
|
||||||
vec2 uScreenSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 顶点属性
|
|
||||||
layout(location = 0) in vec2 aPosition; // 基础顶点位置
|
|
||||||
layout(location = 1) in vec2 aTexCoord; // 基础 UV
|
|
||||||
layout(location = 2) in vec4 aColor; // 基础颜色
|
|
||||||
|
|
||||||
// 实例属性 (每个实例)
|
|
||||||
layout(location = 3) in vec2 aInstancePos; // 实例位置偏移
|
|
||||||
layout(location = 4) in float aInstanceRot; // 实例旋转
|
|
||||||
layout(location = 5) in vec2 aInstanceScale; // 实例缩放
|
|
||||||
layout(location = 6) in vec4 aInstanceColor; // 实例颜色
|
|
||||||
layout(location = 7) in vec4 aInstanceUV; // 实例 UV 区域
|
|
||||||
|
|
||||||
// 输出到片段着色器
|
|
||||||
out vec2 vTexCoord;
|
|
||||||
out vec4 vColor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 顶点着色器入口(实例化版本)
|
|
||||||
*
|
|
||||||
* 计算顶点在裁剪空间中的位置,应用实例的变换
|
|
||||||
*/
|
|
||||||
void main() {
|
|
||||||
// 构建旋转矩阵
|
|
||||||
float cosRot = cos(aInstanceRot);
|
|
||||||
float sinRot = sin(aInstanceRot);
|
|
||||||
mat2 rotation = mat2(
|
|
||||||
cosRot, -sinRot,
|
|
||||||
sinRot, cosRot
|
|
||||||
);
|
|
||||||
|
|
||||||
// 应用缩放和旋转
|
|
||||||
vec2 scaledPos = aPosition * aInstanceScale;
|
|
||||||
vec2 rotatedPos = rotation * scaledPos;
|
|
||||||
|
|
||||||
// 应用实例位置偏移
|
|
||||||
vec2 worldPos = rotatedPos + aInstancePos;
|
|
||||||
|
|
||||||
// 计算裁剪空间位置
|
|
||||||
gl_Position = uViewProjection * vec4(worldPos, 0.0, 1.0);
|
|
||||||
|
|
||||||
// 计算 UV 坐标(应用实例 UV 区域)
|
|
||||||
vTexCoord = aTexCoord * aInstanceUV.zw + aInstanceUV.xy;
|
|
||||||
|
|
||||||
// 混合顶点颜色和实例颜色
|
|
||||||
vColor = aColor * aInstanceColor;
|
|
||||||
}
|
|
||||||
|
|
@ -101,13 +101,6 @@ void AssetsModule::onGLContextReady() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建实例化渲染资源
|
|
||||||
if (!createInstancedResources()) {
|
|
||||||
E2D_LOG_WARN("Failed to create instanced resources, instanced rendering "
|
|
||||||
"will not be available");
|
|
||||||
// 不返回错误,实例化渲染是可选功能
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultResourcesCreated_ = true;
|
defaultResourcesCreated_ = true;
|
||||||
E2D_LOG_INFO("Default resources created successfully");
|
E2D_LOG_INFO("Default resources created successfully");
|
||||||
}
|
}
|
||||||
|
|
@ -558,82 +551,6 @@ Material *AssetsModule::getDefaultMaterialPtr() {
|
||||||
|
|
||||||
Mesh *AssetsModule::getDefaultQuadPtr() { return meshes_.get(defaultQuad_); }
|
Mesh *AssetsModule::getDefaultQuadPtr() { return meshes_.get(defaultQuad_); }
|
||||||
|
|
||||||
Handle<Shader> AssetsModule::getInstancedShader() { return instancedShader_; }
|
|
||||||
|
|
||||||
Handle<Material> AssetsModule::getInstancedMaterial() {
|
|
||||||
return instancedMaterial_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Shader *AssetsModule::getInstancedShaderPtr() {
|
|
||||||
return shaders_.get(instancedShader_);
|
|
||||||
}
|
|
||||||
|
|
||||||
Material *AssetsModule::getInstancedMaterialPtr() {
|
|
||||||
return materials_.get(instancedMaterial_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AssetsModule::createInstancedResources() {
|
|
||||||
// 加载实例化着色器
|
|
||||||
// 使用 FileModule 的 assetPath 来获取正确的资源路径(支持 RomFS)
|
|
||||||
FileModule *fileModule = getFileModule();
|
|
||||||
std::string vertPath =
|
|
||||||
fileModule ? fileModule->assetPath("shader/sprite_instanced.vert")
|
|
||||||
: "shader/sprite_instanced.vert";
|
|
||||||
std::string fragPath =
|
|
||||||
fileModule ? fileModule->assetPath("shader/sprite_instanced.frag")
|
|
||||||
: "shader/sprite_instanced.frag";
|
|
||||||
|
|
||||||
if (!fileExists(vertPath) || !fileExists(fragPath)) {
|
|
||||||
E2D_LOG_ERROR("Instanced shader files not found: {}, {}", vertPath,
|
|
||||||
fragPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取着色器文件内容
|
|
||||||
std::string vsSource = readFileToString(vertPath);
|
|
||||||
std::string fsSource = readFileToString(fragPath);
|
|
||||||
if (vsSource.empty() || fsSource.empty()) {
|
|
||||||
E2D_LOG_ERROR("Failed to read instanced shader files: {}, {}", vertPath,
|
|
||||||
fragPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从源码加载实例化着色器
|
|
||||||
Ptr<Shader> shader = makePtr<Shader>();
|
|
||||||
if (!shader->loadInstancedFromSource(vsSource, fsSource)) {
|
|
||||||
E2D_LOG_ERROR("Failed to compile instanced shader: {}, {}", vertPath,
|
|
||||||
fragPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
instancedShader_ = shaders_.insert(shader);
|
|
||||||
E2D_LOG_DEBUG("Loaded instanced shader from files: {}, {}", vertPath,
|
|
||||||
fragPath);
|
|
||||||
|
|
||||||
// 创建实例化材质布局
|
|
||||||
Ptr<MaterialLayout> layout = makePtr<MaterialLayout>();
|
|
||||||
layout->addParam("uColor", MaterialParamType::Color);
|
|
||||||
layout->addParam("uTintColor", MaterialParamType::Color);
|
|
||||||
layout->addParam("uOpacity", MaterialParamType::Float);
|
|
||||||
layout->finalize();
|
|
||||||
|
|
||||||
Ptr<Material> material = makePtr<Material>();
|
|
||||||
material->setShader(getPtr(instancedShader_));
|
|
||||||
material->setLayout(layout);
|
|
||||||
|
|
||||||
// 设置默认材质参数
|
|
||||||
material->setColor("uColor", Color::White);
|
|
||||||
material->setColor("uTintColor", Color::White);
|
|
||||||
material->setFloat("uOpacity", 1.0f);
|
|
||||||
|
|
||||||
// 添加默认纹理到材质
|
|
||||||
material->setTexture("uTexture", getPtr(defaultTexture_), 0);
|
|
||||||
instancedMaterial_ = materials_.insert(material);
|
|
||||||
E2D_LOG_DEBUG("Created instanced material with default texture and layout");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 热重载
|
// 热重载
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <renderer/command_queue.h>
|
#include <renderer/command_queue.h>
|
||||||
#include <renderer/instance_buffer.h>
|
|
||||||
#include <renderer/material.h>
|
#include <renderer/material.h>
|
||||||
#include <renderer/mesh.h>
|
#include <renderer/mesh.h>
|
||||||
#include <renderer/rhi_module.h>
|
#include <renderer/rhi_module.h>
|
||||||
|
|
@ -406,59 +405,6 @@ void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
sorter_.addCommand(cmd);
|
sorter_.addCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandQueue::submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
|
||||||
BufferHandle instanceBuffer, uint32_t instanceCount) {
|
|
||||||
if (!material || !mesh || !material->getShader() || instanceCount == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawCommand cmd;
|
|
||||||
|
|
||||||
// 构建排序键
|
|
||||||
uint32_t materialId = getMaterialId(material.get());
|
|
||||||
cmd.key = DrawKey::make(materialId, 0, 0);
|
|
||||||
|
|
||||||
// 设置管线
|
|
||||||
cmd.pipeline = material->getPipeline();
|
|
||||||
|
|
||||||
// 设置网格数据
|
|
||||||
cmd.vertexBuffer = mesh->getVertexBuffer();
|
|
||||||
cmd.indexBuffer = mesh->getIndexBuffer();
|
|
||||||
cmd.vertexCount = mesh->getVertexCount();
|
|
||||||
cmd.indexCount = mesh->getIndexCount();
|
|
||||||
cmd.instanceCount = instanceCount;
|
|
||||||
|
|
||||||
// 设置实例缓冲区
|
|
||||||
cmd.instanceBuffer = instanceBuffer;
|
|
||||||
cmd.instanceBufferStride = sizeof(InstanceData); // 64 bytes
|
|
||||||
|
|
||||||
// 设置纹理
|
|
||||||
const auto &textures = material->getTextures();
|
|
||||||
cmd.textureCount =
|
|
||||||
static_cast<uint32_t>(std::min(textures.size(), size_t(8)));
|
|
||||||
for (uint32_t i = 0; i < cmd.textureCount; ++i) {
|
|
||||||
if (textures[i].texture) {
|
|
||||||
cmd.textures[i] = textures[i].texture->getHandle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分配材质 UBO 空间(使用 CPU 缓冲区)
|
|
||||||
uint32_t materialDataSize = material->getDataSize();
|
|
||||||
if (materialDataSize > 0) {
|
|
||||||
auto [ubo, offset] = allocateMaterialUBO(materialDataSize);
|
|
||||||
(void)ubo; // ubo 为 nullptr,使用 CPU 缓冲区
|
|
||||||
|
|
||||||
// 复制材质数据到 CPU 缓冲区
|
|
||||||
if (offset + materialDataSize <= materialUBOData_.size()) {
|
|
||||||
std::memcpy(materialUBOData_.data() + offset, material->getData(), materialDataSize);
|
|
||||||
cmd.materialUBOOffset = offset;
|
|
||||||
cmd.materialUBOSize = materialDataSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sorter_.addCommand(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandQueue::submitClear(const Color &color, uint32_t flags) {
|
void CommandQueue::submitClear(const Color &color, uint32_t flags) {
|
||||||
// 清除命令直接执行,不加入排序队列
|
// 清除命令直接执行,不加入排序队列
|
||||||
if (!commandList_) {
|
if (!commandList_) {
|
||||||
|
|
@ -575,12 +521,6 @@ void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch,
|
||||||
E2D_LOG_WARN("Draw command has no valid vertex buffer!");
|
E2D_LOG_WARN("Draw command has no valid vertex buffer!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绑定实例缓冲区(实例化渲染)
|
|
||||||
if (cmd.isInstanced() && cmd.instanceBuffer.isValid()) {
|
|
||||||
commandList_->setVertexBuffer(1, cmd.instanceBuffer.get(), 0, cmd.instanceBufferStride);
|
|
||||||
stats_.bufferBinds++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定索引缓冲区(如果有)
|
// 绑定索引缓冲区(如果有)
|
||||||
if (cmd.isIndexed() && cmd.indexBuffer.isValid()) {
|
if (cmd.isIndexed() && cmd.indexBuffer.isValid()) {
|
||||||
commandList_->setIndexBuffer(cmd.indexBuffer.get(), IndexType::UInt16, 0);
|
commandList_->setIndexBuffer(cmd.indexBuffer.get(), IndexType::UInt16, 0);
|
||||||
|
|
@ -593,34 +533,20 @@ void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch,
|
||||||
stats_.bufferBinds++;
|
stats_.bufferBinds++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置模型矩阵(仅在非实例化渲染时使用)
|
// 设置模型矩阵
|
||||||
if (!cmd.isInstanced()) {
|
commandList_->setUniform("uModelMatrix", cmd.modelMatrix);
|
||||||
commandList_->setUniform("uModelMatrix", cmd.modelMatrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制
|
// 绘制
|
||||||
if (cmd.isInstanced()) {
|
if (cmd.isIndexed()) {
|
||||||
if (cmd.isIndexed()) {
|
commandList_->drawIndexed(cmd.indexCount, 0, 0, 1, 0);
|
||||||
commandList_->drawIndexed(cmd.indexCount, 0, 0, cmd.instanceCount, 0);
|
stats_.drawCalls++;
|
||||||
stats_.drawCalls++;
|
stats_.triangles += cmd.indexCount / 3;
|
||||||
stats_.triangles += (cmd.indexCount / 3) * cmd.instanceCount;
|
|
||||||
} else {
|
|
||||||
commandList_->draw(cmd.vertexCount, 0, cmd.instanceCount, 0);
|
|
||||||
stats_.drawCalls++;
|
|
||||||
stats_.triangles += (cmd.vertexCount / 3) * cmd.instanceCount;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (cmd.isIndexed()) {
|
commandList_->draw(cmd.vertexCount, 0, 1, 0);
|
||||||
commandList_->drawIndexed(cmd.indexCount, 0, 0, 1, 0);
|
stats_.drawCalls++;
|
||||||
stats_.drawCalls++;
|
stats_.triangles += cmd.vertexCount / 3;
|
||||||
stats_.triangles += cmd.indexCount / 3;
|
|
||||||
} else {
|
|
||||||
commandList_->draw(cmd.vertexCount, 0, 1, 0);
|
|
||||||
stats_.drawCalls++;
|
|
||||||
stats_.triangles += cmd.vertexCount / 3;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
stats_.vertices += cmd.isInstanced() ? (cmd.vertexCount * cmd.instanceCount) : cmd.vertexCount;
|
stats_.vertices += cmd.vertexCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <renderer/rhi/rhi_device.h>
|
#include <renderer/rhi/rhi_device.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -20,7 +21,8 @@ InstanceBuffer::InstanceBuffer(InstanceBuffer&& other) noexcept
|
||||||
: bufferHandle_(std::move(other.bufferHandle_))
|
: bufferHandle_(std::move(other.bufferHandle_))
|
||||||
, maxInstances_(other.maxInstances_)
|
, maxInstances_(other.maxInstances_)
|
||||||
, instanceCount_(other.instanceCount_)
|
, instanceCount_(other.instanceCount_)
|
||||||
, cpuBuffer_(std::move(other.cpuBuffer_)) {
|
, cpuBuffer_(std::move(other.cpuBuffer_))
|
||||||
|
, dirtyRanges_(std::move(other.dirtyRanges_)) {
|
||||||
other.maxInstances_ = 0;
|
other.maxInstances_ = 0;
|
||||||
other.instanceCount_ = 0;
|
other.instanceCount_ = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -33,6 +35,7 @@ InstanceBuffer& InstanceBuffer::operator=(InstanceBuffer&& other) noexcept {
|
||||||
maxInstances_ = other.maxInstances_;
|
maxInstances_ = other.maxInstances_;
|
||||||
instanceCount_ = other.instanceCount_;
|
instanceCount_ = other.instanceCount_;
|
||||||
cpuBuffer_ = std::move(other.cpuBuffer_);
|
cpuBuffer_ = std::move(other.cpuBuffer_);
|
||||||
|
dirtyRanges_ = std::move(other.dirtyRanges_);
|
||||||
|
|
||||||
other.maxInstances_ = 0;
|
other.maxInstances_ = 0;
|
||||||
other.instanceCount_ = 0;
|
other.instanceCount_ = 0;
|
||||||
|
|
@ -72,6 +75,8 @@ bool InstanceBuffer::initialize(uint32_t maxInstances) {
|
||||||
maxInstances_ = maxInstances;
|
maxInstances_ = maxInstances;
|
||||||
instanceCount_ = 0;
|
instanceCount_ = 0;
|
||||||
cpuBuffer_.reserve(maxInstances);
|
cpuBuffer_.reserve(maxInstances);
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
dirtyRanges_.reserve(MAX_DIRTY_RANGES);
|
||||||
|
|
||||||
E2D_LOG_DEBUG("InstanceBuffer initialized with capacity for {} instances", maxInstances);
|
E2D_LOG_DEBUG("InstanceBuffer initialized with capacity for {} instances", maxInstances);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -83,6 +88,8 @@ void InstanceBuffer::shutdown() {
|
||||||
instanceCount_ = 0;
|
instanceCount_ = 0;
|
||||||
cpuBuffer_.clear();
|
cpuBuffer_.clear();
|
||||||
cpuBuffer_.shrink_to_fit();
|
cpuBuffer_.shrink_to_fit();
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
dirtyRanges_.shrink_to_fit();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstanceBuffer::updateInstances(const InstanceData* instances, uint32_t count) {
|
bool InstanceBuffer::updateInstances(const InstanceData* instances, uint32_t count) {
|
||||||
|
|
@ -96,12 +103,57 @@ bool InstanceBuffer::updateInstances(const InstanceData* instances, uint32_t cou
|
||||||
count = maxInstances_;
|
count = maxInstances_;
|
||||||
}
|
}
|
||||||
|
|
||||||
RHIBuffer* rhiBuffer = bufferHandle_.get();
|
// 复制到CPU缓冲区
|
||||||
if (rhiBuffer) {
|
if (count > 0) {
|
||||||
rhiBuffer->update(instances, count * sizeof(InstanceData), 0);
|
if (cpuBuffer_.size() < count) {
|
||||||
|
cpuBuffer_.resize(count);
|
||||||
|
}
|
||||||
|
std::memcpy(cpuBuffer_.data(), instances, count * sizeof(InstanceData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 标记整个范围为脏
|
||||||
|
markAllDirty();
|
||||||
|
|
||||||
|
// 立即上传到GPU
|
||||||
|
bool result = updateGPU();
|
||||||
|
|
||||||
instanceCount_ = count;
|
instanceCount_ = count;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceBuffer::updateInstancesRange(const InstanceData* instances, uint32_t start, uint32_t count) {
|
||||||
|
if (!bufferHandle_.isValid() || !instances || count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start >= maxInstances_) {
|
||||||
|
E2D_LOG_WARN("InstanceBuffer::updateInstancesRange: start {} exceeds maxInstances {}",
|
||||||
|
start, maxInstances_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start + count > maxInstances_) {
|
||||||
|
E2D_LOG_WARN("InstanceBuffer::updateInstancesRange: range {}-{} exceeds maxInstances {}",
|
||||||
|
start, start + count, maxInstances_);
|
||||||
|
count = maxInstances_ - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保CPU缓冲区足够大
|
||||||
|
if (cpuBuffer_.size() < start + count) {
|
||||||
|
cpuBuffer_.resize(start + count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到CPU缓冲区
|
||||||
|
std::memcpy(cpuBuffer_.data() + start, instances, count * sizeof(InstanceData));
|
||||||
|
|
||||||
|
// 标记为脏
|
||||||
|
markDirty(start, count);
|
||||||
|
|
||||||
|
// 更新实例数量
|
||||||
|
if (start + count > instanceCount_) {
|
||||||
|
instanceCount_ = start + count;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,18 +172,108 @@ uint32_t InstanceBuffer::addInstance(const InstanceData& instance) {
|
||||||
cpuBuffer_[index] = instance;
|
cpuBuffer_[index] = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 GPU 缓冲区
|
// 标记为脏(单个实例)
|
||||||
RHIBuffer* rhiBuffer = bufferHandle_.get();
|
markDirty(index, 1);
|
||||||
if (rhiBuffer) {
|
|
||||||
rhiBuffer->update(&instance, sizeof(InstanceData), index * sizeof(InstanceData));
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::markDirty(uint32_t start, uint32_t count) {
|
||||||
|
if (count == 0 || start >= maxInstances_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制范围
|
||||||
|
if (start + count > maxInstances_) {
|
||||||
|
count = maxInstances_ - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDirtyRange(DirtyRange(start, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::markAllDirty() {
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
if (instanceCount_ > 0) {
|
||||||
|
dirtyRanges_.push_back(DirtyRange(0, instanceCount_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceBuffer::updateGPU() {
|
||||||
|
if (!bufferHandle_.isValid() || dirtyRanges_.empty()) {
|
||||||
|
return true; // 没有脏数据,无需更新
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIBuffer* rhiBuffer = bufferHandle_.get();
|
||||||
|
if (!rhiBuffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果脏区域太多,合并为全量更新
|
||||||
|
if (dirtyRanges_.size() > MAX_DIRTY_RANGES) {
|
||||||
|
mergeAllDirtyRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传所有脏区域
|
||||||
|
for (const auto& range : dirtyRanges_) {
|
||||||
|
if (!range.isValid()) continue;
|
||||||
|
|
||||||
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(cpuBuffer_.data());
|
||||||
|
uint32_t offset = range.start * sizeof(InstanceData);
|
||||||
|
uint32_t size = range.count * sizeof(InstanceData);
|
||||||
|
|
||||||
|
rhiBuffer->update(data + offset, size, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空脏区域列表
|
||||||
|
clearDirtyRanges();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void InstanceBuffer::clear() {
|
void InstanceBuffer::clear() {
|
||||||
instanceCount_ = 0;
|
instanceCount_ = 0;
|
||||||
cpuBuffer_.clear();
|
cpuBuffer_.clear();
|
||||||
|
clearDirtyRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::addDirtyRange(const DirtyRange& range) {
|
||||||
|
if (!range.isValid()) return;
|
||||||
|
|
||||||
|
// 检查是否可以与现有区域合并
|
||||||
|
for (auto& existing : dirtyRanges_) {
|
||||||
|
if (existing.canMerge(range)) {
|
||||||
|
existing.merge(range);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新区域
|
||||||
|
dirtyRanges_.push_back(range);
|
||||||
|
|
||||||
|
// 如果脏区域太多,合并所有区域
|
||||||
|
if (dirtyRanges_.size() > MAX_DIRTY_RANGES) {
|
||||||
|
mergeAllDirtyRanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::mergeAllDirtyRanges() {
|
||||||
|
if (dirtyRanges_.empty()) return;
|
||||||
|
|
||||||
|
// 找到最小起始和最大结束
|
||||||
|
uint32_t minStart = dirtyRanges_[0].start;
|
||||||
|
uint32_t maxEnd = dirtyRanges_[0].end();
|
||||||
|
|
||||||
|
for (const auto& range : dirtyRanges_) {
|
||||||
|
if (range.start < minStart) minStart = range.start;
|
||||||
|
if (range.end() > maxEnd) maxEnd = range.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
dirtyRanges_.push_back(DirtyRange(minStart, maxEnd - minStart));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::clearDirtyRanges() {
|
||||||
|
dirtyRanges_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#include <assets/assets_module.h>
|
#include <assets/assets_module.h>
|
||||||
#include <platform/window_module.h>
|
#include <platform/window_module.h>
|
||||||
#include <renderer/instance_buffer.h>
|
|
||||||
#include <renderer/render_graph.h>
|
#include <renderer/render_graph.h>
|
||||||
#include <renderer/renderer_module.h>
|
#include <renderer/renderer_module.h>
|
||||||
#include <renderer/rhi_module.h>
|
#include <renderer/rhi_module.h>
|
||||||
|
|
@ -202,26 +201,6 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case RenderCommandType::DrawMeshInstanced: {
|
|
||||||
auto *assets = getAssets();
|
|
||||||
if (!assets)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Material *material = assets->get(cmd.drawInstanced.material);
|
|
||||||
Mesh *mesh = assets->get(cmd.drawInstanced.mesh);
|
|
||||||
InstanceBuffer *instanceBuffer =
|
|
||||||
static_cast<InstanceBuffer *>(cmd.drawInstanced.instanceBuffer);
|
|
||||||
|
|
||||||
if (material && mesh && instanceBuffer && instanceBuffer->isValid()) {
|
|
||||||
commandQueue_->submitDrawInstanced(
|
|
||||||
Ptr<Material>(material), Ptr<Mesh>(mesh),
|
|
||||||
BufferHandle(instanceBuffer->getRHIBuffer()),
|
|
||||||
cmd.drawInstanced.instanceCount);
|
|
||||||
stats_.commandsSubmitted++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case RenderCommandType::SetViewport: {
|
case RenderCommandType::SetViewport: {
|
||||||
setViewport(cmd.viewport.x, cmd.viewport.y, cmd.viewport.width,
|
setViewport(cmd.viewport.x, cmd.viewport.y, cmd.viewport.width,
|
||||||
cmd.viewport.height);
|
cmd.viewport.height);
|
||||||
|
|
|
||||||
|
|
@ -25,47 +25,6 @@ bool Shader::loadFromSource(const std::string &vsSource,
|
||||||
return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::loadInstancedFromSource(const std::string &vsSource,
|
|
||||||
const std::string &fsSource) {
|
|
||||||
// 创建实例化渲染顶点布局
|
|
||||||
// 槽位0:顶点属性(位置、UV、颜色)
|
|
||||||
// 槽位1:实例属性(位置、旋转、缩放、颜色、UV区域)
|
|
||||||
VertexLayout vertexLayout;
|
|
||||||
|
|
||||||
// 顶点属性(每个顶点)- 槽位0
|
|
||||||
vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置
|
|
||||||
vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // UV
|
|
||||||
vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色
|
|
||||||
|
|
||||||
// 实例属性(每个实例)- 槽位1
|
|
||||||
// InstanceData 布局:
|
|
||||||
// Vec2 position; // offset 0
|
|
||||||
// float rotation; // offset 8
|
|
||||||
// float padding0; // offset 12
|
|
||||||
// Vec2 scale; // offset 16
|
|
||||||
// float padding1[2]; // offset 24
|
|
||||||
// Color color; // offset 32
|
|
||||||
// float uvX; // offset 48
|
|
||||||
// float uvY; // offset 52
|
|
||||||
// float uvWidth; // offset 56
|
|
||||||
// float uvHeight; // offset 60
|
|
||||||
vertexLayout.addAttribute(
|
|
||||||
VertexAttribute::perInstance(3, VertexFormat::Float2, 0, 1)); // iPosition
|
|
||||||
vertexLayout.addAttribute(
|
|
||||||
VertexAttribute::perInstance(4, VertexFormat::Float1, 8, 1)); // iRotation
|
|
||||||
vertexLayout.addAttribute(
|
|
||||||
VertexAttribute::perInstance(5, VertexFormat::Float2, 16, 1)); // iScale
|
|
||||||
vertexLayout.addAttribute(
|
|
||||||
VertexAttribute::perInstance(6, VertexFormat::Float4, 32, 1)); // iColor
|
|
||||||
vertexLayout.addAttribute(
|
|
||||||
VertexAttribute::perInstance(7, VertexFormat::Float4, 48, 1)); // iUVRect
|
|
||||||
|
|
||||||
// 注意:stride 在实例化渲染中由 buffer 的 stride 参数控制
|
|
||||||
vertexLayout.stride = sizeof(float) * 8; // 顶点缓冲区步长
|
|
||||||
|
|
||||||
return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Shader::loadFromSourceWithLayout(const std::string &vsSource,
|
bool Shader::loadFromSourceWithLayout(const std::string &vsSource,
|
||||||
const std::string &fsSource,
|
const std::string &fsSource,
|
||||||
const VertexLayout &vertexLayout) {
|
const VertexLayout &vertexLayout) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue