Add renderer phase A textured scene path
This commit is contained in:
33
engine/include/XCEngine/Components/MeshFilterComponent.h
Normal file
33
engine/include/XCEngine/Components/MeshFilterComponent.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Components/Component.h>
|
||||||
|
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||||
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
|
||||||
|
class MeshFilterComponent : public Component {
|
||||||
|
public:
|
||||||
|
std::string GetName() const override { return "MeshFilter"; }
|
||||||
|
|
||||||
|
Resources::Mesh* GetMesh() const { return m_mesh.Get(); }
|
||||||
|
const Resources::ResourceHandle<Resources::Mesh>& GetMeshHandle() const { return m_mesh; }
|
||||||
|
const std::string& GetMeshPath() const { return m_meshPath; }
|
||||||
|
|
||||||
|
void SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh);
|
||||||
|
void SetMesh(Resources::Mesh* mesh);
|
||||||
|
void ClearMesh();
|
||||||
|
|
||||||
|
void Serialize(std::ostream& os) const override;
|
||||||
|
void Deserialize(std::istream& is) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Resources::ResourceHandle<Resources::Mesh> m_mesh;
|
||||||
|
std::string m_meshPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Components
|
||||||
|
} // namespace XCEngine
|
||||||
52
engine/include/XCEngine/Components/MeshRendererComponent.h
Normal file
52
engine/include/XCEngine/Components/MeshRendererComponent.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Components/Component.h>
|
||||||
|
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||||
|
#include <XCEngine/Resources/Material/Material.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
|
||||||
|
class MeshRendererComponent : public Component {
|
||||||
|
public:
|
||||||
|
std::string GetName() const override { return "MeshRenderer"; }
|
||||||
|
|
||||||
|
size_t GetMaterialCount() const { return m_materials.size(); }
|
||||||
|
Resources::Material* GetMaterial(size_t index) const;
|
||||||
|
const Resources::ResourceHandle<Resources::Material>& GetMaterialHandle(size_t index) const;
|
||||||
|
const std::vector<std::string>& GetMaterialPaths() const { return m_materialPaths; }
|
||||||
|
|
||||||
|
void SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material);
|
||||||
|
void SetMaterial(size_t index, Resources::Material* material);
|
||||||
|
void SetMaterials(const std::vector<Resources::ResourceHandle<Resources::Material>>& materials);
|
||||||
|
void ClearMaterials();
|
||||||
|
|
||||||
|
bool GetCastShadows() const { return m_castShadows; }
|
||||||
|
void SetCastShadows(bool value) { m_castShadows = value; }
|
||||||
|
|
||||||
|
bool GetReceiveShadows() const { return m_receiveShadows; }
|
||||||
|
void SetReceiveShadows(bool value) { m_receiveShadows = value; }
|
||||||
|
|
||||||
|
uint32_t GetRenderLayer() const { return m_renderLayer; }
|
||||||
|
void SetRenderLayer(uint32_t value) { m_renderLayer = value; }
|
||||||
|
|
||||||
|
void Serialize(std::ostream& os) const override;
|
||||||
|
void Deserialize(std::istream& is) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void EnsureMaterialSlot(size_t index);
|
||||||
|
static std::string MaterialPathFromHandle(const Resources::ResourceHandle<Resources::Material>& material);
|
||||||
|
|
||||||
|
std::vector<Resources::ResourceHandle<Resources::Material>> m_materials;
|
||||||
|
std::vector<std::string> m_materialPaths;
|
||||||
|
bool m_castShadows = true;
|
||||||
|
bool m_receiveShadows = true;
|
||||||
|
uint32_t m_renderLayer = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Components
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/RenderPipeline.h>
|
||||||
|
#include <XCEngine/Rendering/RenderResourceCache.h>
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||||
|
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||||||
|
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||||
|
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||||
|
#include <XCEngine/RHI/RHIResourceView.h>
|
||||||
|
#include <XCEngine/RHI/RHISampler.h>
|
||||||
|
#include <XCEngine/RHI/RHITexture.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Resources {
|
||||||
|
class Material;
|
||||||
|
class Texture;
|
||||||
|
} // namespace Resources
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
class RenderSurface;
|
||||||
|
|
||||||
|
namespace Pipelines {
|
||||||
|
|
||||||
|
class BuiltinForwardPipeline : public RenderPipeline {
|
||||||
|
public:
|
||||||
|
BuiltinForwardPipeline() = default;
|
||||||
|
~BuiltinForwardPipeline() override;
|
||||||
|
|
||||||
|
bool Initialize(const RenderContext& context) override;
|
||||||
|
void Shutdown() override;
|
||||||
|
bool Render(
|
||||||
|
const RenderContext& context,
|
||||||
|
const RenderSurface& surface,
|
||||||
|
const RenderSceneData& sceneData) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PerObjectConstants {
|
||||||
|
Math::Matrix4x4 projection = Math::Matrix4x4::Identity();
|
||||||
|
Math::Matrix4x4 view = Math::Matrix4x4::Identity();
|
||||||
|
Math::Matrix4x4 model = Math::Matrix4x4::Identity();
|
||||||
|
};
|
||||||
|
|
||||||
|
bool EnsureInitialized(const RenderContext& context);
|
||||||
|
bool CreatePipelineResources(const RenderContext& context);
|
||||||
|
void DestroyPipelineResources();
|
||||||
|
|
||||||
|
const Resources::Material* ResolveMaterial(const VisibleRenderObject& visibleObject, uint32_t materialIndex) const;
|
||||||
|
const Resources::Texture* ResolveTexture(const Resources::Material* material) const;
|
||||||
|
RHI::RHIResourceView* ResolveTextureView(const VisibleRenderObject& visibleObject, uint32_t materialIndex);
|
||||||
|
bool DrawVisibleObject(
|
||||||
|
const RenderContext& context,
|
||||||
|
const RenderSceneData& sceneData,
|
||||||
|
const VisibleRenderObject& visibleObject);
|
||||||
|
|
||||||
|
RHI::RHIDevice* m_device = nullptr;
|
||||||
|
RHI::RHIType m_backendType = RHI::RHIType::D3D12;
|
||||||
|
bool m_initialized = false;
|
||||||
|
|
||||||
|
RenderResourceCache m_resourceCache;
|
||||||
|
|
||||||
|
RHI::RHIDescriptorPool* m_constantPool = nullptr;
|
||||||
|
RHI::RHIDescriptorSet* m_constantSet = nullptr;
|
||||||
|
RHI::RHIDescriptorPool* m_texturePool = nullptr;
|
||||||
|
RHI::RHIDescriptorSet* m_textureSet = nullptr;
|
||||||
|
RHI::RHIDescriptorPool* m_samplerPool = nullptr;
|
||||||
|
RHI::RHIDescriptorSet* m_samplerSet = nullptr;
|
||||||
|
RHI::RHIPipelineLayout* m_pipelineLayout = nullptr;
|
||||||
|
RHI::RHIPipelineState* m_pipelineState = nullptr;
|
||||||
|
RHI::RHISampler* m_sampler = nullptr;
|
||||||
|
RHI::RHITexture* m_fallbackTexture = nullptr;
|
||||||
|
RHI::RHIResourceView* m_fallbackTextureView = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Pipelines
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
23
engine/include/XCEngine/Rendering/RenderCameraData.h
Normal file
23
engine/include/XCEngine/Rendering/RenderCameraData.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Math/Color.h>
|
||||||
|
#include <XCEngine/Core/Math/Matrix4.h>
|
||||||
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct RenderCameraData {
|
||||||
|
Math::Matrix4x4 view = Math::Matrix4x4::Identity();
|
||||||
|
Math::Matrix4x4 projection = Math::Matrix4x4::Identity();
|
||||||
|
Math::Matrix4x4 viewProjection = Math::Matrix4x4::Identity();
|
||||||
|
Math::Vector3 worldPosition = Math::Vector3::Zero();
|
||||||
|
Math::Color clearColor = Math::Color::Black();
|
||||||
|
uint32_t viewportWidth = 0;
|
||||||
|
uint32_t viewportHeight = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
26
engine/include/XCEngine/Rendering/RenderContext.h
Normal file
26
engine/include/XCEngine/Rendering/RenderContext.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/RHIEnums.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace RHI {
|
||||||
|
class RHICommandList;
|
||||||
|
class RHICommandQueue;
|
||||||
|
class RHIDevice;
|
||||||
|
} // namespace RHI
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct RenderContext {
|
||||||
|
RHI::RHIDevice* device = nullptr;
|
||||||
|
RHI::RHICommandList* commandList = nullptr;
|
||||||
|
RHI::RHICommandQueue* commandQueue = nullptr;
|
||||||
|
RHI::RHIType backendType = RHI::RHIType::D3D12;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return device != nullptr && commandList != nullptr && commandQueue != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
24
engine/include/XCEngine/Rendering/RenderPipeline.h
Normal file
24
engine/include/XCEngine/Rendering/RenderPipeline.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
|
#include <XCEngine/Rendering/RenderSceneExtractor.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class RenderSurface;
|
||||||
|
|
||||||
|
class RenderPipeline {
|
||||||
|
public:
|
||||||
|
virtual ~RenderPipeline() = default;
|
||||||
|
|
||||||
|
virtual bool Initialize(const RenderContext& context) = 0;
|
||||||
|
virtual void Shutdown() = 0;
|
||||||
|
virtual bool Render(
|
||||||
|
const RenderContext& context,
|
||||||
|
const RenderSurface& surface,
|
||||||
|
const RenderSceneData& sceneData) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
18
engine/include/XCEngine/Rendering/RenderPipelineAsset.h
Normal file
18
engine/include/XCEngine/Rendering/RenderPipelineAsset.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class RenderPipeline;
|
||||||
|
|
||||||
|
class RenderPipelineAsset {
|
||||||
|
public:
|
||||||
|
virtual ~RenderPipelineAsset() = default;
|
||||||
|
|
||||||
|
virtual std::unique_ptr<RenderPipeline> CreatePipeline() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
51
engine/include/XCEngine/Rendering/RenderResourceCache.h
Normal file
51
engine/include/XCEngine/Rendering/RenderResourceCache.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/RHIBuffer.h>
|
||||||
|
#include <XCEngine/RHI/RHIDevice.h>
|
||||||
|
#include <XCEngine/RHI/RHIResourceView.h>
|
||||||
|
#include <XCEngine/RHI/RHITexture.h>
|
||||||
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||||
|
#include <XCEngine/Resources/Texture/Texture.h>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class RenderResourceCache {
|
||||||
|
public:
|
||||||
|
struct CachedMesh {
|
||||||
|
RHI::RHIBuffer* vertexBuffer = nullptr;
|
||||||
|
RHI::RHIResourceView* vertexBufferView = nullptr;
|
||||||
|
RHI::RHIBuffer* indexBuffer = nullptr;
|
||||||
|
RHI::RHIResourceView* indexBufferView = nullptr;
|
||||||
|
uint32_t vertexCount = 0;
|
||||||
|
uint32_t indexCount = 0;
|
||||||
|
uint32_t vertexStride = 0;
|
||||||
|
bool uses32BitIndices = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CachedTexture {
|
||||||
|
RHI::RHITexture* texture = nullptr;
|
||||||
|
RHI::RHIResourceView* shaderResourceView = nullptr;
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
~RenderResourceCache();
|
||||||
|
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
const CachedMesh* GetOrCreateMesh(RHI::RHIDevice* device, const Resources::Mesh* mesh);
|
||||||
|
const CachedTexture* GetOrCreateTexture(RHI::RHIDevice* device, const Resources::Texture* texture);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool UploadMesh(RHI::RHIDevice* device, const Resources::Mesh* mesh, CachedMesh& cachedMesh);
|
||||||
|
bool UploadTexture(RHI::RHIDevice* device, const Resources::Texture* texture, CachedTexture& cachedTexture);
|
||||||
|
|
||||||
|
std::unordered_map<const Resources::Mesh*, CachedMesh> m_meshCache;
|
||||||
|
std::unordered_map<const Resources::Texture*, CachedTexture> m_textureCache;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
48
engine/include/XCEngine/Rendering/RenderSceneExtractor.h
Normal file
48
engine/include/XCEngine/Rendering/RenderSceneExtractor.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/RenderCameraData.h>
|
||||||
|
#include <XCEngine/Rendering/VisibleRenderObject.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
class CameraComponent;
|
||||||
|
class GameObject;
|
||||||
|
class Scene;
|
||||||
|
} // namespace Components
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct RenderSceneData {
|
||||||
|
Components::CameraComponent* camera = nullptr;
|
||||||
|
RenderCameraData cameraData;
|
||||||
|
std::vector<VisibleRenderObject> visibleObjects;
|
||||||
|
|
||||||
|
bool HasCamera() const { return camera != nullptr; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderSceneExtractor {
|
||||||
|
public:
|
||||||
|
RenderSceneData Extract(
|
||||||
|
const Components::Scene& scene,
|
||||||
|
Components::CameraComponent* overrideCamera,
|
||||||
|
uint32_t viewportWidth,
|
||||||
|
uint32_t viewportHeight) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Components::CameraComponent* SelectCamera(
|
||||||
|
const Components::Scene& scene,
|
||||||
|
Components::CameraComponent* overrideCamera) const;
|
||||||
|
RenderCameraData BuildCameraData(
|
||||||
|
const Components::CameraComponent& camera,
|
||||||
|
uint32_t viewportWidth,
|
||||||
|
uint32_t viewportHeight) const;
|
||||||
|
void ExtractVisibleObjects(
|
||||||
|
Components::GameObject* gameObject,
|
||||||
|
std::vector<VisibleRenderObject>& visibleObjects) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
59
engine/include/XCEngine/Rendering/RenderSurface.h
Normal file
59
engine/include/XCEngine/Rendering/RenderSurface.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Math/Color.h>
|
||||||
|
#include <XCEngine/RHI/RHIEnums.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace RHI {
|
||||||
|
class RHIResourceView;
|
||||||
|
} // namespace RHI
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class RenderSurface {
|
||||||
|
public:
|
||||||
|
RenderSurface() = default;
|
||||||
|
RenderSurface(uint32_t width, uint32_t height);
|
||||||
|
|
||||||
|
uint32_t GetWidth() const { return m_width; }
|
||||||
|
uint32_t GetHeight() const { return m_height; }
|
||||||
|
void SetSize(uint32_t width, uint32_t height);
|
||||||
|
|
||||||
|
void SetColorAttachment(RHI::RHIResourceView* colorAttachment);
|
||||||
|
void SetColorAttachments(const std::vector<RHI::RHIResourceView*>& colorAttachments);
|
||||||
|
const std::vector<RHI::RHIResourceView*>& GetColorAttachments() const { return m_colorAttachments; }
|
||||||
|
|
||||||
|
void SetDepthAttachment(RHI::RHIResourceView* depthAttachment) { m_depthAttachment = depthAttachment; }
|
||||||
|
RHI::RHIResourceView* GetDepthAttachment() const { return m_depthAttachment; }
|
||||||
|
|
||||||
|
void SetClearColorOverride(const Math::Color& clearColor);
|
||||||
|
void ClearClearColorOverride();
|
||||||
|
bool HasClearColorOverride() const { return m_hasClearColorOverride; }
|
||||||
|
const Math::Color& GetClearColorOverride() const { return m_clearColorOverride; }
|
||||||
|
|
||||||
|
void SetAutoTransitionEnabled(bool enabled) { m_autoTransition = enabled; }
|
||||||
|
bool IsAutoTransitionEnabled() const { return m_autoTransition; }
|
||||||
|
|
||||||
|
void SetColorStateBefore(RHI::ResourceStates state) { m_colorStateBefore = state; }
|
||||||
|
RHI::ResourceStates GetColorStateBefore() const { return m_colorStateBefore; }
|
||||||
|
|
||||||
|
void SetColorStateAfter(RHI::ResourceStates state) { m_colorStateAfter = state; }
|
||||||
|
RHI::ResourceStates GetColorStateAfter() const { return m_colorStateAfter; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t m_width = 0;
|
||||||
|
uint32_t m_height = 0;
|
||||||
|
std::vector<RHI::RHIResourceView*> m_colorAttachments;
|
||||||
|
RHI::RHIResourceView* m_depthAttachment = nullptr;
|
||||||
|
bool m_hasClearColorOverride = false;
|
||||||
|
Math::Color m_clearColorOverride = Math::Color::Black();
|
||||||
|
bool m_autoTransition = true;
|
||||||
|
RHI::ResourceStates m_colorStateBefore = RHI::ResourceStates::Present;
|
||||||
|
RHI::ResourceStates m_colorStateAfter = RHI::ResourceStates::Present;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
36
engine/include/XCEngine/Rendering/SceneRenderer.h
Normal file
36
engine/include/XCEngine/Rendering/SceneRenderer.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Rendering/RenderPipeline.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
class CameraComponent;
|
||||||
|
class Scene;
|
||||||
|
} // namespace Components
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
class SceneRenderer {
|
||||||
|
public:
|
||||||
|
SceneRenderer();
|
||||||
|
explicit SceneRenderer(std::unique_ptr<RenderPipeline> pipeline);
|
||||||
|
~SceneRenderer();
|
||||||
|
|
||||||
|
void SetPipeline(std::unique_ptr<RenderPipeline> pipeline);
|
||||||
|
RenderPipeline* GetPipeline() const { return m_pipeline.get(); }
|
||||||
|
|
||||||
|
bool Render(
|
||||||
|
const Components::Scene& scene,
|
||||||
|
Components::CameraComponent* overrideCamera,
|
||||||
|
const RenderContext& context,
|
||||||
|
const RenderSurface& surface);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderSceneExtractor m_sceneExtractor;
|
||||||
|
std::unique_ptr<RenderPipeline> m_pipeline;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
24
engine/include/XCEngine/Rendering/VisibleRenderObject.h
Normal file
24
engine/include/XCEngine/Rendering/VisibleRenderObject.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Math/Matrix4.h>
|
||||||
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
class GameObject;
|
||||||
|
class MeshFilterComponent;
|
||||||
|
class MeshRendererComponent;
|
||||||
|
} // namespace Components
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
struct VisibleRenderObject {
|
||||||
|
Components::GameObject* gameObject = nullptr;
|
||||||
|
Components::MeshFilterComponent* meshFilter = nullptr;
|
||||||
|
Components::MeshRendererComponent* meshRenderer = nullptr;
|
||||||
|
Resources::Mesh* mesh = nullptr;
|
||||||
|
Math::Matrix4x4 localToWorld = Math::Matrix4x4::Identity();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
64
engine/src/Components/MeshFilterComponent.cpp
Normal file
64
engine/src/Components/MeshFilterComponent.cpp
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include "Components/MeshFilterComponent.h"
|
||||||
|
|
||||||
|
#include "Core/Asset/ResourceManager.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string ToStdString(const Containers::String& value) {
|
||||||
|
return std::string(value.CStr());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void MeshFilterComponent::SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh) {
|
||||||
|
m_mesh = mesh;
|
||||||
|
m_meshPath = mesh.Get() != nullptr ? ToStdString(mesh->GetPath()) : std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshFilterComponent::SetMesh(Resources::Mesh* mesh) {
|
||||||
|
SetMesh(Resources::ResourceHandle<Resources::Mesh>(mesh));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshFilterComponent::ClearMesh() {
|
||||||
|
m_mesh.Reset();
|
||||||
|
m_meshPath.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshFilterComponent::Serialize(std::ostream& os) const {
|
||||||
|
os << "mesh=" << m_meshPath << ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshFilterComponent::Deserialize(std::istream& is) {
|
||||||
|
m_mesh.Reset();
|
||||||
|
m_meshPath.clear();
|
||||||
|
|
||||||
|
std::string token;
|
||||||
|
while (std::getline(is, token, ';')) {
|
||||||
|
if (token.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t eqPos = token.find('=');
|
||||||
|
if (eqPos == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string key = token.substr(0, eqPos);
|
||||||
|
const std::string value = token.substr(eqPos + 1);
|
||||||
|
|
||||||
|
if (key == "mesh") {
|
||||||
|
m_meshPath = value;
|
||||||
|
if (!m_meshPath.empty()) {
|
||||||
|
m_mesh = Resources::ResourceManager::Get().Load<Resources::Mesh>(m_meshPath.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Components
|
||||||
|
} // namespace XCEngine
|
||||||
129
engine/src/Components/MeshRendererComponent.cpp
Normal file
129
engine/src/Components/MeshRendererComponent.cpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#include "Components/MeshRendererComponent.h"
|
||||||
|
|
||||||
|
#include "Core/Asset/ResourceManager.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string ToStdString(const Containers::String& value) {
|
||||||
|
return std::string(value.CStr());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SplitMaterialPaths(const std::string& value) {
|
||||||
|
std::vector<std::string> paths;
|
||||||
|
std::stringstream stream(value);
|
||||||
|
std::string item;
|
||||||
|
while (std::getline(stream, item, '|')) {
|
||||||
|
paths.push_back(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.empty()) {
|
||||||
|
paths.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Resources::Material* MeshRendererComponent::GetMaterial(size_t index) const {
|
||||||
|
return index < m_materials.size() ? m_materials[index].Get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Resources::ResourceHandle<Resources::Material>& MeshRendererComponent::GetMaterialHandle(size_t index) const {
|
||||||
|
static const Resources::ResourceHandle<Resources::Material> kNullHandle;
|
||||||
|
return index < m_materials.size() ? m_materials[index] : kNullHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshRendererComponent::SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material) {
|
||||||
|
EnsureMaterialSlot(index);
|
||||||
|
m_materials[index] = material;
|
||||||
|
m_materialPaths[index] = MaterialPathFromHandle(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshRendererComponent::SetMaterial(size_t index, Resources::Material* material) {
|
||||||
|
SetMaterial(index, Resources::ResourceHandle<Resources::Material>(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshRendererComponent::SetMaterials(const std::vector<Resources::ResourceHandle<Resources::Material>>& materials) {
|
||||||
|
m_materials = materials;
|
||||||
|
m_materialPaths.resize(materials.size());
|
||||||
|
for (size_t i = 0; i < materials.size(); ++i) {
|
||||||
|
m_materialPaths[i] = MaterialPathFromHandle(materials[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshRendererComponent::ClearMaterials() {
|
||||||
|
m_materials.clear();
|
||||||
|
m_materialPaths.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshRendererComponent::Serialize(std::ostream& os) const {
|
||||||
|
os << "materials=";
|
||||||
|
for (size_t i = 0; i < m_materialPaths.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
os << "|";
|
||||||
|
}
|
||||||
|
os << m_materialPaths[i];
|
||||||
|
}
|
||||||
|
os << ";";
|
||||||
|
os << "castShadows=" << (m_castShadows ? 1 : 0) << ";";
|
||||||
|
os << "receiveShadows=" << (m_receiveShadows ? 1 : 0) << ";";
|
||||||
|
os << "renderLayer=" << m_renderLayer << ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshRendererComponent::Deserialize(std::istream& is) {
|
||||||
|
ClearMaterials();
|
||||||
|
m_castShadows = true;
|
||||||
|
m_receiveShadows = true;
|
||||||
|
m_renderLayer = 0;
|
||||||
|
|
||||||
|
std::string token;
|
||||||
|
while (std::getline(is, token, ';')) {
|
||||||
|
if (token.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t eqPos = token.find('=');
|
||||||
|
if (eqPos == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string key = token.substr(0, eqPos);
|
||||||
|
const std::string value = token.substr(eqPos + 1);
|
||||||
|
|
||||||
|
if (key == "materials") {
|
||||||
|
m_materialPaths = SplitMaterialPaths(value);
|
||||||
|
m_materials.resize(m_materialPaths.size());
|
||||||
|
for (size_t i = 0; i < m_materialPaths.size(); ++i) {
|
||||||
|
if (!m_materialPaths[i].empty()) {
|
||||||
|
m_materials[i] = Resources::ResourceManager::Get().Load<Resources::Material>(m_materialPaths[i].c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (key == "castShadows") {
|
||||||
|
m_castShadows = (std::stoi(value) != 0);
|
||||||
|
} else if (key == "receiveShadows") {
|
||||||
|
m_receiveShadows = (std::stoi(value) != 0);
|
||||||
|
} else if (key == "renderLayer") {
|
||||||
|
m_renderLayer = static_cast<uint32_t>(std::stoul(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshRendererComponent::EnsureMaterialSlot(size_t index) {
|
||||||
|
if (index >= m_materials.size()) {
|
||||||
|
m_materials.resize(index + 1);
|
||||||
|
m_materialPaths.resize(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MeshRendererComponent::MaterialPathFromHandle(const Resources::ResourceHandle<Resources::Material>& material) {
|
||||||
|
return material.Get() != nullptr ? ToStdString(material->GetPath()) : std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Components
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "XCEngine/RHI/OpenGL/OpenGLPipelineState.h"
|
#include "XCEngine/RHI/OpenGL/OpenGLPipelineState.h"
|
||||||
#include "XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h"
|
#include "XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h"
|
||||||
#include "XCEngine/RHI/OpenGL/OpenGLShader.h"
|
#include "XCEngine/RHI/OpenGL/OpenGLShader.h"
|
||||||
|
#include "XCEngine/RHI/OpenGL/OpenGLTexture.h"
|
||||||
#include "XCEngine/RHI/OpenGL/OpenGLFramebuffer.h"
|
#include "XCEngine/RHI/OpenGL/OpenGLFramebuffer.h"
|
||||||
#include "XCEngine/RHI/OpenGL/OpenGLRenderPass.h"
|
#include "XCEngine/RHI/OpenGL/OpenGLRenderPass.h"
|
||||||
#include "XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h"
|
#include "XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h"
|
||||||
@@ -567,9 +568,14 @@ void OpenGLCommandList::SetUniformMat4(const char* name, const float* value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLCommandList::TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) {
|
void OpenGLCommandList::TransitionBarrier(RHIResourceView* resource, ResourceStates stateBefore, ResourceStates stateAfter) {
|
||||||
(void)resource;
|
|
||||||
(void)stateBefore;
|
(void)stateBefore;
|
||||||
(void)stateAfter;
|
if (resource != nullptr && resource->IsValid()) {
|
||||||
|
OpenGLResourceView* view = static_cast<OpenGLResourceView*>(resource);
|
||||||
|
OpenGLTexture* texture = const_cast<OpenGLTexture*>(view->GetTextureResource());
|
||||||
|
if (texture != nullptr) {
|
||||||
|
texture->SetState(stateAfter);
|
||||||
|
}
|
||||||
|
}
|
||||||
glMemoryBarrier(GL_ALL_BARRIER_BITS);
|
glMemoryBarrier(GL_ALL_BARRIER_BITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ bool OpenGLScreenshot::Capture(OpenGLDevice& device, OpenGLSwapChain& swapChain,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLScreenshot::Capture(OpenGLDevice& device, OpenGLSwapChain& swapChain, const char* filename, int width, int height) {
|
bool OpenGLScreenshot::Capture(OpenGLDevice& device, OpenGLSwapChain& swapChain, const char* filename, int width, int height) {
|
||||||
(void)device;
|
device.MakeContextCurrent();
|
||||||
(void)swapChain;
|
|
||||||
|
|
||||||
unsigned char* pixels = (unsigned char*)malloc(width * height * 3);
|
unsigned char* pixels = (unsigned char*)malloc(width * height * 3);
|
||||||
if (!pixels) {
|
if (!pixels) {
|
||||||
@@ -31,12 +30,32 @@ bool OpenGLScreenshot::Capture(OpenGLDevice& device, OpenGLSwapChain& swapChain,
|
|||||||
}
|
}
|
||||||
|
|
||||||
glFinish();
|
glFinish();
|
||||||
glReadBuffer(GL_BACK);
|
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||||
glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
|
glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
|
||||||
glPixelStorei(GL_PACK_SKIP_ROWS, 0);
|
glPixelStorei(GL_PACK_SKIP_ROWS, 0);
|
||||||
|
|
||||||
|
OpenGLTexture* backBufferTexture = static_cast<OpenGLTexture*>(swapChain.GetCurrentBackBuffer());
|
||||||
|
if (backBufferTexture != nullptr &&
|
||||||
|
backBufferTexture->GetID() != 0 &&
|
||||||
|
backBufferTexture->GetState() != ResourceStates::Common) {
|
||||||
|
GLuint readFramebuffer = 0;
|
||||||
|
glGenFramebuffers(1, &readFramebuffer);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFramebuffer);
|
||||||
|
glFramebufferTexture2D(
|
||||||
|
GL_READ_FRAMEBUFFER,
|
||||||
|
GL_COLOR_ATTACHMENT0,
|
||||||
|
GL_TEXTURE_2D,
|
||||||
|
backBufferTexture->GetID(),
|
||||||
|
0);
|
||||||
|
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||||
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
|
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||||
|
glDeleteFramebuffers(1, &readFramebuffer);
|
||||||
|
} else {
|
||||||
|
glReadBuffer(GL_BACK);
|
||||||
|
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
}
|
||||||
|
|
||||||
FILE* f = fopen(filename, "wb");
|
FILE* f = fopen(filename, "wb");
|
||||||
if (!f) {
|
if (!f) {
|
||||||
|
|||||||
@@ -51,7 +51,38 @@ void OpenGLSwapChain::SwapBuffers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLSwapChain::Present(uint32_t syncInterval, uint32_t flags) {
|
void OpenGLSwapChain::Present(uint32_t syncInterval, uint32_t flags) {
|
||||||
|
(void)syncInterval;
|
||||||
|
(void)flags;
|
||||||
if (m_device) {
|
if (m_device) {
|
||||||
|
m_device->MakeContextCurrent();
|
||||||
|
|
||||||
|
if (m_backBufferTexture != nullptr &&
|
||||||
|
m_backBufferTexture->GetID() != 0 &&
|
||||||
|
m_backBufferTexture->GetState() != ResourceStates::Common) {
|
||||||
|
GLuint readFramebuffer = 0;
|
||||||
|
glGenFramebuffers(1, &readFramebuffer);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, readFramebuffer);
|
||||||
|
glFramebufferTexture2D(
|
||||||
|
GL_READ_FRAMEBUFFER,
|
||||||
|
GL_COLOR_ATTACHMENT0,
|
||||||
|
GL_TEXTURE_2D,
|
||||||
|
m_backBufferTexture->GetID(),
|
||||||
|
0);
|
||||||
|
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
|
glDrawBuffer(GL_BACK);
|
||||||
|
glBlitFramebuffer(
|
||||||
|
0, 0, m_width, m_height,
|
||||||
|
0, 0, m_width, m_height,
|
||||||
|
GL_COLOR_BUFFER_BIT,
|
||||||
|
GL_NEAREST);
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
|
glDeleteFramebuffers(1, &readFramebuffer);
|
||||||
|
}
|
||||||
|
|
||||||
::SwapBuffers(m_device->GetPresentationDC());
|
::SwapBuffers(m_device->GetPresentationDC());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +101,7 @@ RHITexture* OpenGLSwapChain::GetCurrentBackBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void* OpenGLSwapChain::GetNativeHandle() {
|
void* OpenGLSwapChain::GetNativeHandle() {
|
||||||
return nullptr;
|
return m_hwnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace RHI
|
} // namespace RHI
|
||||||
|
|||||||
625
engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp
Normal file
625
engine/src/Rendering/Pipelines/BuiltinForwardPipeline.cpp
Normal file
@@ -0,0 +1,625 @@
|
|||||||
|
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
||||||
|
|
||||||
|
#include "Components/MeshFilterComponent.h"
|
||||||
|
#include "Components/MeshRendererComponent.h"
|
||||||
|
#include "RHI/RHICommandList.h"
|
||||||
|
#include "Rendering/RenderSurface.h"
|
||||||
|
#include "Resources/Material/Material.h"
|
||||||
|
#include "Resources/Texture/Texture.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
namespace Pipelines {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr uint32_t kDescriptorFirstSet = 1;
|
||||||
|
constexpr uint32_t kDescriptorSetCount = 4;
|
||||||
|
|
||||||
|
const char kBuiltinForwardHlsl[] = R"(
|
||||||
|
Texture2D gBaseColorTexture : register(t1);
|
||||||
|
SamplerState gLinearSampler : register(s1);
|
||||||
|
|
||||||
|
cbuffer PerObjectConstants : register(b1) {
|
||||||
|
float4x4 gProjectionMatrix;
|
||||||
|
float4x4 gViewMatrix;
|
||||||
|
float4x4 gModelMatrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VSInput {
|
||||||
|
float3 position : POSITION;
|
||||||
|
float2 texcoord : TEXCOORD0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PSInput {
|
||||||
|
float4 position : SV_POSITION;
|
||||||
|
float2 texcoord : TEXCOORD0;
|
||||||
|
};
|
||||||
|
|
||||||
|
PSInput MainVS(VSInput input) {
|
||||||
|
PSInput output;
|
||||||
|
float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f));
|
||||||
|
float4 positionVS = mul(gViewMatrix, positionWS);
|
||||||
|
output.position = mul(gProjectionMatrix, positionVS);
|
||||||
|
output.texcoord = input.texcoord;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 MainPS(PSInput input) : SV_TARGET {
|
||||||
|
return gBaseColorTexture.Sample(gLinearSampler, input.texcoord);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char kBuiltinForwardVertexShader[] = R"(#version 430
|
||||||
|
layout(location = 0) in vec3 aPosition;
|
||||||
|
layout(location = 1) in vec2 aTexCoord;
|
||||||
|
|
||||||
|
layout(std140, binding = 1) uniform PerObjectConstants {
|
||||||
|
mat4 gProjectionMatrix;
|
||||||
|
mat4 gViewMatrix;
|
||||||
|
mat4 gModelMatrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
out vec2 vTexCoord;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 positionWS = gModelMatrix * vec4(aPosition, 1.0);
|
||||||
|
vec4 positionVS = gViewMatrix * positionWS;
|
||||||
|
gl_Position = gProjectionMatrix * positionVS;
|
||||||
|
vTexCoord = aTexCoord;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char kBuiltinForwardFragmentShader[] = R"(#version 430
|
||||||
|
layout(binding = 1) uniform sampler2D uBaseColorTexture;
|
||||||
|
|
||||||
|
in vec2 vTexCoord;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = texture(uBaseColorTexture, vTexCoord);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
RHI::GraphicsPipelineDesc CreatePipelineDesc(RHI::RHIType backendType, RHI::RHIPipelineLayout* pipelineLayout) {
|
||||||
|
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||||
|
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||||
|
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||||
|
pipelineDesc.renderTargetCount = 1;
|
||||||
|
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||||
|
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
||||||
|
pipelineDesc.sampleCount = 1;
|
||||||
|
|
||||||
|
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
||||||
|
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
||||||
|
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
||||||
|
pipelineDesc.rasterizerState.depthClipEnable = true;
|
||||||
|
|
||||||
|
pipelineDesc.depthStencilState.depthTestEnable = false;
|
||||||
|
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
||||||
|
pipelineDesc.depthStencilState.stencilEnable = false;
|
||||||
|
|
||||||
|
RHI::InputElementDesc position = {};
|
||||||
|
position.semanticName = "POSITION";
|
||||||
|
position.semanticIndex = 0;
|
||||||
|
position.format = static_cast<uint32_t>(RHI::Format::R32G32B32A32_Float);
|
||||||
|
position.inputSlot = 0;
|
||||||
|
position.alignedByteOffset = 0;
|
||||||
|
pipelineDesc.inputLayout.elements.push_back(position);
|
||||||
|
|
||||||
|
RHI::InputElementDesc texcoord = {};
|
||||||
|
texcoord.semanticName = "TEXCOORD";
|
||||||
|
texcoord.semanticIndex = 0;
|
||||||
|
texcoord.format = static_cast<uint32_t>(RHI::Format::R32G32_Float);
|
||||||
|
texcoord.inputSlot = 0;
|
||||||
|
texcoord.alignedByteOffset = static_cast<uint32_t>(offsetof(Resources::StaticMeshVertex, uv0));
|
||||||
|
pipelineDesc.inputLayout.elements.push_back(texcoord);
|
||||||
|
|
||||||
|
if (backendType == RHI::RHIType::D3D12) {
|
||||||
|
pipelineDesc.vertexShader.source.assign(
|
||||||
|
kBuiltinForwardHlsl,
|
||||||
|
kBuiltinForwardHlsl + std::strlen(kBuiltinForwardHlsl));
|
||||||
|
pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
||||||
|
pipelineDesc.vertexShader.entryPoint = L"MainVS";
|
||||||
|
pipelineDesc.vertexShader.profile = L"vs_5_0";
|
||||||
|
|
||||||
|
pipelineDesc.fragmentShader.source.assign(
|
||||||
|
kBuiltinForwardHlsl,
|
||||||
|
kBuiltinForwardHlsl + std::strlen(kBuiltinForwardHlsl));
|
||||||
|
pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
||||||
|
pipelineDesc.fragmentShader.entryPoint = L"MainPS";
|
||||||
|
pipelineDesc.fragmentShader.profile = L"ps_5_0";
|
||||||
|
} else {
|
||||||
|
pipelineDesc.vertexShader.source.assign(
|
||||||
|
kBuiltinForwardVertexShader,
|
||||||
|
kBuiltinForwardVertexShader + std::strlen(kBuiltinForwardVertexShader));
|
||||||
|
pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::GLSL;
|
||||||
|
pipelineDesc.vertexShader.profile = L"vs_4_30";
|
||||||
|
|
||||||
|
pipelineDesc.fragmentShader.source.assign(
|
||||||
|
kBuiltinForwardFragmentShader,
|
||||||
|
kBuiltinForwardFragmentShader + std::strlen(kBuiltinForwardFragmentShader));
|
||||||
|
pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::GLSL;
|
||||||
|
pipelineDesc.fragmentShader.profile = L"fs_4_30";
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipelineDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Resources::Texture* FindMaterialTexture(const Resources::Material& material) {
|
||||||
|
static const char* kTextureNames[] = {
|
||||||
|
"_BaseColorTexture",
|
||||||
|
"_MainTex",
|
||||||
|
"mainTexture",
|
||||||
|
"texture"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char* textureName : kTextureNames) {
|
||||||
|
const Resources::ResourceHandle<Resources::Texture> textureHandle = material.GetTexture(textureName);
|
||||||
|
if (textureHandle.Get() != nullptr && textureHandle->IsValid()) {
|
||||||
|
return textureHandle.Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BuiltinForwardPipeline::~BuiltinForwardPipeline() {
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuiltinForwardPipeline::Initialize(const RenderContext& context) {
|
||||||
|
return EnsureInitialized(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuiltinForwardPipeline::Shutdown() {
|
||||||
|
DestroyPipelineResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuiltinForwardPipeline::Render(
|
||||||
|
const RenderContext& context,
|
||||||
|
const RenderSurface& surface,
|
||||||
|
const RenderSceneData& sceneData) {
|
||||||
|
if (!EnsureInitialized(context)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<RHI::RHIResourceView*>& colorAttachments = surface.GetColorAttachments();
|
||||||
|
if (colorAttachments.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<RHI::RHIResourceView*> renderTargets = colorAttachments;
|
||||||
|
RHI::RHICommandList* commandList = context.commandList;
|
||||||
|
|
||||||
|
if (surface.IsAutoTransitionEnabled()) {
|
||||||
|
for (RHI::RHIResourceView* renderTarget : renderTargets) {
|
||||||
|
if (renderTarget != nullptr) {
|
||||||
|
commandList->TransitionBarrier(
|
||||||
|
renderTarget,
|
||||||
|
surface.GetColorStateBefore(),
|
||||||
|
RHI::ResourceStates::RenderTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commandList->SetRenderTargets(
|
||||||
|
static_cast<uint32_t>(renderTargets.size()),
|
||||||
|
renderTargets.data(),
|
||||||
|
surface.GetDepthAttachment());
|
||||||
|
|
||||||
|
const RHI::Viewport viewport = {
|
||||||
|
0.0f,
|
||||||
|
0.0f,
|
||||||
|
static_cast<float>(surface.GetWidth()),
|
||||||
|
static_cast<float>(surface.GetHeight()),
|
||||||
|
0.0f,
|
||||||
|
1.0f
|
||||||
|
};
|
||||||
|
const RHI::Rect scissorRect = {
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
static_cast<int32_t>(surface.GetWidth()),
|
||||||
|
static_cast<int32_t>(surface.GetHeight())
|
||||||
|
};
|
||||||
|
commandList->SetViewport(viewport);
|
||||||
|
commandList->SetScissorRect(scissorRect);
|
||||||
|
|
||||||
|
const Math::Color clearColor = surface.HasClearColorOverride()
|
||||||
|
? surface.GetClearColorOverride()
|
||||||
|
: sceneData.cameraData.clearColor;
|
||||||
|
const float clearValues[4] = { clearColor.r, clearColor.g, clearColor.b, clearColor.a };
|
||||||
|
for (RHI::RHIResourceView* renderTarget : renderTargets) {
|
||||||
|
if (renderTarget != nullptr) {
|
||||||
|
commandList->ClearRenderTarget(renderTarget, clearValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (surface.GetDepthAttachment() != nullptr) {
|
||||||
|
commandList->ClearDepthStencil(surface.GetDepthAttachment(), 1.0f, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
commandList->SetPipelineState(m_pipelineState);
|
||||||
|
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||||
|
|
||||||
|
for (const VisibleRenderObject& visibleObject : sceneData.visibleObjects) {
|
||||||
|
DrawVisibleObject(context, sceneData, visibleObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surface.IsAutoTransitionEnabled()) {
|
||||||
|
for (RHI::RHIResourceView* renderTarget : renderTargets) {
|
||||||
|
if (renderTarget != nullptr) {
|
||||||
|
commandList->TransitionBarrier(
|
||||||
|
renderTarget,
|
||||||
|
RHI::ResourceStates::RenderTarget,
|
||||||
|
surface.GetColorStateAfter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuiltinForwardPipeline::EnsureInitialized(const RenderContext& context) {
|
||||||
|
if (!context.IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_initialized &&
|
||||||
|
m_device == context.device &&
|
||||||
|
m_backendType == context.backendType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyPipelineResources();
|
||||||
|
m_device = context.device;
|
||||||
|
m_backendType = context.backendType;
|
||||||
|
m_initialized = CreatePipelineResources(context);
|
||||||
|
return m_initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuiltinForwardPipeline::CreatePipelineResources(const RenderContext& context) {
|
||||||
|
RHI::DescriptorPoolDesc constantPoolDesc = {};
|
||||||
|
constantPoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
||||||
|
constantPoolDesc.descriptorCount = 1;
|
||||||
|
constantPoolDesc.shaderVisible = false;
|
||||||
|
m_constantPool = context.device->CreateDescriptorPool(constantPoolDesc);
|
||||||
|
if (m_constantPool == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutBinding constantBinding = {};
|
||||||
|
constantBinding.binding = 0;
|
||||||
|
constantBinding.type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
|
||||||
|
constantBinding.count = 1;
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutDesc constantLayout = {};
|
||||||
|
constantLayout.bindings = &constantBinding;
|
||||||
|
constantLayout.bindingCount = 1;
|
||||||
|
m_constantSet = m_constantPool->AllocateSet(constantLayout);
|
||||||
|
if (m_constantSet == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::DescriptorPoolDesc texturePoolDesc = {};
|
||||||
|
texturePoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
||||||
|
texturePoolDesc.descriptorCount = 1;
|
||||||
|
texturePoolDesc.shaderVisible = true;
|
||||||
|
m_texturePool = context.device->CreateDescriptorPool(texturePoolDesc);
|
||||||
|
if (m_texturePool == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutBinding textureBinding = {};
|
||||||
|
textureBinding.binding = 0;
|
||||||
|
textureBinding.type = static_cast<uint32_t>(RHI::DescriptorType::SRV);
|
||||||
|
textureBinding.count = 1;
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutDesc textureLayout = {};
|
||||||
|
textureLayout.bindings = &textureBinding;
|
||||||
|
textureLayout.bindingCount = 1;
|
||||||
|
m_textureSet = m_texturePool->AllocateSet(textureLayout);
|
||||||
|
if (m_textureSet == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::DescriptorPoolDesc samplerPoolDesc = {};
|
||||||
|
samplerPoolDesc.type = RHI::DescriptorHeapType::Sampler;
|
||||||
|
samplerPoolDesc.descriptorCount = 1;
|
||||||
|
samplerPoolDesc.shaderVisible = true;
|
||||||
|
m_samplerPool = context.device->CreateDescriptorPool(samplerPoolDesc);
|
||||||
|
if (m_samplerPool == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutBinding samplerBinding = {};
|
||||||
|
samplerBinding.binding = 0;
|
||||||
|
samplerBinding.type = static_cast<uint32_t>(RHI::DescriptorType::Sampler);
|
||||||
|
samplerBinding.count = 1;
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutDesc samplerLayout = {};
|
||||||
|
samplerLayout.bindings = &samplerBinding;
|
||||||
|
samplerLayout.bindingCount = 1;
|
||||||
|
m_samplerSet = m_samplerPool->AllocateSet(samplerLayout);
|
||||||
|
if (m_samplerSet == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutBinding reservedBindings[3] = {};
|
||||||
|
reservedBindings[0].binding = 0;
|
||||||
|
reservedBindings[0].type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
|
||||||
|
reservedBindings[0].count = 1;
|
||||||
|
reservedBindings[1].binding = 0;
|
||||||
|
reservedBindings[1].type = static_cast<uint32_t>(RHI::DescriptorType::SRV);
|
||||||
|
reservedBindings[1].count = 1;
|
||||||
|
reservedBindings[2].binding = 0;
|
||||||
|
reservedBindings[2].type = static_cast<uint32_t>(RHI::DescriptorType::Sampler);
|
||||||
|
reservedBindings[2].count = 1;
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutDesc reservedLayout = {};
|
||||||
|
reservedLayout.bindings = reservedBindings;
|
||||||
|
reservedLayout.bindingCount = 3;
|
||||||
|
|
||||||
|
RHI::DescriptorSetLayoutDesc setLayouts[kDescriptorSetCount] = {};
|
||||||
|
setLayouts[0] = reservedLayout;
|
||||||
|
setLayouts[1] = constantLayout;
|
||||||
|
setLayouts[2] = textureLayout;
|
||||||
|
setLayouts[3] = samplerLayout;
|
||||||
|
|
||||||
|
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||||
|
pipelineLayoutDesc.setLayouts = setLayouts;
|
||||||
|
pipelineLayoutDesc.setLayoutCount = kDescriptorSetCount;
|
||||||
|
m_pipelineLayout = context.device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||||
|
if (m_pipelineLayout == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RHI::GraphicsPipelineDesc pipelineDesc = CreatePipelineDesc(context.backendType, m_pipelineLayout);
|
||||||
|
m_pipelineState = context.device->CreatePipelineState(pipelineDesc);
|
||||||
|
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::SamplerDesc samplerDesc = {};
|
||||||
|
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
|
||||||
|
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||||
|
samplerDesc.addressV = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||||
|
samplerDesc.addressW = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||||
|
samplerDesc.mipLodBias = 0.0f;
|
||||||
|
samplerDesc.maxAnisotropy = 1;
|
||||||
|
samplerDesc.comparisonFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||||
|
samplerDesc.minLod = 0.0f;
|
||||||
|
samplerDesc.maxLod = 1000.0f;
|
||||||
|
m_sampler = context.device->CreateSampler(samplerDesc);
|
||||||
|
if (m_sampler == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_samplerSet->UpdateSampler(0, m_sampler);
|
||||||
|
|
||||||
|
const unsigned char whitePixel[4] = { 255, 255, 255, 255 };
|
||||||
|
RHI::TextureDesc textureDesc = {};
|
||||||
|
textureDesc.width = 1;
|
||||||
|
textureDesc.height = 1;
|
||||||
|
textureDesc.depth = 1;
|
||||||
|
textureDesc.mipLevels = 1;
|
||||||
|
textureDesc.arraySize = 1;
|
||||||
|
textureDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||||
|
textureDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
|
||||||
|
textureDesc.sampleCount = 1;
|
||||||
|
textureDesc.sampleQuality = 0;
|
||||||
|
textureDesc.flags = 0;
|
||||||
|
m_fallbackTexture = context.device->CreateTexture(textureDesc, whitePixel, sizeof(whitePixel), 4);
|
||||||
|
if (m_fallbackTexture == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::ResourceViewDesc textureViewDesc = {};
|
||||||
|
textureViewDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||||
|
textureViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
|
||||||
|
textureViewDesc.mipLevel = 0;
|
||||||
|
m_fallbackTextureView = context.device->CreateShaderResourceView(m_fallbackTexture, textureViewDesc);
|
||||||
|
if (m_fallbackTextureView == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuiltinForwardPipeline::DestroyPipelineResources() {
|
||||||
|
m_resourceCache.Shutdown();
|
||||||
|
|
||||||
|
if (m_fallbackTextureView != nullptr) {
|
||||||
|
m_fallbackTextureView->Shutdown();
|
||||||
|
delete m_fallbackTextureView;
|
||||||
|
m_fallbackTextureView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_fallbackTexture != nullptr) {
|
||||||
|
m_fallbackTexture->Shutdown();
|
||||||
|
delete m_fallbackTexture;
|
||||||
|
m_fallbackTexture = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_sampler != nullptr) {
|
||||||
|
m_sampler->Shutdown();
|
||||||
|
delete m_sampler;
|
||||||
|
m_sampler = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pipelineState != nullptr) {
|
||||||
|
m_pipelineState->Shutdown();
|
||||||
|
delete m_pipelineState;
|
||||||
|
m_pipelineState = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pipelineLayout != nullptr) {
|
||||||
|
m_pipelineLayout->Shutdown();
|
||||||
|
delete m_pipelineLayout;
|
||||||
|
m_pipelineLayout = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_constantSet != nullptr) {
|
||||||
|
m_constantSet->Shutdown();
|
||||||
|
delete m_constantSet;
|
||||||
|
m_constantSet = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_textureSet != nullptr) {
|
||||||
|
m_textureSet->Shutdown();
|
||||||
|
delete m_textureSet;
|
||||||
|
m_textureSet = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_samplerSet != nullptr) {
|
||||||
|
m_samplerSet->Shutdown();
|
||||||
|
delete m_samplerSet;
|
||||||
|
m_samplerSet = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_constantPool != nullptr) {
|
||||||
|
m_constantPool->Shutdown();
|
||||||
|
delete m_constantPool;
|
||||||
|
m_constantPool = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_texturePool != nullptr) {
|
||||||
|
m_texturePool->Shutdown();
|
||||||
|
delete m_texturePool;
|
||||||
|
m_texturePool = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_samplerPool != nullptr) {
|
||||||
|
m_samplerPool->Shutdown();
|
||||||
|
delete m_samplerPool;
|
||||||
|
m_samplerPool = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_device = nullptr;
|
||||||
|
m_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Resources::Material* BuiltinForwardPipeline::ResolveMaterial(
|
||||||
|
const VisibleRenderObject& visibleObject,
|
||||||
|
uint32_t materialIndex) const {
|
||||||
|
if (visibleObject.meshRenderer != nullptr && materialIndex < visibleObject.meshRenderer->GetMaterialCount()) {
|
||||||
|
if (const Resources::Material* material = visibleObject.meshRenderer->GetMaterial(materialIndex)) {
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibleObject.mesh != nullptr && materialIndex < visibleObject.mesh->GetMaterials().Size()) {
|
||||||
|
if (const Resources::Material* material = visibleObject.mesh->GetMaterials()[materialIndex]) {
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibleObject.meshRenderer != nullptr && visibleObject.meshRenderer->GetMaterialCount() > 0) {
|
||||||
|
if (const Resources::Material* material = visibleObject.meshRenderer->GetMaterial(0)) {
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibleObject.mesh != nullptr && visibleObject.mesh->GetMaterials().Size() > 0) {
|
||||||
|
return visibleObject.mesh->GetMaterials()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Resources::Texture* BuiltinForwardPipeline::ResolveTexture(const Resources::Material* material) const {
|
||||||
|
return material != nullptr ? FindMaterialTexture(*material) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::RHIResourceView* BuiltinForwardPipeline::ResolveTextureView(
|
||||||
|
const VisibleRenderObject& visibleObject,
|
||||||
|
uint32_t materialIndex) {
|
||||||
|
const Resources::Material* material = ResolveMaterial(visibleObject, materialIndex);
|
||||||
|
const Resources::Texture* texture = ResolveTexture(material);
|
||||||
|
if (texture != nullptr) {
|
||||||
|
const RenderResourceCache::CachedTexture* cachedTexture = m_resourceCache.GetOrCreateTexture(m_device, texture);
|
||||||
|
if (cachedTexture != nullptr && cachedTexture->shaderResourceView != nullptr) {
|
||||||
|
return cachedTexture->shaderResourceView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_fallbackTextureView;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuiltinForwardPipeline::DrawVisibleObject(
|
||||||
|
const RenderContext& context,
|
||||||
|
const RenderSceneData& sceneData,
|
||||||
|
const VisibleRenderObject& visibleObject) {
|
||||||
|
const RenderResourceCache::CachedMesh* cachedMesh = m_resourceCache.GetOrCreateMesh(m_device, visibleObject.mesh);
|
||||||
|
if (cachedMesh == nullptr || cachedMesh->vertexBufferView == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::RHICommandList* commandList = context.commandList;
|
||||||
|
|
||||||
|
RHI::RHIResourceView* vertexBuffers[] = { cachedMesh->vertexBufferView };
|
||||||
|
const uint64_t offsets[] = { 0 };
|
||||||
|
const uint32_t strides[] = { cachedMesh->vertexStride };
|
||||||
|
commandList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
|
||||||
|
if (cachedMesh->indexBufferView != nullptr) {
|
||||||
|
commandList->SetIndexBuffer(cachedMesh->indexBufferView, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PerObjectConstants constants = {
|
||||||
|
sceneData.cameraData.projection,
|
||||||
|
sceneData.cameraData.view,
|
||||||
|
visibleObject.localToWorld.Transpose()
|
||||||
|
};
|
||||||
|
|
||||||
|
const Containers::Array<Resources::MeshSection>& sections = visibleObject.mesh->GetSections();
|
||||||
|
const bool hasSections = !sections.Empty();
|
||||||
|
|
||||||
|
if (hasSections) {
|
||||||
|
for (size_t sectionIndex = 0; sectionIndex < sections.Size(); ++sectionIndex) {
|
||||||
|
const Resources::MeshSection& section = sections[sectionIndex];
|
||||||
|
RHI::RHIResourceView* textureView = ResolveTextureView(visibleObject, section.materialID);
|
||||||
|
if (textureView == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_constantSet->WriteConstant(0, &constants, sizeof(constants));
|
||||||
|
m_textureSet->Update(0, textureView);
|
||||||
|
RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, m_textureSet, m_samplerSet };
|
||||||
|
commandList->SetGraphicsDescriptorSets(kDescriptorFirstSet, 3, descriptorSets, m_pipelineLayout);
|
||||||
|
|
||||||
|
if (cachedMesh->indexBufferView != nullptr && section.indexCount > 0) {
|
||||||
|
commandList->DrawIndexed(section.indexCount, 1, section.startIndex, static_cast<int32_t>(section.baseVertex), 0);
|
||||||
|
} else if (section.vertexCount > 0) {
|
||||||
|
commandList->Draw(section.vertexCount, 1, section.baseVertex, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::RHIResourceView* textureView = ResolveTextureView(visibleObject, 0);
|
||||||
|
if (textureView == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_constantSet->WriteConstant(0, &constants, sizeof(constants));
|
||||||
|
m_textureSet->Update(0, textureView);
|
||||||
|
RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet, m_textureSet, m_samplerSet };
|
||||||
|
commandList->SetGraphicsDescriptorSets(kDescriptorFirstSet, 3, descriptorSets, m_pipelineLayout);
|
||||||
|
|
||||||
|
if (cachedMesh->indexBufferView != nullptr && cachedMesh->indexCount > 0) {
|
||||||
|
commandList->DrawIndexed(cachedMesh->indexCount, 1, 0, 0, 0);
|
||||||
|
} else if (cachedMesh->vertexCount > 0) {
|
||||||
|
commandList->Draw(cachedMesh->vertexCount, 1, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Pipelines
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
250
engine/src/Rendering/RenderResourceCache.cpp
Normal file
250
engine/src/Rendering/RenderResourceCache.cpp
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
#include "Rendering/RenderResourceCache.h"
|
||||||
|
|
||||||
|
#include <XCEngine/RHI/RHIEnums.h>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
RHI::Format ToRHITextureFormat(Resources::TextureFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case Resources::TextureFormat::RGBA8_UNORM:
|
||||||
|
case Resources::TextureFormat::RGBA8_SRGB:
|
||||||
|
return RHI::Format::R8G8B8A8_UNorm;
|
||||||
|
default:
|
||||||
|
return RHI::Format::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::TextureType ToRHITextureType(Resources::TextureType textureType) {
|
||||||
|
switch (textureType) {
|
||||||
|
case Resources::TextureType::Texture2DArray:
|
||||||
|
return RHI::TextureType::Texture2DArray;
|
||||||
|
case Resources::TextureType::Texture3D:
|
||||||
|
return RHI::TextureType::Texture3D;
|
||||||
|
case Resources::TextureType::TextureCube:
|
||||||
|
return RHI::TextureType::TextureCube;
|
||||||
|
case Resources::TextureType::TextureCubeArray:
|
||||||
|
return RHI::TextureType::TextureCubeArray;
|
||||||
|
case Resources::TextureType::Texture2D:
|
||||||
|
default:
|
||||||
|
return RHI::TextureType::Texture2D;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownMesh(RenderResourceCache::CachedMesh& cachedMesh) {
|
||||||
|
if (cachedMesh.vertexBufferView != nullptr) {
|
||||||
|
cachedMesh.vertexBufferView->Shutdown();
|
||||||
|
delete cachedMesh.vertexBufferView;
|
||||||
|
cachedMesh.vertexBufferView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedMesh.indexBufferView != nullptr) {
|
||||||
|
cachedMesh.indexBufferView->Shutdown();
|
||||||
|
delete cachedMesh.indexBufferView;
|
||||||
|
cachedMesh.indexBufferView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedMesh.vertexBuffer != nullptr) {
|
||||||
|
cachedMesh.vertexBuffer->Shutdown();
|
||||||
|
delete cachedMesh.vertexBuffer;
|
||||||
|
cachedMesh.vertexBuffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedMesh.indexBuffer != nullptr) {
|
||||||
|
cachedMesh.indexBuffer->Shutdown();
|
||||||
|
delete cachedMesh.indexBuffer;
|
||||||
|
cachedMesh.indexBuffer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShutdownTexture(RenderResourceCache::CachedTexture& cachedTexture) {
|
||||||
|
if (cachedTexture.shaderResourceView != nullptr) {
|
||||||
|
cachedTexture.shaderResourceView->Shutdown();
|
||||||
|
delete cachedTexture.shaderResourceView;
|
||||||
|
cachedTexture.shaderResourceView = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cachedTexture.texture != nullptr) {
|
||||||
|
cachedTexture.texture->Shutdown();
|
||||||
|
delete cachedTexture.texture;
|
||||||
|
cachedTexture.texture = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
RenderResourceCache::~RenderResourceCache() {
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderResourceCache::Shutdown() {
|
||||||
|
for (auto& entry : m_meshCache) {
|
||||||
|
ShutdownMesh(entry.second);
|
||||||
|
}
|
||||||
|
m_meshCache.clear();
|
||||||
|
|
||||||
|
for (auto& entry : m_textureCache) {
|
||||||
|
ShutdownTexture(entry.second);
|
||||||
|
}
|
||||||
|
m_textureCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderResourceCache::CachedMesh* RenderResourceCache::GetOrCreateMesh(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::Mesh* mesh) {
|
||||||
|
if (device == nullptr || mesh == nullptr || !mesh->IsValid()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = m_meshCache.find(mesh);
|
||||||
|
if (it != m_meshCache.end()) {
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
CachedMesh cachedMesh;
|
||||||
|
if (!UploadMesh(device, mesh, cachedMesh)) {
|
||||||
|
ShutdownMesh(cachedMesh);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result = m_meshCache.emplace(mesh, cachedMesh);
|
||||||
|
return &result.first->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RenderResourceCache::CachedTexture* RenderResourceCache::GetOrCreateTexture(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::Texture* texture) {
|
||||||
|
if (device == nullptr || texture == nullptr || !texture->IsValid()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = m_textureCache.find(texture);
|
||||||
|
if (it != m_textureCache.end()) {
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
CachedTexture cachedTexture;
|
||||||
|
if (!UploadTexture(device, texture, cachedTexture)) {
|
||||||
|
ShutdownTexture(cachedTexture);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result = m_textureCache.emplace(texture, cachedTexture);
|
||||||
|
return &result.first->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderResourceCache::UploadMesh(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::Mesh* mesh,
|
||||||
|
CachedMesh& cachedMesh) {
|
||||||
|
if (mesh->GetVertexData() == nullptr || mesh->GetVertexDataSize() == 0 || mesh->GetVertexStride() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::BufferDesc vertexBufferDesc = {};
|
||||||
|
vertexBufferDesc.size = static_cast<uint64_t>(mesh->GetVertexDataSize());
|
||||||
|
vertexBufferDesc.stride = mesh->GetVertexStride();
|
||||||
|
vertexBufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Vertex);
|
||||||
|
cachedMesh.vertexBuffer = device->CreateBuffer(vertexBufferDesc);
|
||||||
|
if (cachedMesh.vertexBuffer == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedMesh.vertexBuffer->SetData(mesh->GetVertexData(), mesh->GetVertexDataSize());
|
||||||
|
cachedMesh.vertexBuffer->SetStride(mesh->GetVertexStride());
|
||||||
|
cachedMesh.vertexBuffer->SetBufferType(RHI::BufferType::Vertex);
|
||||||
|
|
||||||
|
RHI::ResourceViewDesc vertexViewDesc = {};
|
||||||
|
vertexViewDesc.dimension = RHI::ResourceViewDimension::Buffer;
|
||||||
|
vertexViewDesc.structureByteStride = mesh->GetVertexStride();
|
||||||
|
cachedMesh.vertexBufferView = device->CreateVertexBufferView(cachedMesh.vertexBuffer, vertexViewDesc);
|
||||||
|
if (cachedMesh.vertexBufferView == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedMesh.vertexCount = mesh->GetVertexCount();
|
||||||
|
cachedMesh.vertexStride = mesh->GetVertexStride();
|
||||||
|
|
||||||
|
if (mesh->GetIndexData() != nullptr && mesh->GetIndexDataSize() > 0 && mesh->GetIndexCount() > 0) {
|
||||||
|
RHI::BufferDesc indexBufferDesc = {};
|
||||||
|
indexBufferDesc.size = static_cast<uint64_t>(mesh->GetIndexDataSize());
|
||||||
|
indexBufferDesc.stride = mesh->IsUse32BitIndex() ? sizeof(uint32_t) : sizeof(uint16_t);
|
||||||
|
indexBufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Index);
|
||||||
|
cachedMesh.indexBuffer = device->CreateBuffer(indexBufferDesc);
|
||||||
|
if (cachedMesh.indexBuffer == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedMesh.indexBuffer->SetData(mesh->GetIndexData(), mesh->GetIndexDataSize());
|
||||||
|
cachedMesh.indexBuffer->SetStride(indexBufferDesc.stride);
|
||||||
|
cachedMesh.indexBuffer->SetBufferType(RHI::BufferType::Index);
|
||||||
|
|
||||||
|
RHI::ResourceViewDesc indexViewDesc = {};
|
||||||
|
indexViewDesc.dimension = RHI::ResourceViewDimension::Buffer;
|
||||||
|
indexViewDesc.format = static_cast<uint32_t>(
|
||||||
|
mesh->IsUse32BitIndex() ? RHI::Format::R32_UInt : RHI::Format::R16_UInt);
|
||||||
|
cachedMesh.indexBufferView = device->CreateIndexBufferView(cachedMesh.indexBuffer, indexViewDesc);
|
||||||
|
if (cachedMesh.indexBufferView == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedMesh.indexCount = mesh->GetIndexCount();
|
||||||
|
cachedMesh.uses32BitIndices = mesh->IsUse32BitIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderResourceCache::UploadTexture(
|
||||||
|
RHI::RHIDevice* device,
|
||||||
|
const Resources::Texture* texture,
|
||||||
|
CachedTexture& cachedTexture) {
|
||||||
|
if (texture->GetPixelData() == nullptr || texture->GetPixelDataSize() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RHI::Format format = ToRHITextureFormat(texture->GetFormat());
|
||||||
|
if (format == RHI::Format::Unknown) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::TextureDesc textureDesc = {};
|
||||||
|
textureDesc.width = texture->GetWidth();
|
||||||
|
textureDesc.height = texture->GetHeight();
|
||||||
|
textureDesc.depth = texture->GetDepth();
|
||||||
|
textureDesc.mipLevels = texture->GetMipLevels();
|
||||||
|
textureDesc.arraySize = 1;
|
||||||
|
textureDesc.format = static_cast<uint32_t>(format);
|
||||||
|
textureDesc.textureType = static_cast<uint32_t>(ToRHITextureType(texture->GetTextureType()));
|
||||||
|
textureDesc.sampleCount = 1;
|
||||||
|
textureDesc.sampleQuality = 0;
|
||||||
|
textureDesc.flags = 0;
|
||||||
|
|
||||||
|
const uint32_t rowPitch = texture->GetWidth() * 4;
|
||||||
|
cachedTexture.texture = device->CreateTexture(
|
||||||
|
textureDesc,
|
||||||
|
texture->GetPixelData(),
|
||||||
|
texture->GetPixelDataSize(),
|
||||||
|
rowPitch);
|
||||||
|
if (cachedTexture.texture == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHI::ResourceViewDesc textureViewDesc = {};
|
||||||
|
textureViewDesc.format = static_cast<uint32_t>(format);
|
||||||
|
textureViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
|
||||||
|
textureViewDesc.mipLevel = 0;
|
||||||
|
cachedTexture.shaderResourceView = device->CreateShaderResourceView(cachedTexture.texture, textureViewDesc);
|
||||||
|
if (cachedTexture.shaderResourceView == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedTexture.width = texture->GetWidth();
|
||||||
|
cachedTexture.height = texture->GetHeight();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
150
engine/src/Rendering/RenderSceneExtractor.cpp
Normal file
150
engine/src/Rendering/RenderSceneExtractor.cpp
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#include "Rendering/RenderSceneExtractor.h"
|
||||||
|
|
||||||
|
#include "Components/CameraComponent.h"
|
||||||
|
#include "Components/GameObject.h"
|
||||||
|
#include "Components/MeshFilterComponent.h"
|
||||||
|
#include "Components/MeshRendererComponent.h"
|
||||||
|
#include "Components/TransformComponent.h"
|
||||||
|
#include "Scene/Scene.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool IsUsableCamera(const Components::CameraComponent* camera) {
|
||||||
|
return camera != nullptr &&
|
||||||
|
camera->IsEnabled() &&
|
||||||
|
camera->GetGameObject() != nullptr &&
|
||||||
|
camera->GetGameObject()->IsActiveInHierarchy();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
RenderSceneData RenderSceneExtractor::Extract(
|
||||||
|
const Components::Scene& scene,
|
||||||
|
Components::CameraComponent* overrideCamera,
|
||||||
|
uint32_t viewportWidth,
|
||||||
|
uint32_t viewportHeight) const {
|
||||||
|
RenderSceneData sceneData;
|
||||||
|
sceneData.camera = SelectCamera(scene, overrideCamera);
|
||||||
|
if (sceneData.camera == nullptr) {
|
||||||
|
return sceneData;
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneData.cameraData = BuildCameraData(*sceneData.camera, viewportWidth, viewportHeight);
|
||||||
|
|
||||||
|
const std::vector<Components::GameObject*> rootGameObjects = scene.GetRootGameObjects();
|
||||||
|
for (Components::GameObject* rootGameObject : rootGameObjects) {
|
||||||
|
ExtractVisibleObjects(rootGameObject, sceneData.visibleObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sceneData;
|
||||||
|
}
|
||||||
|
|
||||||
|
Components::CameraComponent* RenderSceneExtractor::SelectCamera(
|
||||||
|
const Components::Scene& scene,
|
||||||
|
Components::CameraComponent* overrideCamera) const {
|
||||||
|
if (IsUsableCamera(overrideCamera)) {
|
||||||
|
return overrideCamera;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Components::CameraComponent*> cameras = scene.FindObjectsOfType<Components::CameraComponent>();
|
||||||
|
|
||||||
|
Components::CameraComponent* primaryCamera = nullptr;
|
||||||
|
for (Components::CameraComponent* camera : cameras) {
|
||||||
|
if (!IsUsableCamera(camera)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (camera->IsPrimary()) {
|
||||||
|
if (primaryCamera == nullptr || camera->GetDepth() > primaryCamera->GetDepth()) {
|
||||||
|
primaryCamera = camera;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (primaryCamera != nullptr) {
|
||||||
|
return primaryCamera;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Components::CameraComponent* camera : cameras) {
|
||||||
|
if (IsUsableCamera(camera)) {
|
||||||
|
return camera;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCameraData RenderSceneExtractor::BuildCameraData(
|
||||||
|
const Components::CameraComponent& camera,
|
||||||
|
uint32_t viewportWidth,
|
||||||
|
uint32_t viewportHeight) const {
|
||||||
|
RenderCameraData cameraData;
|
||||||
|
cameraData.viewportWidth = viewportWidth;
|
||||||
|
cameraData.viewportHeight = viewportHeight;
|
||||||
|
cameraData.worldPosition = camera.transform().GetPosition();
|
||||||
|
cameraData.clearColor = camera.GetClearColor();
|
||||||
|
|
||||||
|
const Math::Matrix4x4 view = camera.transform().GetWorldToLocalMatrix();
|
||||||
|
const float aspect = viewportHeight > 0
|
||||||
|
? static_cast<float>(viewportWidth) / static_cast<float>(viewportHeight)
|
||||||
|
: 1.0f;
|
||||||
|
|
||||||
|
Math::Matrix4x4 projection = Math::Matrix4x4::Identity();
|
||||||
|
if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) {
|
||||||
|
projection = Math::Matrix4x4::Perspective(
|
||||||
|
camera.GetFieldOfView() * Math::DEG_TO_RAD,
|
||||||
|
aspect,
|
||||||
|
camera.GetNearClipPlane(),
|
||||||
|
camera.GetFarClipPlane());
|
||||||
|
} else {
|
||||||
|
const float orthoSize = camera.GetOrthographicSize();
|
||||||
|
projection = Math::Matrix4x4::Orthographic(
|
||||||
|
-orthoSize * aspect,
|
||||||
|
orthoSize * aspect,
|
||||||
|
-orthoSize,
|
||||||
|
orthoSize,
|
||||||
|
camera.GetNearClipPlane(),
|
||||||
|
camera.GetFarClipPlane());
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraData.view = view.Transpose();
|
||||||
|
cameraData.projection = projection.Transpose();
|
||||||
|
cameraData.viewProjection = (projection * view).Transpose();
|
||||||
|
return cameraData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSceneExtractor::ExtractVisibleObjects(
|
||||||
|
Components::GameObject* gameObject,
|
||||||
|
std::vector<VisibleRenderObject>& visibleObjects) const {
|
||||||
|
if (gameObject == nullptr || !gameObject->IsActiveInHierarchy()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* meshFilter = gameObject->GetComponent<Components::MeshFilterComponent>();
|
||||||
|
auto* meshRenderer = gameObject->GetComponent<Components::MeshRendererComponent>();
|
||||||
|
if (meshFilter != nullptr &&
|
||||||
|
meshRenderer != nullptr &&
|
||||||
|
meshFilter->IsEnabled() &&
|
||||||
|
meshRenderer->IsEnabled()) {
|
||||||
|
Resources::Mesh* mesh = meshFilter->GetMesh();
|
||||||
|
if (mesh != nullptr && mesh->IsValid()) {
|
||||||
|
VisibleRenderObject visibleObject;
|
||||||
|
visibleObject.gameObject = gameObject;
|
||||||
|
visibleObject.meshFilter = meshFilter;
|
||||||
|
visibleObject.meshRenderer = meshRenderer;
|
||||||
|
visibleObject.mesh = mesh;
|
||||||
|
visibleObject.localToWorld = gameObject->GetTransform()->GetLocalToWorldMatrix();
|
||||||
|
visibleObjects.push_back(visibleObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Components::GameObject* child : gameObject->GetChildren()) {
|
||||||
|
ExtractVisibleObjects(child, visibleObjects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
37
engine/src/Rendering/RenderSurface.cpp
Normal file
37
engine/src/Rendering/RenderSurface.cpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include "Rendering/RenderSurface.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
RenderSurface::RenderSurface(uint32_t width, uint32_t height)
|
||||||
|
: m_width(width)
|
||||||
|
, m_height(height) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSurface::SetSize(uint32_t width, uint32_t height) {
|
||||||
|
m_width = width;
|
||||||
|
m_height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSurface::SetColorAttachment(RHI::RHIResourceView* colorAttachment) {
|
||||||
|
m_colorAttachments.clear();
|
||||||
|
if (colorAttachment != nullptr) {
|
||||||
|
m_colorAttachments.push_back(colorAttachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSurface::SetColorAttachments(const std::vector<RHI::RHIResourceView*>& colorAttachments) {
|
||||||
|
m_colorAttachments = colorAttachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSurface::SetClearColorOverride(const Math::Color& clearColor) {
|
||||||
|
m_hasClearColorOverride = true;
|
||||||
|
m_clearColorOverride = clearColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderSurface::ClearClearColorOverride() {
|
||||||
|
m_hasClearColorOverride = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
60
engine/src/Rendering/SceneRenderer.cpp
Normal file
60
engine/src/Rendering/SceneRenderer.cpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#include "Rendering/SceneRenderer.h"
|
||||||
|
|
||||||
|
#include "Rendering/Pipelines/BuiltinForwardPipeline.h"
|
||||||
|
#include "Rendering/RenderSurface.h"
|
||||||
|
#include "Scene/Scene.h"
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
SceneRenderer::SceneRenderer()
|
||||||
|
: m_pipeline(std::make_unique<Pipelines::BuiltinForwardPipeline>()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneRenderer::SceneRenderer(std::unique_ptr<RenderPipeline> pipeline)
|
||||||
|
: m_pipeline(std::move(pipeline)) {
|
||||||
|
if (!m_pipeline) {
|
||||||
|
m_pipeline = std::make_unique<Pipelines::BuiltinForwardPipeline>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneRenderer::~SceneRenderer() {
|
||||||
|
if (m_pipeline) {
|
||||||
|
m_pipeline->Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneRenderer::SetPipeline(std::unique_ptr<RenderPipeline> pipeline) {
|
||||||
|
if (m_pipeline) {
|
||||||
|
m_pipeline->Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pipeline = std::move(pipeline);
|
||||||
|
if (!m_pipeline) {
|
||||||
|
m_pipeline = std::make_unique<Pipelines::BuiltinForwardPipeline>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneRenderer::Render(
|
||||||
|
const Components::Scene& scene,
|
||||||
|
Components::CameraComponent* overrideCamera,
|
||||||
|
const RenderContext& context,
|
||||||
|
const RenderSurface& surface) {
|
||||||
|
if (!context.IsValid() || m_pipeline == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderSceneData sceneData = m_sceneExtractor.Extract(
|
||||||
|
scene,
|
||||||
|
overrideCamera,
|
||||||
|
surface.GetWidth(),
|
||||||
|
surface.GetHeight());
|
||||||
|
if (!sceneData.HasCamera()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_pipeline->Render(context, surface, sceneData);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -8,6 +8,7 @@ set(COMPONENTS_TEST_SOURCES
|
|||||||
test_transform_component.cpp
|
test_transform_component.cpp
|
||||||
test_game_object.cpp
|
test_game_object.cpp
|
||||||
test_camera_light_component.cpp
|
test_camera_light_component.cpp
|
||||||
|
test_mesh_render_components.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(components_tests ${COMPONENTS_TEST_SOURCES})
|
add_executable(components_tests ${COMPONENTS_TEST_SOURCES})
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <XCEngine/Components/ComponentFactoryRegistry.h>
|
#include <XCEngine/Components/ComponentFactoryRegistry.h>
|
||||||
#include <XCEngine/Components/GameObject.h>
|
#include <XCEngine/Components/GameObject.h>
|
||||||
#include <XCEngine/Components/LightComponent.h>
|
#include <XCEngine/Components/LightComponent.h>
|
||||||
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||||
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||||
|
|
||||||
using namespace XCEngine::Components;
|
using namespace XCEngine::Components;
|
||||||
|
|
||||||
@@ -18,6 +20,8 @@ TEST(ComponentFactoryRegistry_Test, BuiltInTypesAreRegistered) {
|
|||||||
EXPECT_TRUE(registry.IsRegistered("Light"));
|
EXPECT_TRUE(registry.IsRegistered("Light"));
|
||||||
EXPECT_TRUE(registry.IsRegistered("AudioSource"));
|
EXPECT_TRUE(registry.IsRegistered("AudioSource"));
|
||||||
EXPECT_TRUE(registry.IsRegistered("AudioListener"));
|
EXPECT_TRUE(registry.IsRegistered("AudioListener"));
|
||||||
|
EXPECT_TRUE(registry.IsRegistered("MeshFilter"));
|
||||||
|
EXPECT_TRUE(registry.IsRegistered("MeshRenderer"));
|
||||||
EXPECT_FALSE(registry.IsRegistered("Transform"));
|
EXPECT_FALSE(registry.IsRegistered("Transform"));
|
||||||
EXPECT_FALSE(registry.IsRegistered("MissingComponent"));
|
EXPECT_FALSE(registry.IsRegistered("MissingComponent"));
|
||||||
}
|
}
|
||||||
@@ -30,6 +34,8 @@ TEST(ComponentFactoryRegistry_Test, CreateBuiltInComponentsByTypeName) {
|
|||||||
EXPECT_NE(dynamic_cast<LightComponent*>(registry.CreateComponent(&gameObject, "Light")), nullptr);
|
EXPECT_NE(dynamic_cast<LightComponent*>(registry.CreateComponent(&gameObject, "Light")), nullptr);
|
||||||
EXPECT_NE(dynamic_cast<AudioSourceComponent*>(registry.CreateComponent(&gameObject, "AudioSource")), nullptr);
|
EXPECT_NE(dynamic_cast<AudioSourceComponent*>(registry.CreateComponent(&gameObject, "AudioSource")), nullptr);
|
||||||
EXPECT_NE(dynamic_cast<AudioListenerComponent*>(registry.CreateComponent(&gameObject, "AudioListener")), nullptr);
|
EXPECT_NE(dynamic_cast<AudioListenerComponent*>(registry.CreateComponent(&gameObject, "AudioListener")), nullptr);
|
||||||
|
EXPECT_NE(dynamic_cast<MeshFilterComponent*>(registry.CreateComponent(&gameObject, "MeshFilter")), nullptr);
|
||||||
|
EXPECT_NE(dynamic_cast<MeshRendererComponent*>(registry.CreateComponent(&gameObject, "MeshRenderer")), nullptr);
|
||||||
EXPECT_EQ(registry.CreateComponent(&gameObject, "MissingComponent"), nullptr);
|
EXPECT_EQ(registry.CreateComponent(&gameObject, "MissingComponent"), nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
125
tests/Components/test_mesh_render_components.cpp
Normal file
125
tests/Components/test_mesh_render_components.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <XCEngine/Components/GameObject.h>
|
||||||
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||||
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||||
|
#include <XCEngine/Core/Asset/IResource.h>
|
||||||
|
#include <XCEngine/Resources/Material/Material.h>
|
||||||
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using namespace XCEngine::Components;
|
||||||
|
using namespace XCEngine::Resources;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Mesh* CreateTestMesh(const char* name, const char* path) {
|
||||||
|
auto* mesh = new Mesh();
|
||||||
|
IResource::ConstructParams params = {};
|
||||||
|
params.name = name;
|
||||||
|
params.path = path;
|
||||||
|
params.guid = ResourceGUID::Generate(path);
|
||||||
|
mesh->Initialize(params);
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material* CreateTestMaterial(const char* name, const char* path) {
|
||||||
|
auto* material = new Material();
|
||||||
|
IResource::ConstructParams params = {};
|
||||||
|
params.name = name;
|
||||||
|
params.path = path;
|
||||||
|
params.guid = ResourceGUID::Generate(path);
|
||||||
|
material->Initialize(params);
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MeshFilterComponent_Test, SetMeshCachesResourceAndPath) {
|
||||||
|
GameObject gameObject("MeshHolder");
|
||||||
|
auto* component = gameObject.AddComponent<MeshFilterComponent>();
|
||||||
|
Mesh* mesh = CreateTestMesh("Quad", "Meshes/quad.mesh");
|
||||||
|
|
||||||
|
component->SetMesh(mesh);
|
||||||
|
|
||||||
|
EXPECT_EQ(component->GetMesh(), mesh);
|
||||||
|
EXPECT_EQ(component->GetMeshPath(), "Meshes/quad.mesh");
|
||||||
|
|
||||||
|
component->ClearMesh();
|
||||||
|
delete mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MeshFilterComponent_Test, SerializeAndDeserializePreservesPath) {
|
||||||
|
MeshFilterComponent source;
|
||||||
|
Mesh* mesh = CreateTestMesh("Quad", "Meshes/serialized.mesh");
|
||||||
|
source.SetMesh(mesh);
|
||||||
|
|
||||||
|
std::stringstream stream;
|
||||||
|
source.Serialize(stream);
|
||||||
|
|
||||||
|
MeshFilterComponent target;
|
||||||
|
target.Deserialize(stream);
|
||||||
|
|
||||||
|
EXPECT_EQ(target.GetMeshPath(), "Meshes/serialized.mesh");
|
||||||
|
EXPECT_EQ(target.GetMesh(), nullptr);
|
||||||
|
|
||||||
|
source.ClearMesh();
|
||||||
|
delete mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MeshRendererComponent_Test, SetMaterialsKeepsSlotsAndFlags) {
|
||||||
|
GameObject gameObject("RendererHolder");
|
||||||
|
auto* component = gameObject.AddComponent<MeshRendererComponent>();
|
||||||
|
Material* material0 = CreateTestMaterial("M0", "Materials/m0.mat");
|
||||||
|
Material* material1 = CreateTestMaterial("M1", "Materials/m1.mat");
|
||||||
|
|
||||||
|
component->SetMaterial(0, material0);
|
||||||
|
component->SetMaterial(1, material1);
|
||||||
|
component->SetCastShadows(false);
|
||||||
|
component->SetReceiveShadows(false);
|
||||||
|
component->SetRenderLayer(7);
|
||||||
|
|
||||||
|
ASSERT_EQ(component->GetMaterialCount(), 2u);
|
||||||
|
EXPECT_EQ(component->GetMaterial(0), material0);
|
||||||
|
EXPECT_EQ(component->GetMaterial(1), material1);
|
||||||
|
EXPECT_EQ(component->GetMaterialPaths()[0], "Materials/m0.mat");
|
||||||
|
EXPECT_EQ(component->GetMaterialPaths()[1], "Materials/m1.mat");
|
||||||
|
EXPECT_FALSE(component->GetCastShadows());
|
||||||
|
EXPECT_FALSE(component->GetReceiveShadows());
|
||||||
|
EXPECT_EQ(component->GetRenderLayer(), 7u);
|
||||||
|
|
||||||
|
component->ClearMaterials();
|
||||||
|
delete material0;
|
||||||
|
delete material1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesMaterialPathsAndSettings) {
|
||||||
|
MeshRendererComponent source;
|
||||||
|
Material* material0 = CreateTestMaterial("M0", "Materials/serialized0.mat");
|
||||||
|
Material* material1 = CreateTestMaterial("M1", "Materials/serialized1.mat");
|
||||||
|
source.SetMaterial(0, material0);
|
||||||
|
source.SetMaterial(1, material1);
|
||||||
|
source.SetCastShadows(false);
|
||||||
|
source.SetReceiveShadows(true);
|
||||||
|
source.SetRenderLayer(3);
|
||||||
|
|
||||||
|
std::stringstream stream;
|
||||||
|
source.Serialize(stream);
|
||||||
|
|
||||||
|
MeshRendererComponent target;
|
||||||
|
target.Deserialize(stream);
|
||||||
|
|
||||||
|
ASSERT_EQ(target.GetMaterialCount(), 2u);
|
||||||
|
EXPECT_EQ(target.GetMaterial(0), nullptr);
|
||||||
|
EXPECT_EQ(target.GetMaterial(1), nullptr);
|
||||||
|
EXPECT_EQ(target.GetMaterialPaths()[0], "Materials/serialized0.mat");
|
||||||
|
EXPECT_EQ(target.GetMaterialPaths()[1], "Materials/serialized1.mat");
|
||||||
|
EXPECT_FALSE(target.GetCastShadows());
|
||||||
|
EXPECT_TRUE(target.GetReceiveShadows());
|
||||||
|
EXPECT_EQ(target.GetRenderLayer(), 3u);
|
||||||
|
|
||||||
|
source.ClearMaterials();
|
||||||
|
delete material0;
|
||||||
|
delete material1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
6
tests/Rendering/CMakeLists.txt
Normal file
6
tests/Rendering/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
project(XCEngine_RenderingTests)
|
||||||
|
|
||||||
|
add_subdirectory(unit)
|
||||||
|
add_subdirectory(integration)
|
||||||
5
tests/Rendering/integration/CMakeLists.txt
Normal file
5
tests/Rendering/integration/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
project(XCEngine_RenderingIntegrationTests)
|
||||||
|
|
||||||
|
add_subdirectory(textured_quad_scene)
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
project(rendering_integration_textured_quad_scene)
|
||||||
|
|
||||||
|
set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine)
|
||||||
|
set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/tests/opengl/package)
|
||||||
|
|
||||||
|
get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE)
|
||||||
|
|
||||||
|
add_executable(rendering_integration_textured_quad_scene
|
||||||
|
main.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp
|
||||||
|
${PACKAGE_DIR}/src/glad.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(rendering_integration_textured_quad_scene PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${CMAKE_SOURCE_DIR}/tests/RHI/integration/fixtures
|
||||||
|
${ENGINE_ROOT_DIR}/include
|
||||||
|
${PACKAGE_DIR}/include
|
||||||
|
${PROJECT_ROOT_DIR}/engine/src
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(rendering_integration_textured_quad_scene PRIVATE
|
||||||
|
d3d12
|
||||||
|
dxgi
|
||||||
|
d3dcompiler
|
||||||
|
winmm
|
||||||
|
opengl32
|
||||||
|
XCEngine
|
||||||
|
GTest::gtest
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(rendering_integration_textured_quad_scene PRIVATE
|
||||||
|
UNICODE
|
||||||
|
_UNICODE
|
||||||
|
XCENGINE_SUPPORT_OPENGL
|
||||||
|
XCENGINE_SUPPORT_D3D12
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(TARGET rendering_integration_textured_quad_scene POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_SOURCE_DIR}/tests/RHI/integration/compare_ppm.py
|
||||||
|
$<TARGET_FILE_DIR:rendering_integration_textured_quad_scene>/
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm
|
||||||
|
$<TARGET_FILE_DIR:rendering_integration_textured_quad_scene>/GT.ppm
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${ENGINE_ROOT_DIR}/third_party/renderdoc/renderdoc.dll
|
||||||
|
$<TARGET_FILE_DIR:rendering_integration_textured_quad_scene>/
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(rendering_integration_textured_quad_scene)
|
||||||
4
tests/Rendering/integration/textured_quad_scene/GT.ppm
Normal file
4
tests/Rendering/integration/textured_quad_scene/GT.ppm
Normal file
File diff suppressed because one or more lines are too long
272
tests/Rendering/integration/textured_quad_scene/main.cpp
Normal file
272
tests/Rendering/integration/textured_quad_scene/main.cpp
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
|
#include <XCEngine/Components/GameObject.h>
|
||||||
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||||
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||||
|
#include <XCEngine/Core/Asset/IResource.h>
|
||||||
|
#include <XCEngine/Core/Math/Color.h>
|
||||||
|
#include <XCEngine/Core/Math/Vector2.h>
|
||||||
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
|
#include <XCEngine/Debug/ConsoleLogSink.h>
|
||||||
|
#include <XCEngine/Debug/Logger.h>
|
||||||
|
#include <XCEngine/Rendering/RenderContext.h>
|
||||||
|
#include <XCEngine/Rendering/RenderSurface.h>
|
||||||
|
#include <XCEngine/Rendering/SceneRenderer.h>
|
||||||
|
#include <XCEngine/Resources/Material/Material.h>
|
||||||
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||||
|
#include <XCEngine/Resources/Texture/Texture.h>
|
||||||
|
#include <XCEngine/Scene/Scene.h>
|
||||||
|
|
||||||
|
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace XCEngine::Components;
|
||||||
|
using namespace XCEngine::Debug;
|
||||||
|
using namespace XCEngine::Math;
|
||||||
|
using namespace XCEngine::Rendering;
|
||||||
|
using namespace XCEngine::Resources;
|
||||||
|
using namespace XCEngine::RHI;
|
||||||
|
using namespace XCEngine::RHI::Integration;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kD3D12Screenshot = "textured_quad_scene_d3d12.ppm";
|
||||||
|
constexpr const char* kOpenGLScreenshot = "textured_quad_scene_opengl.ppm";
|
||||||
|
|
||||||
|
Mesh* CreateQuadMesh() {
|
||||||
|
auto* mesh = new Mesh();
|
||||||
|
IResource::ConstructParams params = {};
|
||||||
|
params.name = "TexturedQuad";
|
||||||
|
params.path = "Tests/Rendering/TexturedQuad.mesh";
|
||||||
|
params.guid = ResourceGUID::Generate(params.path);
|
||||||
|
mesh->Initialize(params);
|
||||||
|
|
||||||
|
StaticMeshVertex vertices[4] = {};
|
||||||
|
vertices[0].position = Vector3(-1.0f, -1.0f, 0.0f);
|
||||||
|
vertices[0].uv0 = Vector2(0.0f, 1.0f);
|
||||||
|
vertices[1].position = Vector3(-1.0f, 1.0f, 0.0f);
|
||||||
|
vertices[1].uv0 = Vector2(0.0f, 0.0f);
|
||||||
|
vertices[2].position = Vector3(1.0f, -1.0f, 0.0f);
|
||||||
|
vertices[2].uv0 = Vector2(1.0f, 1.0f);
|
||||||
|
vertices[3].position = Vector3(1.0f, 1.0f, 0.0f);
|
||||||
|
vertices[3].uv0 = Vector2(1.0f, 0.0f);
|
||||||
|
|
||||||
|
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
|
||||||
|
mesh->SetVertexData(
|
||||||
|
vertices,
|
||||||
|
sizeof(vertices),
|
||||||
|
4,
|
||||||
|
sizeof(StaticMeshVertex),
|
||||||
|
VertexAttribute::Position | VertexAttribute::UV0);
|
||||||
|
mesh->SetIndexData(indices, sizeof(indices), 6, true);
|
||||||
|
|
||||||
|
MeshSection section = {};
|
||||||
|
section.baseVertex = 0;
|
||||||
|
section.vertexCount = 4;
|
||||||
|
section.startIndex = 0;
|
||||||
|
section.indexCount = 6;
|
||||||
|
section.materialID = 0;
|
||||||
|
mesh->AddSection(section);
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture* CreateCheckerTexture() {
|
||||||
|
auto* texture = new Texture();
|
||||||
|
IResource::ConstructParams params = {};
|
||||||
|
params.name = "Checker";
|
||||||
|
params.path = "Tests/Rendering/Checker.texture";
|
||||||
|
params.guid = ResourceGUID::Generate(params.path);
|
||||||
|
texture->Initialize(params);
|
||||||
|
|
||||||
|
const unsigned char pixels[16] = {
|
||||||
|
255, 32, 32, 255,
|
||||||
|
32, 255, 32, 255,
|
||||||
|
32, 32, 255, 255,
|
||||||
|
255, 255, 32, 255
|
||||||
|
};
|
||||||
|
texture->Create(
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
XCEngine::Resources::TextureType::Texture2D,
|
||||||
|
XCEngine::Resources::TextureFormat::RGBA8_UNORM,
|
||||||
|
pixels,
|
||||||
|
sizeof(pixels));
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material* CreateMaterial(Texture* texture) {
|
||||||
|
auto* material = new Material();
|
||||||
|
IResource::ConstructParams params = {};
|
||||||
|
params.name = "QuadMaterial";
|
||||||
|
params.path = "Tests/Rendering/Quad.material";
|
||||||
|
params.guid = ResourceGUID::Generate(params.path);
|
||||||
|
material->Initialize(params);
|
||||||
|
material->SetTexture("_BaseColorTexture", ResourceHandle<Texture>(texture));
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetScreenshotFilename(RHIType backendType) {
|
||||||
|
return backendType == RHIType::D3D12 ? kD3D12Screenshot : kOpenGLScreenshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetComparisonThreshold(RHIType backendType) {
|
||||||
|
return backendType == RHIType::OpenGL ? 5 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TexturedQuadSceneTest : public RHIIntegrationFixture {
|
||||||
|
protected:
|
||||||
|
void SetUp() override;
|
||||||
|
void TearDown() override;
|
||||||
|
void RenderFrame() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
RHIResourceView* GetCurrentBackBufferView();
|
||||||
|
|
||||||
|
std::unique_ptr<Scene> mScene;
|
||||||
|
std::unique_ptr<SceneRenderer> mSceneRenderer;
|
||||||
|
std::vector<RHIResourceView*> mBackBufferViews;
|
||||||
|
Mesh* mMesh = nullptr;
|
||||||
|
Material* mMaterial = nullptr;
|
||||||
|
Texture* mTexture = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TexturedQuadSceneTest::SetUp() {
|
||||||
|
RHIIntegrationFixture::SetUp();
|
||||||
|
|
||||||
|
mSceneRenderer = std::make_unique<SceneRenderer>();
|
||||||
|
mScene = std::make_unique<Scene>("TexturedQuadScene");
|
||||||
|
|
||||||
|
mMesh = CreateQuadMesh();
|
||||||
|
mTexture = CreateCheckerTexture();
|
||||||
|
mMaterial = CreateMaterial(mTexture);
|
||||||
|
|
||||||
|
GameObject* cameraObject = mScene->CreateGameObject("MainCamera");
|
||||||
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
||||||
|
camera->SetPrimary(true);
|
||||||
|
camera->SetClearColor(XCEngine::Math::Color(0.08f, 0.08f, 0.10f, 1.0f));
|
||||||
|
|
||||||
|
GameObject* quadObject = mScene->CreateGameObject("Quad");
|
||||||
|
quadObject->GetTransform()->SetLocalPosition(Vector3(0.0f, 0.0f, 3.0f));
|
||||||
|
auto* meshFilter = quadObject->AddComponent<MeshFilterComponent>();
|
||||||
|
auto* meshRenderer = quadObject->AddComponent<MeshRendererComponent>();
|
||||||
|
meshFilter->SetMesh(ResourceHandle<Mesh>(mMesh));
|
||||||
|
meshRenderer->SetMaterial(0, ResourceHandle<Material>(mMaterial));
|
||||||
|
|
||||||
|
mBackBufferViews.resize(2, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TexturedQuadSceneTest::TearDown() {
|
||||||
|
mSceneRenderer.reset();
|
||||||
|
|
||||||
|
for (RHIResourceView*& backBufferView : mBackBufferViews) {
|
||||||
|
if (backBufferView != nullptr) {
|
||||||
|
backBufferView->Shutdown();
|
||||||
|
delete backBufferView;
|
||||||
|
backBufferView = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mBackBufferViews.clear();
|
||||||
|
|
||||||
|
mScene.reset();
|
||||||
|
|
||||||
|
delete mMaterial;
|
||||||
|
mMaterial = nullptr;
|
||||||
|
delete mMesh;
|
||||||
|
mMesh = nullptr;
|
||||||
|
delete mTexture;
|
||||||
|
mTexture = nullptr;
|
||||||
|
|
||||||
|
RHIIntegrationFixture::TearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIResourceView* TexturedQuadSceneTest::GetCurrentBackBufferView() {
|
||||||
|
const int backBufferIndex = GetCurrentBackBufferIndex();
|
||||||
|
if (backBufferIndex < 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static_cast<size_t>(backBufferIndex) >= mBackBufferViews.size()) {
|
||||||
|
mBackBufferViews.resize(static_cast<size_t>(backBufferIndex) + 1, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mBackBufferViews[backBufferIndex] == nullptr) {
|
||||||
|
ResourceViewDesc viewDesc = {};
|
||||||
|
viewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
||||||
|
viewDesc.dimension = ResourceViewDimension::Texture2D;
|
||||||
|
viewDesc.mipLevel = 0;
|
||||||
|
mBackBufferViews[backBufferIndex] = GetDevice()->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mBackBufferViews[backBufferIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
void TexturedQuadSceneTest::RenderFrame() {
|
||||||
|
ASSERT_NE(mScene, nullptr);
|
||||||
|
ASSERT_NE(mSceneRenderer, nullptr);
|
||||||
|
|
||||||
|
RHICommandList* commandList = GetCommandList();
|
||||||
|
ASSERT_NE(commandList, nullptr);
|
||||||
|
|
||||||
|
commandList->Reset();
|
||||||
|
|
||||||
|
RenderSurface surface(1280, 720);
|
||||||
|
surface.SetColorAttachment(GetCurrentBackBufferView());
|
||||||
|
|
||||||
|
RenderContext renderContext = {};
|
||||||
|
renderContext.device = GetDevice();
|
||||||
|
renderContext.commandList = commandList;
|
||||||
|
renderContext.commandQueue = GetCommandQueue();
|
||||||
|
renderContext.backendType = GetBackendType();
|
||||||
|
|
||||||
|
ASSERT_TRUE(mSceneRenderer->Render(*mScene, nullptr, renderContext, surface));
|
||||||
|
|
||||||
|
commandList->Close();
|
||||||
|
void* commandLists[] = { commandList };
|
||||||
|
GetCommandQueue()->ExecuteCommandLists(1, commandLists);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(TexturedQuadSceneTest, RenderTexturedQuadScene) {
|
||||||
|
RHICommandQueue* commandQueue = GetCommandQueue();
|
||||||
|
RHISwapChain* swapChain = GetSwapChain();
|
||||||
|
const int targetFrameCount = 30;
|
||||||
|
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
|
||||||
|
const int comparisonThreshold = GetComparisonThreshold(GetBackendType());
|
||||||
|
|
||||||
|
for (int frameCount = 0; frameCount <= targetFrameCount; ++frameCount) {
|
||||||
|
if (frameCount > 0) {
|
||||||
|
commandQueue->WaitForPreviousFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginRender();
|
||||||
|
RenderFrame();
|
||||||
|
|
||||||
|
if (frameCount >= targetFrameCount) {
|
||||||
|
commandQueue->WaitForIdle();
|
||||||
|
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
|
||||||
|
ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast<float>(comparisonThreshold)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
swapChain->Present(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(D3D12, TexturedQuadSceneTest, ::testing::Values(RHIType::D3D12));
|
||||||
|
INSTANTIATE_TEST_SUITE_P(OpenGL, TexturedQuadSceneTest, ::testing::Values(RHIType::OpenGL));
|
||||||
|
|
||||||
|
GTEST_API_ int main(int argc, char** argv) {
|
||||||
|
Logger::Get().Initialize();
|
||||||
|
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
|
||||||
|
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
||||||
|
|
||||||
|
testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
28
tests/Rendering/unit/CMakeLists.txt
Normal file
28
tests/Rendering/unit/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
project(XCEngine_RenderingUnitTests)
|
||||||
|
|
||||||
|
set(RENDERING_UNIT_TEST_SOURCES
|
||||||
|
test_render_scene_extractor.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(rendering_unit_tests ${RENDERING_UNIT_TEST_SOURCES})
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
set_target_properties(rendering_unit_tests PROPERTIES
|
||||||
|
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(rendering_unit_tests PRIVATE
|
||||||
|
XCEngine
|
||||||
|
GTest::gtest
|
||||||
|
GTest::gtest_main
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(rendering_unit_tests PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/engine/include
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(rendering_unit_tests)
|
||||||
101
tests/Rendering/unit/test_render_scene_extractor.cpp
Normal file
101
tests/Rendering/unit/test_render_scene_extractor.cpp
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
|
#include <XCEngine/Components/GameObject.h>
|
||||||
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||||
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||||
|
#include <XCEngine/Core/Asset/IResource.h>
|
||||||
|
#include <XCEngine/Core/Math/Vector3.h>
|
||||||
|
#include <XCEngine/Rendering/RenderSceneExtractor.h>
|
||||||
|
#include <XCEngine/Scene/Scene.h>
|
||||||
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||||
|
|
||||||
|
using namespace XCEngine::Components;
|
||||||
|
using namespace XCEngine::Math;
|
||||||
|
using namespace XCEngine::Rendering;
|
||||||
|
using namespace XCEngine::Resources;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Mesh* CreateTestMesh(const char* path) {
|
||||||
|
auto* mesh = new Mesh();
|
||||||
|
IResource::ConstructParams params = {};
|
||||||
|
params.name = "TestMesh";
|
||||||
|
params.path = path;
|
||||||
|
params.guid = ResourceGUID::Generate(path);
|
||||||
|
mesh->Initialize(params);
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RenderSceneExtractor_Test, SelectsHighestDepthPrimaryCameraAndVisibleObjects) {
|
||||||
|
Scene scene("RenderScene");
|
||||||
|
|
||||||
|
GameObject* cameraObjectA = scene.CreateGameObject("CameraA");
|
||||||
|
auto* cameraA = cameraObjectA->AddComponent<CameraComponent>();
|
||||||
|
cameraA->SetPrimary(true);
|
||||||
|
cameraA->SetDepth(0.0f);
|
||||||
|
|
||||||
|
GameObject* cameraObjectB = scene.CreateGameObject("CameraB");
|
||||||
|
auto* cameraB = cameraObjectB->AddComponent<CameraComponent>();
|
||||||
|
cameraB->SetPrimary(true);
|
||||||
|
cameraB->SetDepth(5.0f);
|
||||||
|
cameraObjectB->GetTransform()->SetLocalPosition(Vector3(2.0f, 3.0f, 4.0f));
|
||||||
|
|
||||||
|
GameObject* visibleObject = scene.CreateGameObject("VisibleQuad");
|
||||||
|
visibleObject->GetTransform()->SetLocalPosition(Vector3(1.0f, 2.0f, 3.0f));
|
||||||
|
auto* meshFilter = visibleObject->AddComponent<MeshFilterComponent>();
|
||||||
|
visibleObject->AddComponent<MeshRendererComponent>();
|
||||||
|
Mesh* visibleMesh = CreateTestMesh("Meshes/visible.mesh");
|
||||||
|
meshFilter->SetMesh(visibleMesh);
|
||||||
|
|
||||||
|
GameObject* hiddenObject = scene.CreateGameObject("HiddenQuad");
|
||||||
|
hiddenObject->SetActive(false);
|
||||||
|
auto* hiddenMeshFilter = hiddenObject->AddComponent<MeshFilterComponent>();
|
||||||
|
hiddenObject->AddComponent<MeshRendererComponent>();
|
||||||
|
Mesh* hiddenMesh = CreateTestMesh("Meshes/hidden.mesh");
|
||||||
|
hiddenMeshFilter->SetMesh(hiddenMesh);
|
||||||
|
|
||||||
|
RenderSceneExtractor extractor;
|
||||||
|
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 1280, 720);
|
||||||
|
|
||||||
|
ASSERT_TRUE(sceneData.HasCamera());
|
||||||
|
EXPECT_EQ(sceneData.camera, cameraB);
|
||||||
|
EXPECT_EQ(sceneData.cameraData.viewportWidth, 1280u);
|
||||||
|
EXPECT_EQ(sceneData.cameraData.viewportHeight, 720u);
|
||||||
|
EXPECT_EQ(sceneData.cameraData.worldPosition, Vector3(2.0f, 3.0f, 4.0f));
|
||||||
|
|
||||||
|
ASSERT_EQ(sceneData.visibleObjects.size(), 1u);
|
||||||
|
EXPECT_EQ(sceneData.visibleObjects[0].gameObject, visibleObject);
|
||||||
|
EXPECT_EQ(sceneData.visibleObjects[0].mesh, visibleMesh);
|
||||||
|
EXPECT_EQ(sceneData.visibleObjects[0].localToWorld.GetTranslation(), Vector3(1.0f, 2.0f, 3.0f));
|
||||||
|
|
||||||
|
meshFilter->ClearMesh();
|
||||||
|
hiddenMeshFilter->ClearMesh();
|
||||||
|
delete visibleMesh;
|
||||||
|
delete hiddenMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RenderSceneExtractor_Test, OverrideCameraTakesPriority) {
|
||||||
|
Scene scene("OverrideScene");
|
||||||
|
|
||||||
|
GameObject* primaryObject = scene.CreateGameObject("PrimaryCamera");
|
||||||
|
auto* primaryCamera = primaryObject->AddComponent<CameraComponent>();
|
||||||
|
primaryCamera->SetPrimary(true);
|
||||||
|
primaryCamera->SetDepth(10.0f);
|
||||||
|
|
||||||
|
GameObject* overrideObject = scene.CreateGameObject("OverrideCamera");
|
||||||
|
auto* overrideCamera = overrideObject->AddComponent<CameraComponent>();
|
||||||
|
overrideCamera->SetPrimary(false);
|
||||||
|
overrideCamera->SetDepth(-1.0f);
|
||||||
|
|
||||||
|
RenderSceneExtractor extractor;
|
||||||
|
const RenderSceneData sceneData = extractor.Extract(scene, overrideCamera, 640, 480);
|
||||||
|
|
||||||
|
ASSERT_TRUE(sceneData.HasCamera());
|
||||||
|
EXPECT_EQ(sceneData.camera, overrideCamera);
|
||||||
|
EXPECT_NE(sceneData.camera, primaryCamera);
|
||||||
|
EXPECT_EQ(sceneData.cameraData.viewportWidth, 640u);
|
||||||
|
EXPECT_EQ(sceneData.cameraData.viewportHeight, 480u);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
Reference in New Issue
Block a user