optimizations for hair particle system (#1298)
This commit is contained in:
@@ -377,6 +377,17 @@ void HairParticleWindow::UpdateData()
|
||||
ss += "Tip: To use hair particle system, first you must select a surface mesh to spawn particles on, and then increase particle count to grow particles. The particles will get their texture from the Material that is created on the current entity.\n\n";
|
||||
ss += "Position format: " + std::string(wi::graphics::GetFormatString(hair->position_format)) + "\n";
|
||||
ss += "Memory usage: " + wi::helper::GetMemorySizeText(hair->GetMemorySizeInBytes()) + "\n";
|
||||
|
||||
ss += "\nSuballocation offset: ";
|
||||
if (hair->generalBufferOffsetAllocation.IsValid())
|
||||
{
|
||||
ss += wi::helper::GetMemorySizeText(hair->generalBufferOffsetAllocation.byte_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
ss += "suballocation is not used for this hair particle system";
|
||||
}
|
||||
|
||||
infoLabel.SetText(ss);
|
||||
|
||||
meshComboBox.ClearItems();
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace wi::allocator
|
||||
{
|
||||
Reset();
|
||||
allocator = std::move(other.allocator);
|
||||
internal_state = other.internal_state;
|
||||
internal_state = std::move(other.internal_state);
|
||||
byte_offset = other.byte_offset;
|
||||
other.allocator = nullptr;
|
||||
other.internal_state = nullptr;
|
||||
@@ -197,7 +197,7 @@ namespace wi::allocator
|
||||
{
|
||||
Reset();
|
||||
allocator = std::move(other.allocator);
|
||||
internal_state = other.internal_state;
|
||||
internal_state = std::move(other.internal_state);
|
||||
byte_offset = other.byte_offset;
|
||||
other.allocator = nullptr;
|
||||
other.internal_state = nullptr;
|
||||
@@ -248,7 +248,7 @@ namespace wi::allocator
|
||||
}
|
||||
|
||||
// returns true if no pages are allocated
|
||||
inline bool is_empty()
|
||||
inline bool is_empty() const
|
||||
{
|
||||
return allocator->allocator.storageReport().totalFreeSpace == page_count;
|
||||
}
|
||||
|
||||
@@ -431,6 +431,10 @@ namespace wi::ecs
|
||||
// Returns the tightly packed [read only] component array
|
||||
inline const wi::vector<Component>& GetComponentArray() const { return components; }
|
||||
|
||||
// Returns the raw data pointer of components:
|
||||
inline const Component* GetData() const { return components.data(); }
|
||||
inline Component* GetData() { return components.data(); }
|
||||
|
||||
private:
|
||||
// This is a linear array of alive components
|
||||
wi::vector<Component> components;
|
||||
|
||||
@@ -241,6 +241,7 @@ namespace wi::graphics
|
||||
|
||||
// Some useful helpers:
|
||||
|
||||
// This can be used to create a buffer filled from CPU data pointer, the copy will be done on the CPU (and additional GPU copy for non-UMA)
|
||||
bool CreateBuffer(const GPUBufferDesc* desc, const void* initial_data, GPUBuffer* buffer, const GPUResource* alias = nullptr, uint64_t alias_offset = 0ull) const
|
||||
{
|
||||
if (initial_data == nullptr)
|
||||
@@ -250,16 +251,19 @@ namespace wi::graphics
|
||||
return CreateBuffer2(desc, [&](void* dest) { std::memcpy(dest, initial_data, desc->size); }, buffer, alias, alias_offset);
|
||||
}
|
||||
|
||||
// This can be used to create a buffer filled with a single value, the data initialization will be done on the CPU (and additional GPU copy for non-UMA)
|
||||
bool CreateBufferCleared(const GPUBufferDesc* desc, uint8_t value, GPUBuffer* buffer, const GPUResource* alias = nullptr, uint64_t alias_offset = 0ull) const
|
||||
{
|
||||
return CreateBuffer2(desc, [&](void* dest) { std::memset(dest, value, desc->size); }, buffer, alias, alias_offset);
|
||||
}
|
||||
|
||||
// This can be used to create a buffer filled with zeroes, the data initialization will be done on the CPU (and additional GPU copy for non-UMA)
|
||||
bool CreateBufferZeroed(const GPUBufferDesc* desc, GPUBuffer* buffer, const GPUResource* alias = nullptr, uint64_t alias_offset = 0ull) const
|
||||
{
|
||||
return CreateBufferCleared(desc, 0, buffer, alias, alias_offset);
|
||||
}
|
||||
|
||||
// Execute a single GPU barrier
|
||||
void Barrier(const GPUBarrier& barrier, CommandList cmd)
|
||||
{
|
||||
Barrier(&barrier, 1, cmd);
|
||||
@@ -347,6 +351,7 @@ namespace wi::graphics
|
||||
BindConstantBuffer(&allocation.buffer, slot, cmd, allocation.offset);
|
||||
}
|
||||
|
||||
// Simplified renderpass beginning for a single render target and optional clear (if clear is false, it will use load operation)
|
||||
void RenderPassBegin(const Texture* rendertarget, CommandList cmd, bool clear = true)
|
||||
{
|
||||
RenderPassImage rp[] = {
|
||||
|
||||
@@ -89,8 +89,31 @@ namespace wi
|
||||
_flags |= REBUILD_BUFFERS;
|
||||
}
|
||||
|
||||
void HairParticleSystem::DeleteRenderData()
|
||||
{
|
||||
constantBuffer = {};
|
||||
generalBuffer = {};
|
||||
generalBufferOffsetAllocation = {};
|
||||
generalBufferOffsetAllocationAlias = {};
|
||||
simulation_view = {};
|
||||
vb_pos[0] = {};
|
||||
vb_pos[1] = {};
|
||||
vb_nor = {};
|
||||
vb_pos_raytracing = {};
|
||||
vb_uvs = {};
|
||||
wetmap = {};
|
||||
ib_culled = {};
|
||||
indirect_view = {};
|
||||
prim_view = {};
|
||||
indexBuffer = {};
|
||||
vertexBuffer_length = {};
|
||||
BLAS = {};
|
||||
}
|
||||
|
||||
void HairParticleSystem::CreateRenderData()
|
||||
{
|
||||
DeleteRenderData();
|
||||
|
||||
GraphicsDevice* device = wi::graphics::GetDevice();
|
||||
|
||||
BLAS = {};
|
||||
@@ -144,8 +167,24 @@ namespace wi
|
||||
AlignTo(prim_view.size, alignment) +
|
||||
AlignTo(vb_pos_raytracing.size, alignment)
|
||||
;
|
||||
device->CreateBufferZeroed(&bd, &generalBuffer);
|
||||
device->SetName(&generalBuffer, "HairParticleSystem::generalBuffer");
|
||||
#if 0 // Suballocation is disabled for hair particle system for now, there is some memory overwrite-like issue somewhere
|
||||
wi::renderer::BufferSuballocation suballoc = wi::renderer::SuballocateGPUBuffer(bd.size);
|
||||
if (suballoc.IsValid())
|
||||
{
|
||||
bool success = device->CreateBuffer(&bd, nullptr, &generalBuffer, &suballoc.alias, suballoc.allocation.byte_offset);
|
||||
assert(success);
|
||||
device->SetName(&generalBuffer, "HairParticleSystem::generalBuffer (suballocated)");
|
||||
generalBufferOffsetAllocation = std::move(suballoc.allocation);
|
||||
generalBufferOffsetAllocationAlias = std::move(suballoc.alias);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// If suballocation was not successful, a standalone buffer can be created instead:
|
||||
bool success = device->CreateBuffer(&bd, nullptr, &generalBuffer);
|
||||
assert(success);
|
||||
device->SetName(&generalBuffer, "HairParticleSystem::generalBuffer");
|
||||
}
|
||||
gpu_initialized = false;
|
||||
|
||||
uint64_t buffer_offset = 0ull;
|
||||
@@ -347,6 +386,32 @@ namespace wi
|
||||
|
||||
std::swap(vb_pos[0], vb_pos[1]);
|
||||
}
|
||||
void HairParticleSystem::InitializeGPUBuffersIfNeeded(
|
||||
const HairParticleSystem* hairs,
|
||||
size_t hairCount,
|
||||
CommandList cmd
|
||||
)
|
||||
{
|
||||
GraphicsDevice* device = wi::graphics::GetDevice();
|
||||
device->EventBegin("HairParticleSystem - InitializeGPUBuffersIfNeeded", cmd);
|
||||
|
||||
for (uint32_t i = 0; i < hairCount; ++i)
|
||||
{
|
||||
const HairParticleSystem& hair = hairs[i];
|
||||
if (hair.strandCount == 0 || !hair.generalBuffer.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!hair.gpu_initialized)
|
||||
{
|
||||
hair.gpu_initialized = true;
|
||||
device->ClearUAV(&hair.generalBuffer, 0, cmd);
|
||||
wi::renderer::PushBarrier(GPUBarrier::Buffer(&hair.generalBuffer, ResourceState::UNORDERED_ACCESS, ResourceState::COPY_DST));
|
||||
}
|
||||
}
|
||||
wi::renderer::FlushBarriers(cmd);
|
||||
device->EventEnd(cmd);
|
||||
}
|
||||
void HairParticleSystem::UpdateGPU(
|
||||
const UpdateGPUItem* items,
|
||||
uint32_t itemCount,
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace wi
|
||||
public:
|
||||
wi::graphics::GPUBuffer constantBuffer;
|
||||
wi::graphics::GPUBuffer generalBuffer;
|
||||
wi::allocator::PageAllocator::Allocation generalBufferOffsetAllocation;
|
||||
wi::graphics::GPUBuffer generalBufferOffsetAllocationAlias;
|
||||
wi::scene::MeshComponent::BufferView simulation_view;
|
||||
wi::scene::MeshComponent::BufferView vb_pos[2];
|
||||
wi::scene::MeshComponent::BufferView vb_nor;
|
||||
@@ -37,6 +39,7 @@ namespace wi
|
||||
wi::graphics::RaytracingAccelerationStructure BLAS;
|
||||
|
||||
void CreateFromMesh(const wi::scene::MeshComponent& mesh);
|
||||
void DeleteRenderData();
|
||||
void CreateRenderData();
|
||||
void CreateRaytracingRenderData();
|
||||
|
||||
@@ -46,6 +49,12 @@ namespace wi
|
||||
float dt
|
||||
);
|
||||
|
||||
static void InitializeGPUBuffersIfNeeded(
|
||||
const HairParticleSystem* hairs,
|
||||
size_t hairCount,
|
||||
wi::graphics::CommandList cmd
|
||||
);
|
||||
|
||||
struct UpdateGPUItem
|
||||
{
|
||||
const HairParticleSystem* hair = nullptr;
|
||||
|
||||
+50
-37
@@ -2731,46 +2731,54 @@ BufferSuballocation SuballocateGPUBuffer(uint64_t size)
|
||||
if (size > GPUSubAllocator::blocksize / 2)
|
||||
return {}; // invalid, larger allocations than half block size will not be suballocated
|
||||
|
||||
// scoped for locker
|
||||
std::scoped_lock lock(suballocator.locker);
|
||||
|
||||
// See if any of the large blocks can fulfill the allocation request:
|
||||
BufferSuballocation allocation;
|
||||
for (auto& block : suballocator.blocks)
|
||||
{
|
||||
std::scoped_lock lock(suballocator.locker);
|
||||
|
||||
// See if any of the large blocks can fulfill the allocation request:
|
||||
BufferSuballocation allocation;
|
||||
for (auto& block : suballocator.blocks)
|
||||
allocation.allocation = block.allocator.allocate(size);
|
||||
if (allocation.allocation.IsValid())
|
||||
{
|
||||
allocation.allocation = block.allocator.allocate(size);
|
||||
if (allocation.allocation.IsValid())
|
||||
{
|
||||
allocation.alias = block.buffer;
|
||||
//wilog("SuballocateGPUBuffer allocated size: %s, pages: %d, free space remaining: %s", wi::helper::GetMemorySizeText(size).c_str(), block.allocator.page_count_from_bytes(size), wi::helper::GetMemorySizeText(allocation.allocation.allocator->allocator.storageReport().totalFreeSpace * block.allocator.page_size).c_str());
|
||||
return allocation;
|
||||
}
|
||||
allocation.alias = block.buffer;
|
||||
//wilog("SuballocateGPUBuffer allocated size: %s, pages: %d, free space remaining: %s", wi::helper::GetMemorySizeText(size).c_str(), block.allocator.page_count_from_bytes(size), wi::helper::GetMemorySizeText(allocation.allocation.allocator->allocator.storageReport().totalFreeSpace * block.allocator.page_size).c_str());
|
||||
return allocation;
|
||||
}
|
||||
|
||||
// Allocation couldn't be fulfilled, create new block:
|
||||
GPUBufferDesc desc;
|
||||
desc.size = GPUSubAllocator::blocksize;
|
||||
if (device->CheckCapability(GraphicsDeviceCapability::CACHE_COHERENT_UMA))
|
||||
{
|
||||
// In UMA mode, it is better to create UPLOAD buffer, this avoids one copy from UPLOAD to DEFAULT
|
||||
desc.usage = Usage::UPLOAD;
|
||||
}
|
||||
else
|
||||
{
|
||||
desc.usage = Usage::DEFAULT;
|
||||
}
|
||||
desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::VERTEX_BUFFER | BindFlag::INDEX_BUFFER;
|
||||
desc.misc_flags = ResourceMiscFlag::ALIASING_BUFFER | ResourceMiscFlag::NO_DEFAULT_DESCRIPTORS;
|
||||
desc.alignment = device->GetMinOffsetAlignment(&desc);
|
||||
auto& block = suballocator.blocks.emplace_back();
|
||||
bool success = device->CreateBuffer(&desc, nullptr, &block.buffer);
|
||||
assert(success);
|
||||
device->SetName(&block.buffer, "GPUSubAllocator");
|
||||
block.allocator.init(desc.size, (uint32_t)desc.alignment, true);
|
||||
wilog("SuballocateGPUBuffer created buffer block with size: %s, with page size: %s, page count: %d", wi::helper::GetMemorySizeText(block.allocator.total_size_in_bytes()).c_str(), wi::helper::GetMemorySizeText(block.allocator.page_size).c_str(), (int)block.allocator.page_count);
|
||||
}
|
||||
return SuballocateGPUBuffer(size); // retry
|
||||
|
||||
// Allocation couldn't be fulfilled, create new block:
|
||||
GPUBufferDesc desc;
|
||||
desc.size = GPUSubAllocator::blocksize;
|
||||
if (device->CheckCapability(GraphicsDeviceCapability::CACHE_COHERENT_UMA))
|
||||
{
|
||||
// In UMA mode, it is better to create UPLOAD buffer, this avoids one copy from UPLOAD to DEFAULT
|
||||
desc.usage = Usage::UPLOAD;
|
||||
}
|
||||
else
|
||||
{
|
||||
desc.usage = Usage::DEFAULT;
|
||||
}
|
||||
desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::VERTEX_BUFFER | BindFlag::INDEX_BUFFER | BindFlag::UNORDERED_ACCESS;
|
||||
desc.misc_flags = ResourceMiscFlag::ALIASING_BUFFER | ResourceMiscFlag::NO_DEFAULT_DESCRIPTORS;
|
||||
if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING))
|
||||
{
|
||||
desc.misc_flags |= ResourceMiscFlag::RAY_TRACING;
|
||||
}
|
||||
desc.alignment = device->GetMinOffsetAlignment(&desc);
|
||||
auto& block = suballocator.blocks.emplace_back();
|
||||
bool success = device->CreateBuffer(&desc, nullptr, &block.buffer);
|
||||
assert(success);
|
||||
device->SetName(&block.buffer, "GPUSubAllocator");
|
||||
block.allocator.init(desc.size, (uint32_t)desc.alignment, true);
|
||||
wilog("SuballocateGPUBuffer created buffer block with size: %s, with page size: %s, page count: %d", wi::helper::GetMemorySizeText(block.allocator.total_size_in_bytes()).c_str(), wi::helper::GetMemorySizeText(block.allocator.page_size).c_str(), (int)block.allocator.page_count);
|
||||
|
||||
allocation.allocation = block.allocator.allocate(size);
|
||||
if (allocation.allocation.IsValid())
|
||||
{
|
||||
allocation.alias = block.buffer;
|
||||
//wilog("SuballocateGPUBuffer allocated size: %s, pages: %d, free space remaining: %s", wi::helper::GetMemorySizeText(size).c_str(), block.allocator.page_count_from_bytes(size), wi::helper::GetMemorySizeText(allocation.allocation.allocator->allocator.storageReport().totalFreeSpace * block.allocator.page_size).c_str());
|
||||
}
|
||||
return allocation;
|
||||
}
|
||||
void UpdateGPUSuballocator()
|
||||
{
|
||||
@@ -2781,8 +2789,10 @@ void UpdateGPUSuballocator()
|
||||
}
|
||||
for (size_t i = 0; i < suballocator.blocks.size(); ++i)
|
||||
{
|
||||
if (suballocator.blocks[i].allocator.is_empty())
|
||||
const auto& block = suballocator.blocks[i];
|
||||
if (block.allocator.is_empty())
|
||||
{
|
||||
wilog("deleted suballocation buffer block with size: %s, with page size: %s, page count: %d", wi::helper::GetMemorySizeText(block.allocator.total_size_in_bytes()).c_str(), wi::helper::GetMemorySizeText(block.allocator.page_size).c_str(), (int)block.allocator.page_count);
|
||||
suballocator.blocks.erase(suballocator.blocks.begin() + i);
|
||||
break;
|
||||
}
|
||||
@@ -5275,6 +5285,9 @@ void UpdateRenderData(
|
||||
|
||||
FlushBarriers(cmd); // wind/skinning flush
|
||||
|
||||
// Hair particle systems clearing (needs to be for everything, not just visible):
|
||||
HairParticleSystem::InitializeGPUBuffersIfNeeded(vis.scene->hairs.GetData(), vis.scene->hairs.GetCount(), cmd);
|
||||
|
||||
// Hair particle systems GPU simulation:
|
||||
// (This must be non-async too, as prepass will render hairs!)
|
||||
static thread_local wi::vector<HairParticleSystem::UpdateGPUItem> hair_updates;
|
||||
|
||||
@@ -75,9 +75,10 @@ namespace wi::renderer
|
||||
{
|
||||
wi::graphics::GPUBuffer alias;
|
||||
wi::allocator::PageAllocator::Allocation allocation;
|
||||
inline bool IsValid() const { return allocation.IsValid(); }
|
||||
};
|
||||
// Sub-allocate (thread-safe) from a global GPU buffer for memory aliasing purpose:
|
||||
// The buffer will be DEFAULT usage, useable as vertex buffer, index buffer and shader resource
|
||||
// The buffer will be DEFAULT usage, useable as vertex buffer, index buffer, shader resource and unordered access
|
||||
// The purpose is to suballocate smaller GPUBuffers inside a larger GPUBuffer and bind the large GPUBuffer once as index buffer,
|
||||
// while the small buffers can be allocated/deallocated from it with memory aliasing and also used regularly by themselves
|
||||
BufferSuballocation SuballocateGPUBuffer(uint64_t size);
|
||||
|
||||
@@ -591,6 +591,7 @@ namespace wi::scene
|
||||
void MeshComponent::DeleteRenderData()
|
||||
{
|
||||
generalBufferOffsetAllocation = {};
|
||||
generalBufferOffsetAllocationAlias = {};
|
||||
generalBuffer = {};
|
||||
streamoutBuffer = {};
|
||||
ib_provoke = {};
|
||||
@@ -1352,7 +1353,7 @@ namespace wi::scene
|
||||
// With this we can avoid rebinding the index buffer for every mesh and can work with purely offsets
|
||||
// Though the index buffer will still need to be rebound if the index format changes, but that happens less frequently
|
||||
wi::renderer::BufferSuballocation suballoc = wi::renderer::SuballocateGPUBuffer(bd.size);
|
||||
if (suballoc.allocation.IsValid())
|
||||
if (suballoc.IsValid())
|
||||
{
|
||||
bool success = device->CreateBuffer2(&bd, init_callback, &generalBuffer, &suballoc.alias, suballoc.allocation.byte_offset);
|
||||
assert(success);
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace wi::version
|
||||
// minor features, major updates, breaking compatibility changes
|
||||
const int minor = 71;
|
||||
// minor bug fixes, alterations, refactors, updates
|
||||
const int revision = 854;
|
||||
const int revision = 855;
|
||||
|
||||
const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace wi::version
|
||||
"Aldo, lokimx, K. Osterman, Nomad, ykl, Alex Krokos, Timmy, Avaflow, mat, Hexegonel Samael Michael, Joe Spataro, soru, GeniokV, Mammoth, Ignacio, datae, Jason Rice, " \
|
||||
"MarsBEKET, Tim, Twisty, Zelf ieats kiezen, Romildo Franco, zNachoh, Dmitriy, Alex Minerva, Stefan Kent, Natty, Sunny Krishna, Vilmos Malárik, Ferrata, Rossakis, " \
|
||||
"Stefana Andrei, Taylor, Gunnar Kriik, 赟 杨, Rex, Lemon Brother, flxy, meta_leap, Edik, jusik5348, Agnares, Fred Naar, Saki Asui, DarkRaVen, Ray, Russell Searle, Alexandr Dem'yanenko, "\
|
||||
"Portaloffreedom, nxrighthere, わさび わさび "
|
||||
"Portaloffreedom, nxrighthere, わさび わさび, Durak, ipso"
|
||||
|
||||
const char* GetCreditsString()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user