This commit is contained in:
Dennis Brakhane
2025-11-07 14:51:30 +01:00
parent 33cffdde80
commit 3243f17e3a
8 changed files with 300 additions and 187 deletions
+17
View File
@@ -4,6 +4,7 @@
#include "wiVector.h"
#include "wiColor.h"
#include "wiGraphics.h"
#include "wiAtomic.h"
#include <string>
@@ -469,6 +470,22 @@ namespace wi
}
return *this;
}
template<typename T>
inline Archive& operator>>(std::atomic<T>& data)
{
T val;
(*this) >> val;
data = val;
return *this;
}
template<typename T>
inline Archive& operator>>(wi::relaxed_atomic<T>& data)
{
T val;
(*this) >> val;
data = val;
return *this;
}
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include <atomic>
// MSVC does not have constexpr atomic load(), so for now
// we just test for that, in C++20 we could also use
// type traits to check
#if defined(_MSC_VER) && !defined(__clang__)
#define WI_ATOMIC_CONSTEXPR inline
#else
#define WI_ATOMIC_CONSTEXPR constexpr
#endif
namespace wi {
// subclass of std::atomic that allows copying
// note that copying *is not* atomic; this is a helper class that allows us
// to copy classes/structs that contain atomic members where we can accept
// that copying is not atomic
template<typename T>
struct copyable_atomic : public std::atomic<T> {
copyable_atomic() noexcept = default;
constexpr copyable_atomic(T v) noexcept : std::atomic<T>(v) {};
copyable_atomic(const copyable_atomic& other) {
std::atomic<T>::store(other.load());
}
copyable_atomic& operator=(const copyable_atomic& other) {
std::atomic<T>::store(other.load());
return *this;
}
};
// simple wrapper that always defaults to memory_order_relaxed
template<typename T>
struct relaxed_atomic {
std::atomic<T> val;
relaxed_atomic() noexcept = default;
constexpr relaxed_atomic(T v) noexcept : val(v) {};
relaxed_atomic(const relaxed_atomic& other) : val(other.load(std::memory_order_relaxed)) {};
relaxed_atomic& operator=(const relaxed_atomic& other) {
val.store(other.val.load(std::memory_order_relaxed), std::memory_order_relaxed);
return *this;
}
void store(T desired, std::memory_order order = std::memory_order_relaxed) {
return val.store(desired, order);
}
T load(std::memory_order order = std::memory_order_relaxed) const {
return val.load(order);
}
T fetch_xor(T arg, std::memory_order order = std::memory_order_relaxed) {
return val.fetch_xor(arg, order);
}
T fetch_and(T arg, std::memory_order order = std::memory_order_relaxed) {
return val.fetch_add(arg, order);
}
T fetch_or(T arg, std::memory_order order = std::memory_order_relaxed) {
return val.fetch_or(arg, order);
}
operator T() const noexcept { return load(); }
T operator=(T desired) noexcept { store(desired); return desired;}
T operator&=(T arg) { return fetch_and(arg); }
T operator|=(T arg) { return fetch_or(arg); }
T operator^=(T arg) { return fetch_xor(arg); }
};
}
+9 -6
View File
@@ -36,7 +36,7 @@ namespace wi::backlog
wi::font::Params font_params;
wi::Color backgroundColor = wi::Color(17, 30, 43, 255);
Texture backgroundTex;
bool refitscroll = false;
std::atomic<bool> refitscroll = false;
wi::gui::TextInputField inputField;
wi::gui::Button toggleButton;
wi::gui::GUI GUI;
@@ -44,7 +44,7 @@ namespace wi::backlog
bool locked = false;
bool blockLuaExec = false;
LogLevel logLevel = LogLevel::Default;
LogLevel unseen = LogLevel::None;
std::atomic<LogLevel> unseen = LogLevel::None;
std::deque<LogEntry> history;
std::mutex historyLock;
@@ -494,7 +494,7 @@ namespace wi::backlog
wi::font::SetCanvas(canvas); // always set here as it can be called from outside...
wi::font::Params params = font_params;
params.cursor = {};
if (refitscroll)
if (refitscroll.exchange(false, std::memory_order_relaxed))
{
const float textheight = wi::font::TextHeight(getText(), params);
const float limit = canvas.GetLogicalHeight() - 50;
@@ -502,7 +502,6 @@ namespace wi::backlog
{
scroll = limit - textheight;
}
refitscroll = false;
}
params.posX = 5;
params.posY = pos + scroll;
@@ -599,7 +598,7 @@ namespace wi::backlog
// Enqueue for async file writing
asyncWriter.Enqueue(str);
refitscroll = true;
refitscroll.store(true);
switch (level)
{
@@ -615,7 +614,11 @@ namespace wi::backlog
break;
}
unseen = std::max(unseen, level);
// atomic version of unseen = max(unseen, level)
LogLevel current_unseen = unseen.load(std::memory_order_relaxed);
while (current_unseen < level) {
unseen.compare_exchange_weak(current_unseen, level, std::memory_order_acq_rel, std::memory_order_relaxed);
}
// Force an immediate flush on errors to prevent potential data loss
// in case the application is about to crash
+16 -1
View File
@@ -12,6 +12,7 @@
#include <cassert>
#include <atomic>
#include <memory>
#include <shared_mutex>
#include <string>
// Entity-Component System
@@ -604,14 +605,17 @@ namespace wi::ecs
};
Item items[64];
};
mutable std::shared_mutex mutex;
wi::unordered_map<uint64_t, Block> table;
inline void clear()
{
std::unique_lock lock(mutex);
table.clear();
}
inline void erase(Entity entity)
{
std::unique_lock lock(mutex);
const uint64_t block_index = entity >> 6ull; // entity / 64
auto it = table.find(block_index);
if (it == table.end())
@@ -631,6 +635,7 @@ namespace wi::ecs
}
inline void insert(Entity entity, size_t index)
{
std::unique_lock lock(mutex);
const uint64_t block_index = entity >> 6ull; // entity / 64
const uint64_t item_index = entity & 63ull; // entity % 64
Block& block = table[block_index];
@@ -639,6 +644,7 @@ namespace wi::ecs
}
inline size_t get(Entity entity) const
{
std::shared_lock lock(mutex);
const uint64_t block_index = entity >> 6ull; // entity / 64
const auto it = table.find(block_index);
if (it == table.end())
@@ -653,21 +659,25 @@ namespace wi::ecs
// Implementation with hash table:
// The standard hashing method, performance depends on hashing, hash collisions
wi::unordered_map<Entity, size_t> table;
mutable std::shared_mutex mutex;
inline void clear()
{
std::unique_lock lock(mutex);
table.clear();
}
inline void erase(Entity entity)
{
std::unique_lock lock(mutex);
table.erase(entity);
}
inline void insert(Entity entity, size_t index)
{
std::unique_lock lock(mutex);
table[entity] = index;
}
inline size_t get(Entity entity) const
{
std::shared_lock lock(mutex);
if (table.empty())
return INVALID_INDEX;
auto it = table.find(entity);
@@ -694,6 +704,7 @@ namespace wi::ecs
uint64_t version = 0;
};
wi::unordered_map<std::string, LibraryEntry> entries;
mutable std::shared_mutex mutex;
// Create an instance of ComponentManager of a certain data type
// The name must be unique, it will be used in serialization
@@ -701,6 +712,7 @@ namespace wi::ecs
template<typename T>
inline ComponentManager<T>& Register(const std::string& name, uint64_t version = 0)
{
std::unique_lock lock(mutex);
entries[name].component_manager = std::make_unique<ComponentManager<T>>();
entries[name].version = version;
return static_cast<ComponentManager<T>&>(*entries[name].component_manager);
@@ -709,6 +721,7 @@ namespace wi::ecs
template<typename T>
inline ComponentManager<T>* Get(const std::string& name)
{
std::shared_lock lock(mutex);
auto it = entries.find(name);
if (it == entries.end())
return nullptr;
@@ -718,6 +731,7 @@ namespace wi::ecs
template<typename T>
inline const ComponentManager<T>* Get(const std::string& name) const
{
std::shared_lock lock(mutex);
auto it = entries.find(name);
if (it == entries.end())
return nullptr;
@@ -726,6 +740,7 @@ namespace wi::ecs
inline uint64_t GetVersion(std::string name) const
{
std::shared_lock lock(mutex);
auto it = entries.find(name);
if (it == entries.end())
return 0;
+11 -4
View File
@@ -3,6 +3,7 @@
#include "wiGraphics.h"
#include "wiPlatform.h"
#include <atomic>
#include <cassert>
#include <cstring>
#include <algorithm>
@@ -52,7 +53,7 @@ namespace wi::graphics
{
protected:
static constexpr uint32_t BUFFERCOUNT = 2;
uint64_t FRAMECOUNT = 0;
std::atomic<uint64_t> FRAMECOUNT = 0;
size_t SHADER_IDENTIFIER_SIZE = 0;
size_t TOPLEVEL_ACCELERATION_STRUCTURE_INSTANCE_SIZE = 0;
uint64_t TIMESTAMP_FREQUENCY = 0;
@@ -116,9 +117,15 @@ namespace wi::graphics
// One PipelineState object can be compiled internally for multiple render target or depth-stencil formats, or sample counts
virtual size_t GetActivePipelineCount() const = 0;
#if defined(_MSC_VER) && !defined(__clang__)
#define constexpr_no_msvc
#else
#define constexpr_no_msvc constexpr
#endif
// Returns the number of elapsed frames (submits)
// It is incremented when calling SubmitCommandLists()
constexpr uint64_t GetFrameCount() const { return FRAMECOUNT; }
constexpr_no_msvc uint64_t GetFrameCount() const { return FRAMECOUNT; }
// Check whether the graphics device supports a feature or not
constexpr bool CheckCapability(GraphicsDeviceCapability capability) const { return has_flag(capabilities, capability); }
@@ -126,7 +133,7 @@ namespace wi::graphics
// Returns the buffer count, which is the array size of buffered resources used by both the CPU and GPU
static constexpr uint32_t GetBufferCount() { return BUFFERCOUNT; }
// Returns the current buffer index, which is in range [0, GetBufferCount() - 1]
constexpr uint32_t GetBufferIndex() const { return GetFrameCount() % GetBufferCount(); }
constexpr_no_msvc uint32_t GetBufferIndex() const { return GetFrameCount() % GetBufferCount(); }
// Returns whether the graphics debug layer is enabled. It can be enabled when creating the device.
constexpr bool IsDebugDevice() const { return validationMode != ValidationMode::Disabled; }
@@ -294,7 +301,7 @@ namespace wi::graphics
inline bool IsValid() const { return data != nullptr && buffer.IsValid(); }
};
// Allocates temporary memory that the CPU can write and GPU can read.
// Allocates temporary memory that the CPU can write and GPU can read.
// It is only alive for one frame and automatically invalidated after that.
GPUAllocation AllocateGPU(uint64_t dataSize, CommandList cmd)
{
+3 -3
View File
@@ -5388,7 +5388,7 @@ std::mutex queue_locker;
else
{
allocationhandler->destroylocker.lock();
allocationhandler->destroyer_pipelines.push_back(std::make_pair(x.second, FRAMECOUNT));
allocationhandler->destroyer_pipelines.push_back(std::make_pair(x.second, GetFrameCount()));
allocationhandler->destroylocker.unlock();
}
}
@@ -5767,7 +5767,7 @@ std::mutex queue_locker;
for (auto& x : pipelines_global)
{
allocationhandler->destroyer_pipelines.push_back(std::make_pair(x.second, FRAMECOUNT));
allocationhandler->destroyer_pipelines.push_back(std::make_pair(x.second, GetFrameCount()));
}
pipelines_global.clear();
@@ -5775,7 +5775,7 @@ std::mutex queue_locker;
{
for (auto& y : x->pipelines_worker)
{
allocationhandler->destroyer_pipelines.push_back(std::make_pair(y.second, FRAMECOUNT));
allocationhandler->destroyer_pipelines.push_back(std::make_pair(y.second, GetFrameCount()));
}
x->pipelines_worker.clear();
}
+4 -4
View File
@@ -1635,7 +1635,7 @@ using namespace vulkan_internal;
if (descriptorPool != VK_NULL_HANDLE)
{
device->allocationhandler->destroylocker.lock();
device->allocationhandler->destroyer_descriptorPools.push_back(std::make_pair(descriptorPool, device->FRAMECOUNT));
device->allocationhandler->destroyer_descriptorPools.push_back(std::make_pair(descriptorPool, device->GetFrameCount()));
descriptorPool = VK_NULL_HANDLE;
device->allocationhandler->destroylocker.unlock();
}
@@ -7412,7 +7412,7 @@ using namespace vulkan_internal;
else
{
allocationhandler->destroylocker.lock();
allocationhandler->destroyer_pipelines.push_back(std::make_pair(x.second, FRAMECOUNT));
allocationhandler->destroyer_pipelines.push_back(std::make_pair(x.second, GetFrameCount()));
allocationhandler->destroylocker.unlock();
}
}
@@ -7510,7 +7510,7 @@ using namespace vulkan_internal;
for (auto& x : pipelines_global)
{
allocationhandler->destroyer_pipelines.push_back(std::make_pair(x.second, FRAMECOUNT));
allocationhandler->destroyer_pipelines.push_back(std::make_pair(x.second, GetFrameCount()));
}
pipelines_global.clear();
@@ -7518,7 +7518,7 @@ using namespace vulkan_internal;
{
for (auto& y : x->pipelines_worker)
{
allocationhandler->destroyer_pipelines.push_back(std::make_pair(y.second, FRAMECOUNT));
allocationhandler->destroyer_pipelines.push_back(std::make_pair(y.second, GetFrameCount()));
}
x->pipelines_worker.clear();
}
File diff suppressed because it is too large Load Diff