diff --git a/Editor/HairParticleWindow.cpp b/Editor/HairParticleWindow.cpp index 6cbfc1098..4b8595cd0 100644 --- a/Editor/HairParticleWindow.cpp +++ b/Editor/HairParticleWindow.cpp @@ -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(); diff --git a/WickedEngine/wiAllocator.h b/WickedEngine/wiAllocator.h index af82d4e11..b0a1a9a8d 100644 --- a/WickedEngine/wiAllocator.h +++ b/WickedEngine/wiAllocator.h @@ -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; } diff --git a/WickedEngine/wiECS.h b/WickedEngine/wiECS.h index 78cfcb3f3..9bd93b848 100644 --- a/WickedEngine/wiECS.h +++ b/WickedEngine/wiECS.h @@ -431,6 +431,10 @@ namespace wi::ecs // Returns the tightly packed [read only] component array inline const wi::vector& 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 components; diff --git a/WickedEngine/wiGraphicsDevice.h b/WickedEngine/wiGraphicsDevice.h index d3ff51b04..e67f7be70 100644 --- a/WickedEngine/wiGraphicsDevice.h +++ b/WickedEngine/wiGraphicsDevice.h @@ -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[] = { diff --git a/WickedEngine/wiHairParticle.cpp b/WickedEngine/wiHairParticle.cpp index d9d92a678..ac7b3c235 100644 --- a/WickedEngine/wiHairParticle.cpp +++ b/WickedEngine/wiHairParticle.cpp @@ -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, diff --git a/WickedEngine/wiHairParticle.h b/WickedEngine/wiHairParticle.h index c97c1cc19..0958e8d39 100644 --- a/WickedEngine/wiHairParticle.h +++ b/WickedEngine/wiHairParticle.h @@ -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; diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index e8b3d08a4..232d0663c 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -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 hair_updates; diff --git a/WickedEngine/wiRenderer.h b/WickedEngine/wiRenderer.h index 117944925..cad62a037 100644 --- a/WickedEngine/wiRenderer.h +++ b/WickedEngine/wiRenderer.h @@ -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); diff --git a/WickedEngine/wiScene_Components.cpp b/WickedEngine/wiScene_Components.cpp index 731df6988..65e60704c 100644 --- a/WickedEngine/wiScene_Components.cpp +++ b/WickedEngine/wiScene_Components.cpp @@ -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); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 3cb050ceb..911729922 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -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() {