feat: draw meshes with instanced multi-draw indirect
This commit is contained in:
@@ -64,6 +64,7 @@ set(SRC_FILES
|
|||||||
"src/Graphics/GfxDevice.cpp"
|
"src/Graphics/GfxDevice.cpp"
|
||||||
"src/Graphics/Caches/ImageCache.cpp"
|
"src/Graphics/Caches/ImageCache.cpp"
|
||||||
"src/Graphics/ImageLoader.cpp"
|
"src/Graphics/ImageLoader.cpp"
|
||||||
|
"src/Graphics/IndirectDrawList.cpp"
|
||||||
"src/Graphics/ImmediateExecuter.cpp"
|
"src/Graphics/ImmediateExecuter.cpp"
|
||||||
"src/Graphics/Init.cpp"
|
"src/Graphics/Init.cpp"
|
||||||
"src/Graphics/Caches/MaterialCache.cpp"
|
"src/Graphics/Caches/MaterialCache.cpp"
|
||||||
@@ -232,6 +233,7 @@ if (SPIRV_VAL_EXECUTABLE)
|
|||||||
file(RELATIVE_PATH shader_relative "${ASSETS_SRC_DIR}" "${shader_source}")
|
file(RELATIVE_PATH shader_relative "${ASSETS_SRC_DIR}" "${shader_source}")
|
||||||
list(APPEND VALIDATE_SHADER_COMMANDS
|
list(APPEND VALIDATE_SHADER_COMMANDS
|
||||||
COMMAND "${SPIRV_VAL_EXECUTABLE}"
|
COMMAND "${SPIRV_VAL_EXECUTABLE}"
|
||||||
|
--target-env vulkan1.3 --scalar-block-layout
|
||||||
"${ASSETS_RUNTIME_DIR}/${shader_relative}.spv")
|
"${ASSETS_RUNTIME_DIR}/${shader_relative}.spv")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef INSTANCE_DATA_GLSL
|
||||||
|
#define INSTANCE_DATA_GLSL
|
||||||
|
|
||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
#extension GL_EXT_scalar_block_layout : require
|
||||||
|
|
||||||
|
#include "vertex.glsl"
|
||||||
|
|
||||||
|
// Must match GPUInstanceData in IndirectDrawList.h
|
||||||
|
struct InstanceData {
|
||||||
|
mat4 transform;
|
||||||
|
VertexBuffer vertexBuffer;
|
||||||
|
uint materialID;
|
||||||
|
uint padding;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (buffer_reference, scalar) readonly buffer InstanceBuffer {
|
||||||
|
InstanceData instances[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INSTANCE_DATA_GLSL
|
||||||
@@ -12,6 +12,7 @@ layout (location = 0) in vec3 inPos;
|
|||||||
layout (location = 1) in vec2 inUV;
|
layout (location = 1) in vec2 inUV;
|
||||||
layout (location = 2) in vec3 inNormal;
|
layout (location = 2) in vec3 inNormal;
|
||||||
layout (location = 3) in vec4 inTangent;
|
layout (location = 3) in vec4 inTangent;
|
||||||
|
layout (location = 4) flat in uint inMaterialID;
|
||||||
|
|
||||||
layout (location = 0) out vec4 outFragColor;
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ float calculateShadow(Light light, vec3 fragPos, vec3 geometryNormal)
|
|||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
MaterialData material = pcs.sceneData.materials.data[pcs.materialID];
|
MaterialData material = pcs.sceneData.materials.data[inMaterialID];
|
||||||
uint samplerID = material.textureFilteringMode;
|
uint samplerID = material.textureFilteringMode;
|
||||||
|
|
||||||
vec4 diffuse = sampleTexture2D(material.diffuseTex, samplerID, inUV) * material.baseColor;
|
vec4 diffuse = sampleTexture2D(material.diffuseTex, samplerID, inUV) * material.baseColor;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ layout (location = 0) out vec3 outPos;
|
|||||||
layout (location = 1) out vec2 outUV;
|
layout (location = 1) out vec2 outUV;
|
||||||
layout (location = 2) out vec3 outNormal;
|
layout (location = 2) out vec3 outNormal;
|
||||||
layout (location = 3) out vec4 outTangent;
|
layout (location = 3) out vec4 outTangent;
|
||||||
|
layout (location = 4) flat out uint outMaterialID;
|
||||||
|
|
||||||
// The depth pre-pass reuses this shader, positions have to match bit for bit
|
// The depth pre-pass reuses this shader, positions have to match bit for bit
|
||||||
// so the main pass can depth test against it with LESS_OR_EQUAL
|
// so the main pass can depth test against it with LESS_OR_EQUAL
|
||||||
@@ -15,9 +16,11 @@ invariant gl_Position;
|
|||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
Vertex v = pcs.vertexBuffer.vertices[gl_VertexIndex];
|
// gl_InstanceIndex already includes the indirect command's firstInstance
|
||||||
|
InstanceData instance = pcs.instanceBuffer.instances[gl_InstanceIndex];
|
||||||
|
Vertex v = instance.vertexBuffer.vertices[gl_VertexIndex];
|
||||||
|
|
||||||
vec4 worldPos = pcs.transform * vec4(v.position, 1.0f);
|
vec4 worldPos = instance.transform * vec4(v.position, 1.0f);
|
||||||
|
|
||||||
gl_Position = pcs.sceneData.viewProj * worldPos;
|
gl_Position = pcs.sceneData.viewProj * worldPos;
|
||||||
outPos = worldPos.xyz;
|
outPos = worldPos.xyz;
|
||||||
@@ -25,9 +28,10 @@ void main()
|
|||||||
// A bit inefficient, but okay - this is needed for non-uniform scale
|
// A bit inefficient, but okay - this is needed for non-uniform scale
|
||||||
// models. See: http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix/
|
// models. See: http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix/
|
||||||
// Simpler case, when everything is uniform
|
// Simpler case, when everything is uniform
|
||||||
// outNormal = (pcs.transform * vec4(v.normal, 0.0)).xyz;
|
// outNormal = (instance.transform * vec4(v.normal, 0.0)).xyz;
|
||||||
outNormal = mat3(transpose(inverse(pcs.transform))) * v.normal;
|
outNormal = mat3(transpose(inverse(instance.transform))) * v.normal;
|
||||||
|
|
||||||
// w is the bitangent sign, the fragment shader builds the TBN from the interpolated vectors
|
// w is the bitangent sign, the fragment shader builds the TBN from the interpolated vectors
|
||||||
outTangent = vec4(mat3(pcs.transform) * v.tangent.xyz, v.tangent.w);
|
outTangent = vec4(mat3(instance.transform) * v.tangent.xyz, v.tangent.w);
|
||||||
|
outMaterialID = instance.materialID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,10 @@
|
|||||||
#extension GL_EXT_scalar_block_layout: require
|
#extension GL_EXT_scalar_block_layout: require
|
||||||
|
|
||||||
#include "scene_data.glsl"
|
#include "scene_data.glsl"
|
||||||
#include "vertex.glsl"
|
#include "instance_data.glsl"
|
||||||
|
|
||||||
layout (push_constant, scalar) uniform constants
|
layout (push_constant, scalar) uniform constants
|
||||||
{
|
{
|
||||||
mat4 transform;
|
|
||||||
SceneDataBuffer sceneData;
|
SceneDataBuffer sceneData;
|
||||||
VertexBuffer vertexBuffer;
|
InstanceBuffer instanceBuffer;
|
||||||
uint materialID;
|
|
||||||
uint padding;
|
|
||||||
} pcs;
|
} pcs;
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,18 @@
|
|||||||
#extension GL_EXT_buffer_reference : require
|
#extension GL_EXT_buffer_reference : require
|
||||||
#extension GL_EXT_scalar_block_layout : require
|
#extension GL_EXT_scalar_block_layout : require
|
||||||
|
|
||||||
#include "vertex.glsl"
|
#include "instance_data.glsl"
|
||||||
|
|
||||||
layout (push_constant, scalar) uniform constants
|
layout (push_constant, scalar) uniform constants
|
||||||
{
|
{
|
||||||
mat4 modelToLightClip;
|
mat4 lightViewProj;
|
||||||
VertexBuffer vertexBuffer;
|
InstanceBuffer instanceBuffer;
|
||||||
} pcs;
|
} pcs;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
Vertex v = pcs.vertexBuffer.vertices[gl_VertexIndex];
|
InstanceData instance = pcs.instanceBuffer.instances[gl_InstanceIndex];
|
||||||
|
Vertex v = instance.vertexBuffer.vertices[gl_VertexIndex];
|
||||||
|
|
||||||
gl_Position = pcs.modelToLightClip * vec4(v.position, 1.0f);
|
gl_Position = pcs.lightViewProj * instance.transform * vec4(v.position, 1.0f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,24 @@ public:
|
|||||||
|
|
||||||
MeshID addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh);
|
MeshID addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh);
|
||||||
const GPUMesh& getMesh(MeshID id) const;
|
const GPUMesh& getMesh(MeshID id) const;
|
||||||
|
[[nodiscard]] const std::vector<GPUMesh>& getMeshes() const { return meshes; }
|
||||||
const CPUMesh& getCPUMesh(MeshID id) const;
|
const CPUMesh& getCPUMesh(MeshID id) const;
|
||||||
[[nodiscard]] const std::string& getMeshKey(MeshID id) const;
|
[[nodiscard]] const std::string& getMeshKey(MeshID id) const;
|
||||||
[[nodiscard]] std::optional<MeshID> findMeshByKey(std::string_view key) const;
|
[[nodiscard]] std::optional<MeshID> findMeshByKey(std::string_view key) const;
|
||||||
|
|
||||||
|
[[nodiscard]] const GPUBuffer& getIndexBuffer() const { return indexBuffer; }
|
||||||
|
[[nodiscard]] std::uint32_t getIndexCount() const { return indexCount; }
|
||||||
|
[[nodiscard]] std::uint32_t getIndexCapacity() const { return indexCapacity; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh) const;
|
void uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh);
|
||||||
|
void ensureIndexCapacity(GfxDevice& gfxDevice, std::uint32_t requiredIndices);
|
||||||
|
|
||||||
|
static constexpr std::uint32_t InitialIndexCapacity = 1u << 20;
|
||||||
|
|
||||||
|
GPUBuffer indexBuffer;
|
||||||
|
std::uint32_t indexCount{0};
|
||||||
|
std::uint32_t indexCapacity{0};
|
||||||
|
|
||||||
std::vector<GPUMesh> meshes;
|
std::vector<GPUMesh> meshes;
|
||||||
std::vector<CPUMesh> cpuMeshes;
|
std::vector<CPUMesh> cpuMeshes;
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#ifndef INDIRECTDRAWLIST_H
|
||||||
|
#define INDIRECTDRAWLIST_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/FrameConfig.h>
|
||||||
|
#include <destrum/Graphics/Frustum.h>
|
||||||
|
#include <destrum/Graphics/MeshDrawCommand.h>
|
||||||
|
#include <destrum/Graphics/Resources/Buffer.h>
|
||||||
|
|
||||||
|
class GfxDevice;
|
||||||
|
struct GPUMesh;
|
||||||
|
|
||||||
|
// Must match InstanceData in instance_data.glsl
|
||||||
|
struct GPUInstanceData {
|
||||||
|
glm::mat4 transform;
|
||||||
|
VkDeviceAddress vertexBuffer;
|
||||||
|
std::uint32_t materialId;
|
||||||
|
std::uint32_t padding;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(GPUInstanceData) == 80);
|
||||||
|
|
||||||
|
class IndirectDrawList {
|
||||||
|
public:
|
||||||
|
struct View {
|
||||||
|
std::uint32_t firstCommand{0};
|
||||||
|
std::uint32_t commandCount{0};
|
||||||
|
std::uint32_t instanceCount{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Filter {
|
||||||
|
All,
|
||||||
|
ShadowCasters,
|
||||||
|
};
|
||||||
|
|
||||||
|
void cleanup(GfxDevice& gfxDevice);
|
||||||
|
|
||||||
|
void begin(std::uint32_t frameIndex);
|
||||||
|
|
||||||
|
View addView(
|
||||||
|
const std::vector<GPUMesh>& meshes,
|
||||||
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
|
const std::vector<std::size_t>& sortedDrawCommands,
|
||||||
|
const Frustum& frustum,
|
||||||
|
Filter filter);
|
||||||
|
|
||||||
|
void upload(GfxDevice& gfxDevice);
|
||||||
|
|
||||||
|
void draw(VkCommandBuffer cmd, const GPUBuffer& indexBuffer, const View& view) const;
|
||||||
|
|
||||||
|
[[nodiscard]] VkDeviceAddress getInstanceBufferAddress() const;
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<GPUInstanceData>& getInstances() const { return instances; }
|
||||||
|
[[nodiscard]] const std::vector<VkDrawIndexedIndirectCommand>& getCommands() const { return commands; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct PerFrameBuffers {
|
||||||
|
GPUBuffer instances;
|
||||||
|
GPUBuffer commands;
|
||||||
|
std::size_t instanceCapacity{0};
|
||||||
|
std::size_t commandCapacity{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr std::size_t InitialInstanceCapacity = 1024;
|
||||||
|
static constexpr std::size_t InitialCommandCapacity = 256;
|
||||||
|
|
||||||
|
std::array<PerFrameBuffers, FRAMES_IN_FLIGHT> frames{};
|
||||||
|
std::uint32_t currentFrame{0};
|
||||||
|
|
||||||
|
std::vector<GPUInstanceData> instances;
|
||||||
|
std::vector<VkDrawIndexedIndirectCommand> commands;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INDIRECTDRAWLIST_H
|
||||||
@@ -27,6 +27,8 @@ public:
|
|||||||
VkPhysicalDeviceFeatures device{
|
VkPhysicalDeviceFeatures device{
|
||||||
.imageCubeArray = VK_TRUE,
|
.imageCubeArray = VK_TRUE,
|
||||||
.geometryShader = VK_TRUE,
|
.geometryShader = VK_TRUE,
|
||||||
|
.multiDrawIndirect = VK_TRUE,
|
||||||
|
.drawIndirectFirstInstance = VK_TRUE,
|
||||||
.depthClamp = VK_TRUE,
|
.depthClamp = VK_TRUE,
|
||||||
.fillModeNonSolid = VK_TRUE,
|
.fillModeNonSolid = VK_TRUE,
|
||||||
.samplerAnisotropy = VK_TRUE,
|
.samplerAnisotropy = VK_TRUE,
|
||||||
|
|||||||
@@ -9,9 +9,8 @@
|
|||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
#include <destrum/Graphics/Pipeline.h>
|
#include <destrum/Graphics/Pipeline.h>
|
||||||
#include <destrum/Graphics/Camera.h>
|
#include <destrum/Graphics/IndirectDrawList.h>
|
||||||
#include <destrum/Graphics/Resources/Buffer.h>
|
#include <destrum/Graphics/Resources/Buffer.h>
|
||||||
#include <destrum/Graphics/MeshDrawCommand.h>
|
|
||||||
|
|
||||||
class GfxDevice;
|
class GfxDevice;
|
||||||
class RenderResources;
|
class RenderResources;
|
||||||
@@ -31,19 +30,17 @@ public:
|
|||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
VkExtent2D renderExtent,
|
VkExtent2D renderExtent,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const Camera& camera,
|
|
||||||
const GPUBuffer& sceneDataBuffer,
|
const GPUBuffer& sceneDataBuffer,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands);
|
const IndirectDrawList::View& view);
|
||||||
|
|
||||||
void draw(
|
void draw(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
VkExtent2D renderExtent,
|
VkExtent2D renderExtent,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const Camera& camera,
|
|
||||||
const GPUBuffer& sceneDataBuffer,
|
const GPUBuffer& sceneDataBuffer,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands);
|
const IndirectDrawList::View& view);
|
||||||
|
|
||||||
void cleanup(VkDevice device);
|
void cleanup(VkDevice device);
|
||||||
|
|
||||||
@@ -57,11 +54,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
struct PushConstants {
|
struct PushConstants {
|
||||||
glm::mat4 transform;
|
|
||||||
VkDeviceAddress sceneDataBuffer;
|
VkDeviceAddress sceneDataBuffer;
|
||||||
VkDeviceAddress vertexBuffer;
|
VkDeviceAddress instanceBuffer;
|
||||||
std::uint32_t materialId;
|
|
||||||
std::uint32_t padding;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void drawMeshes(
|
void drawMeshes(
|
||||||
@@ -69,10 +63,9 @@ private:
|
|||||||
const Pipeline& pipeline,
|
const Pipeline& pipeline,
|
||||||
VkExtent2D renderExtent,
|
VkExtent2D renderExtent,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const Camera& camera,
|
|
||||||
const GPUBuffer& sceneDataBuffer,
|
const GPUBuffer& sceneDataBuffer,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands);
|
const IndirectDrawList::View& view);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include <destrum/Graphics/Light.h>
|
#include <destrum/Graphics/Light.h>
|
||||||
#include <destrum/Graphics/Pipeline.h>
|
#include <destrum/Graphics/Pipeline.h>
|
||||||
#include <destrum/Graphics/ids.h>
|
#include <destrum/Graphics/ids.h>
|
||||||
#include <destrum/Graphics/MeshDrawCommand.h>
|
#include <destrum/Graphics/IndirectDrawList.h>
|
||||||
|
|
||||||
class GfxDevice;
|
class GfxDevice;
|
||||||
struct GPUImage;
|
struct GPUImage;
|
||||||
@@ -32,15 +32,15 @@ public:
|
|||||||
void drawDirectionalShadow(
|
void drawDirectionalShadow(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands,
|
const IndirectDrawList::View& view,
|
||||||
const glm::mat4& lightViewProj);
|
const glm::mat4& lightViewProj);
|
||||||
|
|
||||||
void drawSpotShadows(
|
void drawSpotShadows(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands,
|
const std::vector<IndirectDrawList::View>& views,
|
||||||
const std::vector<glm::mat4>& lightViewProjs);
|
const std::vector<glm::mat4>& lightViewProjs);
|
||||||
|
|
||||||
[[nodiscard]] glm::mat4 calculateDirectionalViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const;
|
[[nodiscard]] glm::mat4 calculateDirectionalViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const;
|
||||||
@@ -57,8 +57,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
struct PushConstants
|
struct PushConstants
|
||||||
{
|
{
|
||||||
glm::mat4 modelToLightClip;
|
glm::mat4 lightViewProj;
|
||||||
VkDeviceAddress vertexBuffer;
|
VkDeviceAddress instanceBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
void beginDepthRendering(VkCommandBuffer cmd, const RenderResources& resources, const GPUImage& depthImage);
|
void beginDepthRendering(VkCommandBuffer cmd, const RenderResources& resources, const GPUImage& depthImage);
|
||||||
@@ -67,8 +67,8 @@ private:
|
|||||||
void drawShadowCasters(
|
void drawShadowCasters(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands,
|
const IndirectDrawList::View& view,
|
||||||
const glm::mat4& lightViewProj);
|
const glm::mat4& lightViewProj);
|
||||||
|
|
||||||
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
VkPipelineLayout m_pipelineLayout{VK_NULL_HANDLE};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <glm/vec3.hpp>
|
#include <glm/vec3.hpp>
|
||||||
|
|
||||||
#include <destrum/Graphics/Camera.h>
|
#include <destrum/Graphics/Camera.h>
|
||||||
|
#include <destrum/Graphics/IndirectDrawList.h>
|
||||||
#include <destrum/Graphics/Light.h>
|
#include <destrum/Graphics/Light.h>
|
||||||
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
|
#include <destrum/Graphics/Pipelines/LightCullingPipeline.h>
|
||||||
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
|
#include <destrum/Graphics/Pipelines/LineRenderingPass.h>
|
||||||
@@ -29,6 +30,15 @@
|
|||||||
class GameRenderer
|
class GameRenderer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
struct DrawStats
|
||||||
|
{
|
||||||
|
std::uint32_t meshDraws{0};
|
||||||
|
std::uint32_t indirectCommands{0};
|
||||||
|
std::uint32_t instances{0};
|
||||||
|
std::uint32_t mainViewCommands{0};
|
||||||
|
std::uint32_t mainViewInstances{0};
|
||||||
|
};
|
||||||
|
|
||||||
struct SceneData
|
struct SceneData
|
||||||
{
|
{
|
||||||
const Camera& camera;
|
const Camera& camera;
|
||||||
@@ -106,6 +116,8 @@ public:
|
|||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const DrawStats& getDrawStats() const { return lastDrawStats; }
|
||||||
|
|
||||||
void draw2D(
|
void draw2D(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
GfxDevice& gfxDevice,
|
GfxDevice& gfxDevice,
|
||||||
@@ -125,6 +137,8 @@ private:
|
|||||||
|
|
||||||
std::vector<MeshDrawCommand> meshDrawCommands;
|
std::vector<MeshDrawCommand> meshDrawCommands;
|
||||||
std::vector<std::size_t> sortedMeshDrawCommands;
|
std::vector<std::size_t> sortedMeshDrawCommands;
|
||||||
|
IndirectDrawList drawList;
|
||||||
|
DrawStats lastDrawStats;
|
||||||
|
|
||||||
VkFormat drawImageFormat{VK_FORMAT_R16G16B16A16_SFLOAT};
|
VkFormat drawImageFormat{VK_FORMAT_R16G16B16A16_SFLOAT};
|
||||||
VkFormat depthImageFormat{VK_FORMAT_D32_SFLOAT};
|
VkFormat depthImageFormat{VK_FORMAT_D32_SFLOAT};
|
||||||
|
|||||||
@@ -20,10 +20,11 @@
|
|||||||
|
|
||||||
struct GPUMesh {
|
struct GPUMesh {
|
||||||
GPUBuffer vertexBuffer;
|
GPUBuffer vertexBuffer;
|
||||||
GPUBuffer indexBuffer;
|
|
||||||
|
|
||||||
std::uint32_t numVertices{0};
|
std::uint32_t numVertices{0};
|
||||||
std::uint32_t numIndices{0};
|
std::uint32_t numIndices{0};
|
||||||
|
// offset into MeshCache::getIndexBuffer(), every mesh shares that one index buffer
|
||||||
|
std::uint32_t firstIndex{0};
|
||||||
|
|
||||||
// AABB
|
// AABB
|
||||||
glm::vec3 minPos;
|
glm::vec3 minPos;
|
||||||
|
|||||||
@@ -208,6 +208,13 @@ if (!isRunning) break;
|
|||||||
if (ImGui::Begin("Debug", &m_DebugWindows.stats))
|
if (ImGui::Begin("Debug", &m_DebugWindows.stats))
|
||||||
{
|
{
|
||||||
ImGui::Text("FPS: %.2f", avgFPS);
|
ImGui::Text("FPS: %.2f", avgFPS);
|
||||||
|
|
||||||
|
const auto& stats = renderer.getDrawStats();
|
||||||
|
ImGui::SeparatorText("Meshes");
|
||||||
|
ImGui::Text("Submitted meshes: %u", stats.meshDraws);
|
||||||
|
ImGui::Text("Camera: %u indirect draws, %u instances", stats.mainViewCommands, stats.mainViewInstances);
|
||||||
|
ImGui::Text("All views: %u indirect draws, %u instances", stats.indirectCommands, stats.instances);
|
||||||
|
ImGui::Text("Pending GPU deletions: %zu", gfxDevice.getPendingDeletionCount());
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
#include <destrum/Graphics/IndirectDrawList.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <volk.h>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
|
#include <destrum/Graphics/Resources/Mesh.h>
|
||||||
|
#include <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::size_t grownCapacity(std::size_t current, std::size_t initial, std::size_t required)
|
||||||
|
{
|
||||||
|
std::size_t capacity = std::max(current, initial);
|
||||||
|
while (capacity < required) {
|
||||||
|
capacity *= 2;
|
||||||
|
}
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndirectDrawList::cleanup(GfxDevice& gfxDevice)
|
||||||
|
{
|
||||||
|
for (auto& frame : frames) {
|
||||||
|
gfxDevice.destroyBuffer(frame.instances);
|
||||||
|
gfxDevice.destroyBuffer(frame.commands);
|
||||||
|
frame.instanceCapacity = 0;
|
||||||
|
frame.commandCapacity = 0;
|
||||||
|
}
|
||||||
|
instances.clear();
|
||||||
|
commands.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndirectDrawList::begin(std::uint32_t frameIndex)
|
||||||
|
{
|
||||||
|
assert(frameIndex < FRAMES_IN_FLIGHT);
|
||||||
|
currentFrame = frameIndex;
|
||||||
|
instances.clear();
|
||||||
|
commands.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
IndirectDrawList::View IndirectDrawList::addView(
|
||||||
|
const std::vector<GPUMesh>& meshes,
|
||||||
|
const std::vector<MeshDrawCommand>& drawCommands,
|
||||||
|
const std::vector<std::size_t>& sortedDrawCommands,
|
||||||
|
const Frustum& frustum,
|
||||||
|
Filter filter)
|
||||||
|
{
|
||||||
|
ZoneScopedN("IndirectDrawList::addView");
|
||||||
|
|
||||||
|
View view{
|
||||||
|
.firstCommand = static_cast<std::uint32_t>(commands.size()),
|
||||||
|
};
|
||||||
|
MeshID batchMesh = NULL_MESH_ID;
|
||||||
|
|
||||||
|
const auto addDraw = [&](const MeshDrawCommand& dc)
|
||||||
|
{
|
||||||
|
if (filter == Filter::ShadowCasters && !dc.castShadow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!edge::isInFrustum(frustum, dc.worldBoundingSphere)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(dc.meshId < meshes.size());
|
||||||
|
assert(dc.materialId != NULL_MATERIAL_ID);
|
||||||
|
const GPUMesh& mesh = meshes[dc.meshId];
|
||||||
|
|
||||||
|
// Draws arrive sorted by mesh, so every run of the same mesh becomes one
|
||||||
|
// instanced command. Each instance carries its own transform, material and
|
||||||
|
// vertex buffer (skinned meshes point at their own skinned copy).
|
||||||
|
if (dc.meshId != batchMesh) {
|
||||||
|
batchMesh = dc.meshId;
|
||||||
|
commands.push_back(VkDrawIndexedIndirectCommand{
|
||||||
|
.indexCount = mesh.numIndices,
|
||||||
|
.instanceCount = 0,
|
||||||
|
.firstIndex = mesh.firstIndex,
|
||||||
|
.vertexOffset = 0,
|
||||||
|
.firstInstance = static_cast<std::uint32_t>(instances.size()),
|
||||||
|
});
|
||||||
|
++view.commandCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
instances.push_back(GPUInstanceData{
|
||||||
|
.transform = dc.transformMatrix,
|
||||||
|
.vertexBuffer = dc.skinnedMesh != nullptr
|
||||||
|
? dc.skinnedMesh->skinnedVertexBuffer.address
|
||||||
|
: mesh.vertexBuffer.address,
|
||||||
|
.materialId = dc.materialId,
|
||||||
|
.padding = 0,
|
||||||
|
});
|
||||||
|
++commands.back().instanceCount;
|
||||||
|
++view.instanceCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!sortedDrawCommands.empty()) {
|
||||||
|
for (std::size_t drawIndex : sortedDrawCommands) {
|
||||||
|
assert(drawIndex < drawCommands.size());
|
||||||
|
addDraw(drawCommands[drawIndex]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const MeshDrawCommand& dc : drawCommands) {
|
||||||
|
addDraw(dc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndirectDrawList::upload(GfxDevice& gfxDevice)
|
||||||
|
{
|
||||||
|
ZoneScopedN("IndirectDrawList::upload");
|
||||||
|
|
||||||
|
PerFrameBuffers& frame = frames[currentFrame];
|
||||||
|
|
||||||
|
// Growing is cheap thanks to the deletion queue: the old buffer may still be
|
||||||
|
// read by this slot's previous frame, so it is retired instead of destroyed.
|
||||||
|
if (instances.size() > frame.instanceCapacity || frame.instances.buffer == VK_NULL_HANDLE) {
|
||||||
|
gfxDevice.destroyBuffer(frame.instances);
|
||||||
|
frame.instanceCapacity = grownCapacity(frame.instanceCapacity, InitialInstanceCapacity, instances.size());
|
||||||
|
frame.instances = gfxDevice.createBuffer(
|
||||||
|
frame.instanceCapacity * sizeof(GPUInstanceData),
|
||||||
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||||||
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), frame.instances.buffer, "mesh instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commands.size() > frame.commandCapacity || frame.commands.buffer == VK_NULL_HANDLE) {
|
||||||
|
gfxDevice.destroyBuffer(frame.commands);
|
||||||
|
frame.commandCapacity = grownCapacity(frame.commandCapacity, InitialCommandCapacity, commands.size());
|
||||||
|
frame.commands = gfxDevice.createBuffer(
|
||||||
|
frame.commandCapacity * sizeof(VkDrawIndexedIndirectCommand),
|
||||||
|
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
|
||||||
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), frame.commands.buffer, "mesh indirect commands");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each frame in flight has its own buffers, so nothing on the GPU is reading
|
||||||
|
// these while we write. vkQueueSubmit makes the host writes visible to the GPU.
|
||||||
|
if (!instances.empty()) {
|
||||||
|
std::memcpy(frame.instances.info.pMappedData, instances.data(), instances.size() * sizeof(GPUInstanceData));
|
||||||
|
gfxDevice.getMemoryManager().flushAllocation(frame.instances, 0, instances.size() * sizeof(GPUInstanceData));
|
||||||
|
}
|
||||||
|
if (!commands.empty()) {
|
||||||
|
std::memcpy(frame.commands.info.pMappedData, commands.data(), commands.size() * sizeof(VkDrawIndexedIndirectCommand));
|
||||||
|
gfxDevice.getMemoryManager().flushAllocation(frame.commands, 0, commands.size() * sizeof(VkDrawIndexedIndirectCommand));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndirectDrawList::draw(VkCommandBuffer cmd, const GPUBuffer& indexBuffer, const View& view) const
|
||||||
|
{
|
||||||
|
if (view.commandCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PerFrameBuffers& frame = frames[currentFrame];
|
||||||
|
assert(frame.commands.buffer != VK_NULL_HANDLE && "IndirectDrawList::upload must run before draw");
|
||||||
|
|
||||||
|
vkCmdBindIndexBuffer(cmd, indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
|
||||||
|
vkCmdDrawIndexedIndirect(
|
||||||
|
cmd,
|
||||||
|
frame.commands.buffer,
|
||||||
|
static_cast<VkDeviceSize>(view.firstCommand) * sizeof(VkDrawIndexedIndirectCommand),
|
||||||
|
view.commandCount,
|
||||||
|
sizeof(VkDrawIndexedIndirectCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDeviceAddress IndirectDrawList::getInstanceBufferAddress() const
|
||||||
|
{
|
||||||
|
return frames[currentFrame].instances.address;
|
||||||
|
}
|
||||||
@@ -49,15 +49,73 @@ MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh)
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh) const
|
void MeshCache::ensureIndexCapacity(GfxDevice& gfxDevice, std::uint32_t requiredIndices)
|
||||||
|
{
|
||||||
|
if (requiredIndices <= indexCapacity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t newCapacity = std::max(indexCapacity, InitialIndexCapacity);
|
||||||
|
while (newCapacity < requiredIndices) {
|
||||||
|
newCapacity *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
GPUBuffer newBuffer = gfxDevice.createBuffer(
|
||||||
|
static_cast<std::size_t>(newCapacity) * sizeof(std::uint32_t),
|
||||||
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||||
|
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
||||||
|
vkutil::addDebugLabel(gfxDevice.getDevice(), newBuffer.buffer, "mesh indices");
|
||||||
|
|
||||||
|
if (indexCount > 0) {
|
||||||
|
try {
|
||||||
|
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||||
|
const VkBufferMemoryBarrier2 previousCopies{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||||
|
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||||
|
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||||
|
.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||||
|
.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT,
|
||||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.buffer = indexBuffer.buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.size = VK_WHOLE_SIZE,
|
||||||
|
};
|
||||||
|
const VkDependencyInfo dependency{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||||
|
.bufferMemoryBarrierCount = 1,
|
||||||
|
.pBufferMemoryBarriers = &previousCopies,
|
||||||
|
};
|
||||||
|
vkCmdPipelineBarrier2(cmd, &dependency);
|
||||||
|
|
||||||
|
const VkBufferCopy copy{
|
||||||
|
.srcOffset = 0,
|
||||||
|
.dstOffset = 0,
|
||||||
|
.size = static_cast<VkDeviceSize>(indexCount) * sizeof(std::uint32_t),
|
||||||
|
};
|
||||||
|
vkCmdCopyBuffer(cmd, indexBuffer.buffer, newBuffer.buffer, 1, ©);
|
||||||
|
});
|
||||||
|
} catch (...) {
|
||||||
|
gfxDevice.destroyBufferNow(newBuffer);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// frames in flight were recorded with the old buffer, the deletion queue keeps it alive for them
|
||||||
|
gfxDevice.destroyBuffer(indexBuffer);
|
||||||
|
indexBuffer = newBuffer;
|
||||||
|
indexCapacity = newCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh& gpuMesh)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// create index buffer
|
const auto numIndices = static_cast<std::uint32_t>(cpuMesh.indices.size());
|
||||||
|
ensureIndexCapacity(gfxDevice, indexCount + numIndices);
|
||||||
|
gpuMesh.firstIndex = indexCount;
|
||||||
|
|
||||||
const auto indexBufferSize = cpuMesh.indices.size() * sizeof(std::uint32_t);
|
const auto indexBufferSize = cpuMesh.indices.size() * sizeof(std::uint32_t);
|
||||||
gpuMesh.indexBuffer = gfxDevice.createBuffer(
|
const auto indexBufferOffset = static_cast<VkDeviceSize>(gpuMesh.firstIndex) * sizeof(std::uint32_t);
|
||||||
indexBufferSize,
|
|
||||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
|
||||||
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE);
|
|
||||||
|
|
||||||
// create vertex buffer
|
// create vertex buffer
|
||||||
const auto vertexBufferSize = cpuMesh.vertices.size() * sizeof(CPUMesh::Vertex);
|
const auto vertexBufferSize = cpuMesh.vertices.size() * sizeof(CPUMesh::Vertex);
|
||||||
@@ -81,6 +139,8 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
|||||||
memcpy(static_cast<char*>(vertexIndexData) + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize);
|
memcpy(static_cast<char*>(vertexIndexData) + vertexBufferSize, cpuMesh.indices.data(), indexBufferSize);
|
||||||
gfxDevice.getMemoryManager().flushAllocation(vertexIndexStaging);
|
gfxDevice.getMemoryManager().flushAllocation(vertexIndexStaging);
|
||||||
|
|
||||||
|
// Writing into the shared index buffer is safe while frames are in flight:
|
||||||
|
// this range is new, they only read the ranges of meshes added before it.
|
||||||
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
gfxDevice.immediateSubmit([&](VkCommandBuffer cmd) {
|
||||||
vkutil::bufferHostWriteToTransferReadBarrier(
|
vkutil::bufferHostWriteToTransferReadBarrier(
|
||||||
cmd,
|
cmd,
|
||||||
@@ -97,17 +157,17 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
|||||||
|
|
||||||
const auto indexCopy = VkBufferCopy{
|
const auto indexCopy = VkBufferCopy{
|
||||||
.srcOffset = vertexBufferSize,
|
.srcOffset = vertexBufferSize,
|
||||||
.dstOffset = 0,
|
.dstOffset = indexBufferOffset,
|
||||||
.size = indexBufferSize,
|
.size = indexBufferSize,
|
||||||
};
|
};
|
||||||
vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, gpuMesh.indexBuffer.buffer, 1, &indexCopy);
|
vkCmdCopyBuffer(cmd, vertexIndexStaging.buffer, indexBuffer.buffer, 1, &indexCopy);
|
||||||
|
|
||||||
const std::array<VkBufferMemoryBarrier2, 2> destinationBarriers{{
|
const std::array<VkBufferMemoryBarrier2, 2> destinationBarriers{{
|
||||||
{
|
{
|
||||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||||
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT,
|
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
.dstAccessMask = VK_ACCESS_2_SHADER_STORAGE_READ_BIT,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
@@ -119,13 +179,13 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
|||||||
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
|
||||||
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||||
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT,
|
||||||
.dstStageMask = VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT,
|
.dstStageMask = VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT | VK_PIPELINE_STAGE_2_COPY_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT,
|
.dstAccessMask = VK_ACCESS_2_INDEX_READ_BIT | VK_ACCESS_2_TRANSFER_READ_BIT,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.buffer = gpuMesh.indexBuffer.buffer,
|
.buffer = indexBuffer.buffer,
|
||||||
.offset = 0,
|
.offset = indexBufferOffset,
|
||||||
.size = VK_WHOLE_SIZE,
|
.size = indexBufferSize,
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
const VkDependencyInfo destinationDependency{
|
const VkDependencyInfo destinationDependency{
|
||||||
@@ -141,11 +201,10 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
|||||||
}
|
}
|
||||||
|
|
||||||
gfxDevice.destroyBufferNow(vertexIndexStaging);
|
gfxDevice.destroyBufferNow(vertexIndexStaging);
|
||||||
|
indexCount += numIndices;
|
||||||
|
|
||||||
const auto vtxBufferName = cpuMesh.name + " (vtx)";
|
const auto vtxBufferName = cpuMesh.name + " (vtx)";
|
||||||
const auto idxBufferName = cpuMesh.name + " (idx)";
|
|
||||||
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.vertexBuffer.buffer, vtxBufferName.c_str());
|
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.vertexBuffer.buffer, vtxBufferName.c_str());
|
||||||
vkutil::addDebugLabel(gfxDevice.getDevice(), gpuMesh.indexBuffer.buffer, idxBufferName.c_str());
|
|
||||||
|
|
||||||
if (gpuMesh.hasSkeleton) {
|
if (gpuMesh.hasSkeleton) {
|
||||||
// create skinning data buffer
|
// create skinning data buffer
|
||||||
@@ -214,7 +273,6 @@ void MeshCache::uploadMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh, GPUMesh
|
|||||||
} catch (...) {
|
} catch (...) {
|
||||||
gfxDevice.destroyBufferNow(gpuMesh.skinningDataBuffer);
|
gfxDevice.destroyBufferNow(gpuMesh.skinningDataBuffer);
|
||||||
gfxDevice.destroyBufferNow(gpuMesh.vertexBuffer);
|
gfxDevice.destroyBufferNow(gpuMesh.vertexBuffer);
|
||||||
gfxDevice.destroyBufferNow(gpuMesh.indexBuffer);
|
|
||||||
gpuMesh = {};
|
gpuMesh = {};
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -246,8 +304,11 @@ std::optional<MeshID> MeshCache::findMeshByKey(std::string_view key) const
|
|||||||
|
|
||||||
void MeshCache::cleanup(GfxDevice& gfxDevice)
|
void MeshCache::cleanup(GfxDevice& gfxDevice)
|
||||||
{
|
{
|
||||||
|
gfxDevice.destroyBuffer(indexBuffer);
|
||||||
|
indexCount = 0;
|
||||||
|
indexCapacity = 0;
|
||||||
|
|
||||||
for (auto& mesh : meshes) {
|
for (auto& mesh : meshes) {
|
||||||
gfxDevice.destroyBuffer(mesh.indexBuffer);
|
|
||||||
gfxDevice.destroyBuffer(mesh.vertexBuffer);
|
gfxDevice.destroyBuffer(mesh.vertexBuffer);
|
||||||
gfxDevice.destroyBuffer(mesh.skinningDataBuffer);
|
gfxDevice.destroyBuffer(mesh.skinningDataBuffer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <destrum/Graphics/GfxDevice.h>
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
#include <destrum/Graphics/RenderResources.h>
|
#include <destrum/Graphics/RenderResources.h>
|
||||||
#include <destrum/Graphics/Caches/MeshCache.h>
|
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||||
#include <destrum/Graphics/Frustum.h>
|
|
||||||
#include <destrum/Graphics/Util.h>
|
#include <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
#include "volk.h"
|
#include "volk.h"
|
||||||
@@ -119,10 +118,9 @@ void MeshPipeline::drawDepthPrepass(
|
|||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
VkExtent2D renderExtent,
|
VkExtent2D renderExtent,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const Camera& camera,
|
|
||||||
const GPUBuffer& sceneDataBuffer,
|
const GPUBuffer& sceneDataBuffer,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands)
|
const IndirectDrawList::View& view)
|
||||||
{
|
{
|
||||||
if (!m_depthPrepassPipeline)
|
if (!m_depthPrepassPipeline)
|
||||||
{
|
{
|
||||||
@@ -134,20 +132,18 @@ void MeshPipeline::drawDepthPrepass(
|
|||||||
*m_depthPrepassPipeline,
|
*m_depthPrepassPipeline,
|
||||||
renderExtent,
|
renderExtent,
|
||||||
resources,
|
resources,
|
||||||
camera,
|
|
||||||
sceneDataBuffer,
|
sceneDataBuffer,
|
||||||
drawCommands,
|
drawList,
|
||||||
sortedDrawCommands);
|
view);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshPipeline::draw(
|
void MeshPipeline::draw(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
VkExtent2D renderExtent,
|
VkExtent2D renderExtent,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const Camera& camera,
|
|
||||||
const GPUBuffer& sceneDataBuffer,
|
const GPUBuffer& sceneDataBuffer,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands)
|
const IndirectDrawList::View& view)
|
||||||
{
|
{
|
||||||
if (!m_pipeline)
|
if (!m_pipeline)
|
||||||
{
|
{
|
||||||
@@ -159,10 +155,9 @@ void MeshPipeline::draw(
|
|||||||
*m_pipeline,
|
*m_pipeline,
|
||||||
renderExtent,
|
renderExtent,
|
||||||
resources,
|
resources,
|
||||||
camera,
|
|
||||||
sceneDataBuffer,
|
sceneDataBuffer,
|
||||||
drawCommands,
|
drawList,
|
||||||
sortedDrawCommands);
|
view);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshPipeline::drawMeshes(
|
void MeshPipeline::drawMeshes(
|
||||||
@@ -170,13 +165,10 @@ void MeshPipeline::drawMeshes(
|
|||||||
const Pipeline& pipeline,
|
const Pipeline& pipeline,
|
||||||
VkExtent2D renderExtent,
|
VkExtent2D renderExtent,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const Camera& camera,
|
|
||||||
const GPUBuffer& sceneDataBuffer,
|
const GPUBuffer& sceneDataBuffer,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands)
|
const IndirectDrawList::View& view)
|
||||||
{
|
{
|
||||||
const MeshCache& meshCache = resources.meshes();
|
|
||||||
|
|
||||||
pipeline.bind(cmd);
|
pipeline.bind(cmd);
|
||||||
|
|
||||||
resources.bindBindlessDescSet(cmd, m_pipelineLayout);
|
resources.bindBindlessDescSet(cmd, m_pipelineLayout);
|
||||||
@@ -204,86 +196,32 @@ void MeshPipeline::drawMeshes(
|
|||||||
m_renderWireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL
|
m_renderWireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL
|
||||||
);
|
);
|
||||||
|
|
||||||
MeshID prevMeshId = NULL_MESH_ID;
|
// The same for every draw, per-draw data (transform, material, vertex buffer)
|
||||||
int actualDrawCalls = 0;
|
// lives in the instance buffer and is looked up with gl_InstanceIndex
|
||||||
|
const auto pushConstants = PushConstants{
|
||||||
const auto frustum = edge::createFrustumFromCamera(camera);
|
.sceneDataBuffer = sceneDataBuffer.address,
|
||||||
|
.instanceBuffer = drawList.getInstanceBufferAddress(),
|
||||||
const auto drawOne = [&](const MeshDrawCommand& dc)
|
|
||||||
{
|
|
||||||
if (!edge::isInFrustum(frustum, dc.worldBoundingSphere))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
++actualDrawCalls;
|
|
||||||
|
|
||||||
const auto& mesh = meshCache.getMesh(dc.meshId);
|
|
||||||
|
|
||||||
const std::string& meshKey = meshCache.getMeshKey(dc.meshId);
|
|
||||||
vkutil::cmdBeginLabelFormatted(
|
|
||||||
cmd,
|
|
||||||
vkutil::labelColors::draw,
|
|
||||||
"%s [mesh %zu, material %u%s]",
|
|
||||||
meshKey.empty() ? "Mesh" : meshKey.c_str(),
|
|
||||||
dc.meshId,
|
|
||||||
dc.materialId,
|
|
||||||
dc.skinnedMesh != nullptr ? ", skinned" : "");
|
|
||||||
|
|
||||||
if (dc.meshId != prevMeshId)
|
|
||||||
{
|
|
||||||
prevMeshId = dc.meshId;
|
|
||||||
|
|
||||||
vkCmdBindIndexBuffer(
|
|
||||||
cmd,
|
|
||||||
mesh.indexBuffer.buffer,
|
|
||||||
0,
|
|
||||||
VK_INDEX_TYPE_UINT32
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(dc.materialId != NULL_MATERIAL_ID);
|
|
||||||
|
|
||||||
const auto pushConstants = PushConstants{
|
|
||||||
.transform = dc.transformMatrix,
|
|
||||||
.sceneDataBuffer = sceneDataBuffer.address,
|
|
||||||
.vertexBuffer =
|
|
||||||
dc.skinnedMesh != nullptr
|
|
||||||
? dc.skinnedMesh->skinnedVertexBuffer.address
|
|
||||||
: mesh.vertexBuffer.address,
|
|
||||||
.materialId = dc.materialId,
|
|
||||||
.padding = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
vkCmdPushConstants(
|
|
||||||
cmd,
|
|
||||||
m_pipelineLayout,
|
|
||||||
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
||||||
0,
|
|
||||||
sizeof(PushConstants),
|
|
||||||
&pushConstants
|
|
||||||
);
|
|
||||||
|
|
||||||
vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0);
|
|
||||||
|
|
||||||
vkutil::cmdEndLabel(cmd);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!sortedDrawCommands.empty())
|
vkCmdPushConstants(
|
||||||
{
|
cmd,
|
||||||
for (std::size_t drawIndex : sortedDrawCommands)
|
m_pipelineLayout,
|
||||||
{
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
assert(drawIndex < drawCommands.size());
|
0,
|
||||||
drawOne(drawCommands[drawIndex]);
|
sizeof(PushConstants),
|
||||||
}
|
&pushConstants
|
||||||
}
|
);
|
||||||
else
|
|
||||||
{
|
vkutil::cmdBeginLabelFormatted(
|
||||||
for (const MeshDrawCommand& dc : drawCommands)
|
cmd,
|
||||||
{
|
vkutil::labelColors::draw,
|
||||||
drawOne(dc);
|
"%u indirect draws, %u instances",
|
||||||
}
|
view.commandCount,
|
||||||
}
|
view.instanceCount);
|
||||||
|
|
||||||
|
drawList.draw(cmd, resources.meshes().getIndexBuffer(), view);
|
||||||
|
|
||||||
|
vkutil::cmdEndLabel(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshPipeline::cleanup(VkDevice device)
|
void MeshPipeline::cleanup(VkDevice device)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include <destrum/Graphics/GfxDevice.h>
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
#include <destrum/Graphics/RenderResources.h>
|
#include <destrum/Graphics/RenderResources.h>
|
||||||
#include <destrum/Graphics/Caches/MeshCache.h>
|
#include <destrum/Graphics/Caches/MeshCache.h>
|
||||||
#include <destrum/Graphics/Frustum.h>
|
|
||||||
#include <destrum/Graphics/Util.h>
|
#include <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
#include "volk.h"
|
#include "volk.h"
|
||||||
@@ -206,8 +205,8 @@ float ShadowMapPipeline::getNormalOffset() const
|
|||||||
void ShadowMapPipeline::drawDirectionalShadow(
|
void ShadowMapPipeline::drawDirectionalShadow(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands,
|
const IndirectDrawList::View& view,
|
||||||
const glm::mat4& lightViewProj)
|
const glm::mat4& lightViewProj)
|
||||||
{
|
{
|
||||||
if (!m_pipeline || shadowMapId == NULL_IMAGE_ID)
|
if (!m_pipeline || shadowMapId == NULL_IMAGE_ID)
|
||||||
@@ -219,15 +218,15 @@ void ShadowMapPipeline::drawDirectionalShadow(
|
|||||||
|
|
||||||
beginDepthRendering(cmd, resources, shadowMap);
|
beginDepthRendering(cmd, resources, shadowMap);
|
||||||
setRenderRegion(cmd, VkRect2D{.offset = {}, .extent = shadowMap.getExtent2D()});
|
setRenderRegion(cmd, VkRect2D{.offset = {}, .extent = shadowMap.getExtent2D()});
|
||||||
drawShadowCasters(cmd, resources, drawCommands, sortedDrawCommands, lightViewProj);
|
drawShadowCasters(cmd, resources, drawList, view, lightViewProj);
|
||||||
endDepthRendering(cmd, shadowMap);
|
endDepthRendering(cmd, shadowMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShadowMapPipeline::drawSpotShadows(
|
void ShadowMapPipeline::drawSpotShadows(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands,
|
const std::vector<IndirectDrawList::View>& views,
|
||||||
const std::vector<glm::mat4>& lightViewProjs)
|
const std::vector<glm::mat4>& lightViewProjs)
|
||||||
{
|
{
|
||||||
if (!m_pipeline || spotShadowAtlasId == NULL_IMAGE_ID || lightViewProjs.empty())
|
if (!m_pipeline || spotShadowAtlasId == NULL_IMAGE_ID || lightViewProjs.empty())
|
||||||
@@ -236,6 +235,7 @@ void ShadowMapPipeline::drawSpotShadows(
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert(lightViewProjs.size() <= MAX_SPOT_SHADOWS);
|
assert(lightViewProjs.size() <= MAX_SPOT_SHADOWS);
|
||||||
|
assert(views.size() == lightViewProjs.size());
|
||||||
const auto& spotShadowAtlas = resources.getImage(spotShadowAtlasId);
|
const auto& spotShadowAtlas = resources.getImage(spotShadowAtlasId);
|
||||||
|
|
||||||
beginDepthRendering(cmd, resources, spotShadowAtlas);
|
beginDepthRendering(cmd, resources, spotShadowAtlas);
|
||||||
@@ -244,7 +244,7 @@ void ShadowMapPipeline::drawSpotShadows(
|
|||||||
vkutil::cmdBeginLabelFormatted(cmd, vkutil::labelColors::shadow, "Spot shadow %u", shadowIndex);
|
vkutil::cmdBeginLabelFormatted(cmd, vkutil::labelColors::shadow, "Spot shadow %u", shadowIndex);
|
||||||
|
|
||||||
setRenderRegion(cmd, spotShadowTileRegion(shadowIndex));
|
setRenderRegion(cmd, spotShadowTileRegion(shadowIndex));
|
||||||
drawShadowCasters(cmd, resources, drawCommands, sortedDrawCommands, lightViewProjs[shadowIndex]);
|
drawShadowCasters(cmd, resources, drawList, views[shadowIndex], lightViewProjs[shadowIndex]);
|
||||||
|
|
||||||
vkutil::cmdEndLabel(cmd);
|
vkutil::cmdEndLabel(cmd);
|
||||||
}
|
}
|
||||||
@@ -306,73 +306,31 @@ void ShadowMapPipeline::setRenderRegion(VkCommandBuffer cmd, const VkRect2D& reg
|
|||||||
void ShadowMapPipeline::drawShadowCasters(
|
void ShadowMapPipeline::drawShadowCasters(
|
||||||
VkCommandBuffer cmd,
|
VkCommandBuffer cmd,
|
||||||
const RenderResources& resources,
|
const RenderResources& resources,
|
||||||
const std::vector<MeshDrawCommand>& drawCommands,
|
const IndirectDrawList& drawList,
|
||||||
const std::vector<std::size_t>& sortedDrawCommands,
|
const IndirectDrawList::View& view,
|
||||||
const glm::mat4& lightViewProj)
|
const glm::mat4& lightViewProj)
|
||||||
{
|
{
|
||||||
const MeshCache& meshCache = resources.meshes();
|
const auto pushConstants = PushConstants{
|
||||||
const Frustum lightFrustum = edge::createFrustumFromViewProj(lightViewProj);
|
.lightViewProj = lightViewProj,
|
||||||
MeshID previousMeshId = NULL_MESH_ID;
|
.instanceBuffer = drawList.getInstanceBufferAddress(),
|
||||||
|
|
||||||
const auto drawCaster = [&](const MeshDrawCommand& dc)
|
|
||||||
{
|
|
||||||
if (!dc.castShadow || !edge::isInFrustum(lightFrustum, dc.worldBoundingSphere))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& mesh = meshCache.getMesh(dc.meshId);
|
|
||||||
|
|
||||||
const std::string& meshKey = meshCache.getMeshKey(dc.meshId);
|
|
||||||
vkutil::cmdBeginLabelFormatted(
|
|
||||||
cmd,
|
|
||||||
vkutil::labelColors::draw,
|
|
||||||
"%s [mesh %zu, material %u%s]",
|
|
||||||
meshKey.empty() ? "Mesh" : meshKey.c_str(),
|
|
||||||
dc.meshId,
|
|
||||||
dc.materialId,
|
|
||||||
dc.skinnedMesh != nullptr ? ", skinned" : "");
|
|
||||||
|
|
||||||
if (dc.meshId != previousMeshId)
|
|
||||||
{
|
|
||||||
previousMeshId = dc.meshId;
|
|
||||||
vkCmdBindIndexBuffer(cmd, mesh.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto pushConstants = PushConstants{
|
|
||||||
.modelToLightClip = lightViewProj * dc.transformMatrix,
|
|
||||||
.vertexBuffer =
|
|
||||||
dc.skinnedMesh != nullptr
|
|
||||||
? dc.skinnedMesh->skinnedVertexBuffer.address
|
|
||||||
: mesh.vertexBuffer.address,
|
|
||||||
};
|
|
||||||
|
|
||||||
vkCmdPushConstants(
|
|
||||||
cmd,
|
|
||||||
m_pipelineLayout,
|
|
||||||
VK_SHADER_STAGE_VERTEX_BIT,
|
|
||||||
0,
|
|
||||||
sizeof(PushConstants),
|
|
||||||
&pushConstants);
|
|
||||||
|
|
||||||
vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0);
|
|
||||||
|
|
||||||
vkutil::cmdEndLabel(cmd);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!sortedDrawCommands.empty())
|
vkCmdPushConstants(
|
||||||
{
|
cmd,
|
||||||
for (std::size_t drawIndex : sortedDrawCommands)
|
m_pipelineLayout,
|
||||||
{
|
VK_SHADER_STAGE_VERTEX_BIT,
|
||||||
assert(drawIndex < drawCommands.size());
|
0,
|
||||||
drawCaster(drawCommands[drawIndex]);
|
sizeof(PushConstants),
|
||||||
}
|
&pushConstants);
|
||||||
}
|
|
||||||
else
|
vkutil::cmdBeginLabelFormatted(
|
||||||
{
|
cmd,
|
||||||
for (const MeshDrawCommand& dc : drawCommands)
|
vkutil::labelColors::draw,
|
||||||
{
|
"%u indirect draws, %u instances",
|
||||||
drawCaster(dc);
|
view.commandCount,
|
||||||
}
|
view.instanceCount);
|
||||||
}
|
|
||||||
|
drawList.draw(cmd, resources.meshes().getIndexBuffer(), view);
|
||||||
|
|
||||||
|
vkutil::cmdEndLabel(cmd);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,22 +79,14 @@ void GameRenderer::endDrawing()
|
|||||||
sortedMeshDrawCommands.resize(meshDrawCommands.size());
|
sortedMeshDrawCommands.resize(meshDrawCommands.size());
|
||||||
std::iota(sortedMeshDrawCommands.begin(), sortedMeshDrawCommands.end(), 0);
|
std::iota(sortedMeshDrawCommands.begin(), sortedMeshDrawCommands.end(), 0);
|
||||||
|
|
||||||
|
// Grouping by mesh lets IndirectDrawList merge each run into one instanced draw.
|
||||||
|
// Material and vertex buffer are per instance, so they don't break a batch.
|
||||||
std::stable_sort(
|
std::stable_sort(
|
||||||
sortedMeshDrawCommands.begin(),
|
sortedMeshDrawCommands.begin(),
|
||||||
sortedMeshDrawCommands.end(),
|
sortedMeshDrawCommands.end(),
|
||||||
[this](std::size_t left, std::size_t right)
|
[this](std::size_t left, std::size_t right)
|
||||||
{
|
{
|
||||||
const auto& lhs = meshDrawCommands[left];
|
return meshDrawCommands[left].meshId < meshDrawCommands[right].meshId;
|
||||||
const auto& rhs = meshDrawCommands[right];
|
|
||||||
if (lhs.meshId != rhs.meshId)
|
|
||||||
{
|
|
||||||
return lhs.meshId < rhs.meshId;
|
|
||||||
}
|
|
||||||
if (lhs.materialId != rhs.materialId)
|
|
||||||
{
|
|
||||||
return lhs.materialId < rhs.materialId;
|
|
||||||
}
|
|
||||||
return (lhs.skinnedMesh != nullptr) < (rhs.skinnedMesh != nullptr);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +139,55 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
spotShadowViewProjs.push_back(spotShadow.lightViewProj);
|
spotShadowViewProjs.push_back(spotShadow.lightViewProj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Culling stays on the CPU: every view (camera, directional light, each spot light)
|
||||||
|
// gets its own range of indirect commands in this frame's draw list.
|
||||||
|
IndirectDrawList::View mainView;
|
||||||
|
IndirectDrawList::View directionalShadowView;
|
||||||
|
std::vector<IndirectDrawList::View> spotShadowViews;
|
||||||
|
{
|
||||||
|
ZoneScopedN("Build Indirect Draws");
|
||||||
|
|
||||||
|
const auto& meshes = resources->meshes().getMeshes();
|
||||||
|
drawList.begin(gfxDevice.getCurrentFrameIndex());
|
||||||
|
|
||||||
|
mainView = drawList.addView(
|
||||||
|
meshes,
|
||||||
|
meshDrawCommands,
|
||||||
|
sortedMeshDrawCommands,
|
||||||
|
edge::createFrustumFromCamera(camera),
|
||||||
|
IndirectDrawList::Filter::All);
|
||||||
|
|
||||||
|
if (shadowLightDirection)
|
||||||
|
{
|
||||||
|
directionalShadowView = drawList.addView(
|
||||||
|
meshes,
|
||||||
|
meshDrawCommands,
|
||||||
|
sortedMeshDrawCommands,
|
||||||
|
edge::createFrustumFromViewProj(lightViewProj),
|
||||||
|
IndirectDrawList::Filter::ShadowCasters);
|
||||||
|
}
|
||||||
|
|
||||||
|
spotShadowViews.reserve(spotShadowViewProjs.size());
|
||||||
|
for (const glm::mat4& spotViewProj : spotShadowViewProjs)
|
||||||
|
{
|
||||||
|
spotShadowViews.push_back(drawList.addView(
|
||||||
|
meshes,
|
||||||
|
meshDrawCommands,
|
||||||
|
sortedMeshDrawCommands,
|
||||||
|
edge::createFrustumFromViewProj(spotViewProj),
|
||||||
|
IndirectDrawList::Filter::ShadowCasters));
|
||||||
|
}
|
||||||
|
|
||||||
|
drawList.upload(gfxDevice);
|
||||||
|
lastDrawStats = DrawStats{
|
||||||
|
.meshDraws = static_cast<std::uint32_t>(meshDrawCommands.size()),
|
||||||
|
.indirectCommands = static_cast<std::uint32_t>(drawList.getCommands().size()),
|
||||||
|
.instances = static_cast<std::uint32_t>(drawList.getInstances().size()),
|
||||||
|
.mainViewCommands = mainView.commandCount,
|
||||||
|
.mainViewInstances = mainView.instanceCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: there has to be a better way to do this
|
// TODO: there has to be a better way to do this
|
||||||
const auto gpuSceneData = GPUSceneData{
|
const auto gpuSceneData = GPUSceneData{
|
||||||
.view = sceneData.camera.GetViewMatrix(),
|
.view = sceneData.camera.GetViewMatrix(),
|
||||||
@@ -225,8 +266,8 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
shadowMapPipeline->drawDirectionalShadow(
|
shadowMapPipeline->drawDirectionalShadow(
|
||||||
cmd,
|
cmd,
|
||||||
*resources,
|
*resources,
|
||||||
meshDrawCommands,
|
drawList,
|
||||||
sortedMeshDrawCommands,
|
directionalShadowView,
|
||||||
lightViewProj);
|
lightViewProj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,8 +279,8 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
shadowMapPipeline->drawSpotShadows(
|
shadowMapPipeline->drawSpotShadows(
|
||||||
cmd,
|
cmd,
|
||||||
*resources,
|
*resources,
|
||||||
meshDrawCommands,
|
drawList,
|
||||||
sortedMeshDrawCommands,
|
spotShadowViews,
|
||||||
spotShadowViewProjs);
|
spotShadowViewProjs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,10 +301,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
cmd,
|
cmd,
|
||||||
drawImage.getExtent2D(),
|
drawImage.getExtent2D(),
|
||||||
*resources,
|
*resources,
|
||||||
camera,
|
|
||||||
sceneDataBuffer.getBuffer(),
|
sceneDataBuffer.getBuffer(),
|
||||||
meshDrawCommands,
|
drawList,
|
||||||
sortedMeshDrawCommands);
|
mainView);
|
||||||
|
|
||||||
vkCmdEndRendering(cmd);
|
vkCmdEndRendering(cmd);
|
||||||
}
|
}
|
||||||
@@ -319,10 +359,9 @@ void GameRenderer::draw(VkCommandBuffer cmd, GfxDevice& gfxDevice, const Camera&
|
|||||||
cmd,
|
cmd,
|
||||||
drawImage.getExtent2D(),
|
drawImage.getExtent2D(),
|
||||||
*resources,
|
*resources,
|
||||||
camera,
|
|
||||||
sceneDataBuffer.getBuffer(),
|
sceneDataBuffer.getBuffer(),
|
||||||
meshDrawCommands,
|
drawList,
|
||||||
sortedMeshDrawCommands);
|
mainView);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -400,6 +439,7 @@ void GameRenderer::cleanup(GfxDevice& gfxDevice)
|
|||||||
meshPipeline->cleanup(device);
|
meshPipeline->cleanup(device);
|
||||||
|
|
||||||
sceneDataBuffer.cleanup(gfxDevice);
|
sceneDataBuffer.cleanup(gfxDevice);
|
||||||
|
drawList.cleanup(gfxDevice);
|
||||||
|
|
||||||
// if (drawImageId != NULL_IMAGE_ID)
|
// if (drawImageId != NULL_IMAGE_ID)
|
||||||
// gfxDevice.destroyImage();
|
// gfxDevice.destroyImage();
|
||||||
|
|||||||
Reference in New Issue
Block a user