mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
Common: make the lock-free maps C89
AtomicHashMap.h was a pair of C++ templates. Common/AtomicMap.{h,c}
replaces it with the same structure in C89, keyed by bytes: each key's
node is pushed at its bucket's head with a CAS and stays until the map
is freed.
- atomic_slot_map: one atomic pointer slot per key, for the OGL
pipeline programs (keyed by the PipelineProgramKey bytes) and the
D3D state cache (keyed by each state's hex word).
- atomic_value_map: values that begin with a quiesce_node_t, published
whole and freed once no reader is inside, for the asset library's
id-to-files and path-to-id maps (keyed by the string's bytes).
The OGL program cache's reference count and shader id counter move to
retro_atomic too, and PipelineProgramKeyHash goes with the template
that used it.
This commit is contained in:
1 parent
033f4905cc
commit
d3b454d48b
10 files changed
+433
-289
No files matched your search
@@ -1078,6 +1078,7 @@ DOLPHIN_SOURCES_ASM :=
|
||||
DOLPHIN_SOURCES_OBJCXX :=
|
||||
DOLPHIN_SOURCES_C += \
|
||||
$(CORE_DIR)/Source/Core/AudioCommon/FieldAudio.c \
|
||||
$(CORE_DIR)/Source/Core/Common/AtomicMap.c \
|
||||
$(CORE_DIR)/Source/Core/Common/CallGate.c \
|
||||
$(CORE_DIR)/Source/Core/Common/FrameClock.c \
|
||||
$(CORE_DIR)/Source/Core/Common/ParallelFor.c \
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include "Common/Quiesce.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
// A map any thread may read and write without a lock, for keys that are only ever added (a
|
||||
// value can be replaced or removed, the key's slot stays). Each bucket is a list pushed at
|
||||
// its head with a CAS; a value is a published copy, swapped whole and freed once no reader
|
||||
// is inside the map's quiesce.
|
||||
template <typename K, typename V, size_t BUCKETS = 4096, typename Hash = std::hash<K>>
|
||||
class AtomicHashMap final
|
||||
{
|
||||
public:
|
||||
AtomicHashMap() = default;
|
||||
AtomicHashMap(const AtomicHashMap&) = delete;
|
||||
AtomicHashMap& operator=(const AtomicHashMap&) = delete;
|
||||
|
||||
~AtomicHashMap()
|
||||
{
|
||||
for (auto& bucket : m_buckets)
|
||||
{
|
||||
auto* node = static_cast<Node*>(retro_atomic_load_acquire_ptr(&bucket));
|
||||
while (node)
|
||||
{
|
||||
Node* const next = node->next;
|
||||
delete static_cast<Value*>(retro_atomic_load_acquire_ptr(&node->value));
|
||||
delete node;
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
quiesce_deinit(&m_quiesce);
|
||||
}
|
||||
|
||||
// A copy of the value for key, if it has one.
|
||||
template <typename Key>
|
||||
std::optional<V> Get(const Key& key) const
|
||||
{
|
||||
quiesce_enter(&m_quiesce);
|
||||
std::optional<V> result;
|
||||
if (const Node* node = Find(Bucket(key), key, nullptr))
|
||||
{
|
||||
if (const auto* value = static_cast<const Value*>(retro_atomic_load_acquire_ptr(&node->value)))
|
||||
result = value->value;
|
||||
}
|
||||
quiesce_exit(&m_quiesce);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Set(const K& key, V value) { Replace(key, new Value{{}, std::move(value)}); }
|
||||
|
||||
template <typename Key>
|
||||
void Erase(const Key& key)
|
||||
{
|
||||
quiesce_enter(&m_quiesce);
|
||||
if (Node* node = Find(Bucket(key), key, nullptr))
|
||||
Retire(retro_atomic_exchange_ptr(&node->value, nullptr));
|
||||
quiesce_exit(&m_quiesce);
|
||||
}
|
||||
|
||||
private:
|
||||
struct Value
|
||||
{
|
||||
quiesce_node_t node; // first: the free callback casts it back
|
||||
V value;
|
||||
};
|
||||
struct Node
|
||||
{
|
||||
Node* next; // fixed once published
|
||||
K key;
|
||||
retro_atomic_ptr_t value; // Value*, or null when removed
|
||||
};
|
||||
|
||||
template <typename Key>
|
||||
retro_atomic_ptr_t* Bucket(const Key& key) const
|
||||
{
|
||||
return &m_buckets[Hash{}(key) % BUCKETS];
|
||||
}
|
||||
|
||||
template <typename Key>
|
||||
static Node* Find(retro_atomic_ptr_t* bucket, const Key& key, const Node* stop)
|
||||
{
|
||||
for (auto* node = static_cast<Node*>(retro_atomic_load_acquire_ptr(bucket)); node != stop;
|
||||
node = node->next)
|
||||
{
|
||||
if (node->key == key)
|
||||
return node;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void FreeValue(quiesce_node_t* node) { delete reinterpret_cast<Value*>(node); }
|
||||
|
||||
void Retire(void* value)
|
||||
{
|
||||
if (value)
|
||||
quiesce_retire(&m_quiesce, &static_cast<Value*>(value)->node, FreeValue);
|
||||
}
|
||||
|
||||
void Replace(const K& key, Value* value)
|
||||
{
|
||||
quiesce_enter(&m_quiesce);
|
||||
retro_atomic_ptr_t* const bucket = Bucket(key);
|
||||
Node* node = Find(bucket, key, nullptr);
|
||||
if (!node)
|
||||
{
|
||||
auto* fresh = new Node{static_cast<Node*>(retro_atomic_load_acquire_ptr(bucket)), key, {}};
|
||||
retro_atomic_ptr_init(&fresh->value, nullptr);
|
||||
for (;;)
|
||||
{
|
||||
if (retro_atomic_cas_ptr(bucket, fresh->next, fresh))
|
||||
{
|
||||
node = fresh;
|
||||
break;
|
||||
}
|
||||
// Another thread pushed meanwhile, maybe this very key.
|
||||
Node* const head = static_cast<Node*>(retro_atomic_load_acquire_ptr(bucket));
|
||||
if ((node = Find(bucket, key, fresh->next)) != nullptr)
|
||||
{
|
||||
delete fresh;
|
||||
break;
|
||||
}
|
||||
fresh->next = head;
|
||||
}
|
||||
}
|
||||
Retire(retro_atomic_exchange_ptr(&node->value, value));
|
||||
quiesce_exit(&m_quiesce);
|
||||
}
|
||||
|
||||
mutable retro_atomic_ptr_t m_buckets[BUCKETS]{};
|
||||
mutable quiesce_t m_quiesce{};
|
||||
};
|
||||
|
||||
// The bare form: each key gets one atomic pointer slot, inserted on first use and never
|
||||
// removed, and the caller decides what the pointer means and when what it points to may go.
|
||||
template <typename K, size_t BUCKETS = 1024, typename Hash = std::hash<K>>
|
||||
class AtomicSlotMap final
|
||||
{
|
||||
public:
|
||||
AtomicSlotMap() = default;
|
||||
AtomicSlotMap(const AtomicSlotMap&) = delete;
|
||||
AtomicSlotMap& operator=(const AtomicSlotMap&) = delete;
|
||||
|
||||
~AtomicSlotMap()
|
||||
{
|
||||
for (auto& bucket : m_buckets)
|
||||
{
|
||||
auto* node = static_cast<Node*>(retro_atomic_load_acquire_ptr(&bucket));
|
||||
while (node)
|
||||
{
|
||||
Node* const next = node->next;
|
||||
delete node;
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The slot for key, made empty (null) the first time.
|
||||
retro_atomic_ptr_t* Slot(const K& key)
|
||||
{
|
||||
retro_atomic_ptr_t* const bucket = &m_buckets[Hash{}(key) % BUCKETS];
|
||||
auto* head = static_cast<Node*>(retro_atomic_load_acquire_ptr(bucket));
|
||||
if (Node* node = Find(head, nullptr, key))
|
||||
return &node->slot;
|
||||
|
||||
auto* fresh = new Node{head, key, {}};
|
||||
retro_atomic_ptr_init(&fresh->slot, nullptr);
|
||||
while (!retro_atomic_cas_ptr(bucket, fresh->next, fresh))
|
||||
{
|
||||
// Another thread pushed meanwhile, maybe this very key.
|
||||
auto* newer = static_cast<Node*>(retro_atomic_load_acquire_ptr(bucket));
|
||||
if (Node* node = Find(newer, fresh->next, key))
|
||||
{
|
||||
delete fresh;
|
||||
return &node->slot;
|
||||
}
|
||||
fresh->next = newer;
|
||||
}
|
||||
return &fresh->slot;
|
||||
}
|
||||
|
||||
// Calls fn(slot) for every slot; for teardown, when no other thread uses the map.
|
||||
template <typename Fn>
|
||||
void ForEach(Fn&& fn)
|
||||
{
|
||||
for (auto& bucket : m_buckets)
|
||||
{
|
||||
for (auto* node = static_cast<Node*>(retro_atomic_load_acquire_ptr(&bucket)); node;
|
||||
node = node->next)
|
||||
{
|
||||
fn(&node->slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Node
|
||||
{
|
||||
Node* next; // fixed once published
|
||||
K key;
|
||||
retro_atomic_ptr_t slot;
|
||||
};
|
||||
|
||||
static Node* Find(Node* node, const Node* stop, const K& key)
|
||||
{
|
||||
for (; node != stop; node = node->next)
|
||||
{
|
||||
if (node->key == key)
|
||||
return node;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
retro_atomic_ptr_t m_buckets[BUCKETS]{};
|
||||
};
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,229 @@
|
||||
/* Copyright 2026 Dolphin Emulator Project
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "Common/AtomicMap.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct atomic_map_node
|
||||
{
|
||||
struct atomic_map_node *next; /* fixed once published */
|
||||
retro_atomic_ptr_t slot;
|
||||
size_t len;
|
||||
unsigned char key[1]; /* len bytes */
|
||||
} atomic_map_node_t;
|
||||
|
||||
struct atomic_slot_map
|
||||
{
|
||||
size_t buckets;
|
||||
retro_atomic_ptr_t heads[1]; /* buckets entries */
|
||||
};
|
||||
|
||||
struct atomic_value_map
|
||||
{
|
||||
atomic_slot_map_t *slots;
|
||||
quiesce_free_fn free_value;
|
||||
quiesce_t quiesce;
|
||||
};
|
||||
|
||||
/* FNV-1a */
|
||||
static size_t atomic_map_hash(const void *key, size_t len)
|
||||
{
|
||||
const unsigned char *bytes = (const unsigned char*)key;
|
||||
unsigned long hash = 2166136261UL;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
hash ^= bytes[i];
|
||||
hash = (hash * 16777619UL) & 0xffffffffUL;
|
||||
}
|
||||
return (size_t)hash;
|
||||
}
|
||||
|
||||
static atomic_map_node_t *atomic_map_find(atomic_map_node_t *node,
|
||||
const atomic_map_node_t *stop, const void *key, size_t len)
|
||||
{
|
||||
for (; node != stop; node = node->next)
|
||||
{
|
||||
if (node->len == len && memcmp(node->key, key, len) == 0)
|
||||
return node;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static retro_atomic_ptr_t *atomic_map_bucket(atomic_slot_map_t *map,
|
||||
const void *key, size_t len)
|
||||
{
|
||||
return &map->heads[atomic_map_hash(key, len) % map->buckets];
|
||||
}
|
||||
|
||||
atomic_slot_map_t *atomic_slot_map_new(size_t buckets)
|
||||
{
|
||||
atomic_slot_map_t *map;
|
||||
size_t i;
|
||||
|
||||
if (buckets == 0)
|
||||
buckets = 1;
|
||||
map = (atomic_slot_map_t*)malloc(sizeof(*map)
|
||||
+ (buckets - 1) * sizeof(map->heads[0]));
|
||||
if (!map)
|
||||
return NULL;
|
||||
map->buckets = buckets;
|
||||
for (i = 0; i < buckets; i++)
|
||||
retro_atomic_ptr_init(&map->heads[i], NULL);
|
||||
return map;
|
||||
}
|
||||
|
||||
void atomic_slot_map_free(atomic_slot_map_t *map)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!map)
|
||||
return;
|
||||
for (i = 0; i < map->buckets; i++)
|
||||
{
|
||||
atomic_map_node_t *node =
|
||||
(atomic_map_node_t*)retro_atomic_load_acquire_ptr(&map->heads[i]);
|
||||
while (node)
|
||||
{
|
||||
atomic_map_node_t *next = node->next;
|
||||
free(node);
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
free(map);
|
||||
}
|
||||
|
||||
retro_atomic_ptr_t *atomic_slot_map_find(atomic_slot_map_t *map,
|
||||
const void *key, size_t len)
|
||||
{
|
||||
atomic_map_node_t *node = atomic_map_find(
|
||||
(atomic_map_node_t*)retro_atomic_load_acquire_ptr(
|
||||
atomic_map_bucket(map, key, len)), NULL, key, len);
|
||||
return node ? &node->slot : NULL;
|
||||
}
|
||||
|
||||
retro_atomic_ptr_t *atomic_slot_map_slot(atomic_slot_map_t *map,
|
||||
const void *key, size_t len)
|
||||
{
|
||||
retro_atomic_ptr_t *bucket = atomic_map_bucket(map, key, len);
|
||||
atomic_map_node_t *head =
|
||||
(atomic_map_node_t*)retro_atomic_load_acquire_ptr(bucket);
|
||||
atomic_map_node_t *node = atomic_map_find(head, NULL, key, len);
|
||||
atomic_map_node_t *fresh;
|
||||
|
||||
if (node)
|
||||
return &node->slot;
|
||||
|
||||
fresh = (atomic_map_node_t*)malloc(sizeof(*fresh) + (len ? len - 1 : 0));
|
||||
if (!fresh)
|
||||
return NULL;
|
||||
fresh->next = head;
|
||||
retro_atomic_ptr_init(&fresh->slot, NULL);
|
||||
fresh->len = len;
|
||||
if (len)
|
||||
memcpy(fresh->key, key, len);
|
||||
|
||||
while (!retro_atomic_cas_ptr(bucket, fresh->next, fresh))
|
||||
{
|
||||
/* Another thread pushed meanwhile, maybe this very key. */
|
||||
atomic_map_node_t *newer =
|
||||
(atomic_map_node_t*)retro_atomic_load_acquire_ptr(bucket);
|
||||
node = atomic_map_find(newer, fresh->next, key, len);
|
||||
if (node)
|
||||
{
|
||||
free(fresh);
|
||||
return &node->slot;
|
||||
}
|
||||
fresh->next = newer;
|
||||
}
|
||||
return &fresh->slot;
|
||||
}
|
||||
|
||||
void atomic_slot_map_for_each(atomic_slot_map_t *map,
|
||||
void (*fn)(retro_atomic_ptr_t *slot, void *ctx), void *ctx)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < map->buckets; i++)
|
||||
{
|
||||
atomic_map_node_t *node =
|
||||
(atomic_map_node_t*)retro_atomic_load_acquire_ptr(&map->heads[i]);
|
||||
for (; node; node = node->next)
|
||||
fn(&node->slot, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_value_map_t *atomic_value_map_new(size_t buckets, quiesce_free_fn free_value)
|
||||
{
|
||||
atomic_value_map_t *map = (atomic_value_map_t*)malloc(sizeof(*map));
|
||||
|
||||
if (!map)
|
||||
return NULL;
|
||||
map->slots = atomic_slot_map_new(buckets);
|
||||
if (!map->slots)
|
||||
{
|
||||
free(map);
|
||||
return NULL;
|
||||
}
|
||||
map->free_value = free_value;
|
||||
quiesce_init(&map->quiesce);
|
||||
return map;
|
||||
}
|
||||
|
||||
static void atomic_value_map_free_slot(retro_atomic_ptr_t *slot, void *ctx)
|
||||
{
|
||||
atomic_value_map_t *map = (atomic_value_map_t*)ctx;
|
||||
quiesce_node_t *value = (quiesce_node_t*)retro_atomic_load_acquire_ptr(slot);
|
||||
|
||||
if (value)
|
||||
map->free_value(value);
|
||||
}
|
||||
|
||||
void atomic_value_map_free(atomic_value_map_t *map)
|
||||
{
|
||||
if (!map)
|
||||
return;
|
||||
atomic_slot_map_for_each(map->slots, atomic_value_map_free_slot, map);
|
||||
quiesce_deinit(&map->quiesce);
|
||||
atomic_slot_map_free(map->slots);
|
||||
free(map);
|
||||
}
|
||||
|
||||
void atomic_value_map_enter(atomic_value_map_t *map)
|
||||
{
|
||||
quiesce_enter(&map->quiesce);
|
||||
}
|
||||
|
||||
void atomic_value_map_exit(atomic_value_map_t *map)
|
||||
{
|
||||
quiesce_exit(&map->quiesce);
|
||||
}
|
||||
|
||||
quiesce_node_t *atomic_value_map_get(atomic_value_map_t *map,
|
||||
const void *key, size_t len)
|
||||
{
|
||||
retro_atomic_ptr_t *slot = atomic_slot_map_find(map->slots, key, len);
|
||||
return slot ? (quiesce_node_t*)retro_atomic_load_acquire_ptr(slot) : NULL;
|
||||
}
|
||||
|
||||
void atomic_value_map_set(atomic_value_map_t *map,
|
||||
const void *key, size_t len, quiesce_node_t *value)
|
||||
{
|
||||
retro_atomic_ptr_t *slot;
|
||||
quiesce_node_t *old = NULL;
|
||||
|
||||
quiesce_enter(&map->quiesce);
|
||||
/* Erasing a key that was never added adds nothing. */
|
||||
slot = value ? atomic_slot_map_slot(map->slots, key, len)
|
||||
: atomic_slot_map_find(map->slots, key, len);
|
||||
if (slot)
|
||||
old = (quiesce_node_t*)retro_atomic_exchange_ptr(slot, value);
|
||||
else if (value)
|
||||
map->free_value(value);
|
||||
if (old)
|
||||
quiesce_retire(&map->quiesce, old, map->free_value);
|
||||
quiesce_exit(&map->quiesce);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* Copyright 2026 Dolphin Emulator Project
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#ifndef DOLPHIN_COMMON_ATOMICMAP_H
|
||||
#define DOLPHIN_COMMON_ATOMICMAP_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include "Common/Quiesce.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Maps any thread may read and write without a lock, keyed by bytes.
|
||||
* A key is only ever added: its node is pushed at the head of its
|
||||
* bucket with a CAS and stays until the map is freed. */
|
||||
|
||||
/* The bare form: each key has one atomic pointer slot, null when made;
|
||||
* the caller decides what the pointer means and when what it points
|
||||
* to may go. */
|
||||
typedef struct atomic_slot_map atomic_slot_map_t;
|
||||
|
||||
atomic_slot_map_t *atomic_slot_map_new(size_t buckets);
|
||||
void atomic_slot_map_free(atomic_slot_map_t *map);
|
||||
|
||||
/* The key's slot, made the first time; NULL only when out of memory. */
|
||||
retro_atomic_ptr_t *atomic_slot_map_slot(atomic_slot_map_t *map,
|
||||
const void *key, size_t len);
|
||||
/* The key's slot if it has one, NULL otherwise. */
|
||||
retro_atomic_ptr_t *atomic_slot_map_find(atomic_slot_map_t *map,
|
||||
const void *key, size_t len);
|
||||
/* Calls fn on every slot; for teardown, while no other thread uses the map. */
|
||||
void atomic_slot_map_for_each(atomic_slot_map_t *map,
|
||||
void (*fn)(retro_atomic_ptr_t *slot, void *ctx), void *ctx);
|
||||
|
||||
/* A map of values. A value starts with its quiesce_node_t; it is
|
||||
* published whole, and one replaced or erased is freed with free_value
|
||||
* once no reader is inside the map. */
|
||||
typedef struct atomic_value_map atomic_value_map_t;
|
||||
|
||||
atomic_value_map_t *atomic_value_map_new(size_t buckets, quiesce_free_fn free_value);
|
||||
/* Frees the values still held. */
|
||||
void atomic_value_map_free(atomic_value_map_t *map);
|
||||
|
||||
/* Readers bracket atomic_value_map_get and every use of what it
|
||||
* returned with these. */
|
||||
void atomic_value_map_enter(atomic_value_map_t *map);
|
||||
void atomic_value_map_exit(atomic_value_map_t *map);
|
||||
quiesce_node_t *atomic_value_map_get(atomic_value_map_t *map,
|
||||
const void *key, size_t len);
|
||||
|
||||
/* Takes value, which may be NULL to erase. */
|
||||
void atomic_value_map_set(atomic_value_map_t *map,
|
||||
const void *key, size_t len, quiesce_node_t *value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -357,21 +357,28 @@ static T* Publish(retro_atomic_ptr_t* slot, ComPtr<T>&& state)
|
||||
return static_cast<T*>(retro_atomic_load_acquire_ptr(slot));
|
||||
}
|
||||
|
||||
static void ReleaseSlot(retro_atomic_ptr_t* slot, void* ctx)
|
||||
{
|
||||
IUnknown* state = static_cast<IUnknown*>(retro_atomic_exchange_ptr(slot, nullptr));
|
||||
if (state)
|
||||
state->Release();
|
||||
}
|
||||
|
||||
StateCache::~StateCache()
|
||||
{
|
||||
const auto release = [](retro_atomic_ptr_t* slot) {
|
||||
if (auto* state = static_cast<IUnknown*>(retro_atomic_exchange_ptr(slot, nullptr)))
|
||||
state->Release();
|
||||
};
|
||||
m_sampler.ForEach(release);
|
||||
m_blend.ForEach(release);
|
||||
m_raster.ForEach(release);
|
||||
m_depth.ForEach(release);
|
||||
atomic_slot_map_t* maps[4] = {m_sampler, m_blend, m_raster, m_depth};
|
||||
int i;
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
atomic_slot_map_for_each(maps[i], ReleaseSlot, nullptr);
|
||||
atomic_slot_map_free(maps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ID3D11SamplerState* StateCache::Get(SamplerState state)
|
||||
{
|
||||
retro_atomic_ptr_t* const slot = m_sampler.Slot(state);
|
||||
const u64 key = state.Hex();
|
||||
retro_atomic_ptr_t* const slot = atomic_slot_map_slot(m_sampler, &key, sizeof(key));
|
||||
if (auto* cached = static_cast<ID3D11SamplerState*>(retro_atomic_load_acquire_ptr(slot)))
|
||||
return cached;
|
||||
|
||||
@@ -421,7 +428,7 @@ ID3D11SamplerState* StateCache::Get(SamplerState state)
|
||||
|
||||
ID3D11BlendState* StateCache::Get(BlendingState state)
|
||||
{
|
||||
retro_atomic_ptr_t* const slot = m_blend.Slot(state.hex);
|
||||
retro_atomic_ptr_t* const slot = atomic_slot_map_slot(m_blend, &state.hex, sizeof(state.hex));
|
||||
if (auto* cached = static_cast<ID3D11BlendState*>(retro_atomic_load_acquire_ptr(slot)))
|
||||
return cached;
|
||||
|
||||
@@ -497,7 +504,7 @@ ID3D11BlendState* StateCache::Get(BlendingState state)
|
||||
|
||||
ID3D11RasterizerState* StateCache::Get(RasterizationState state)
|
||||
{
|
||||
retro_atomic_ptr_t* const slot = m_raster.Slot(state.hex);
|
||||
retro_atomic_ptr_t* const slot = atomic_slot_map_slot(m_raster, &state.hex, sizeof(state.hex));
|
||||
if (auto* cached = static_cast<ID3D11RasterizerState*>(retro_atomic_load_acquire_ptr(slot)))
|
||||
return cached;
|
||||
|
||||
@@ -517,7 +524,7 @@ ID3D11RasterizerState* StateCache::Get(RasterizationState state)
|
||||
|
||||
ID3D11DepthStencilState* StateCache::Get(DepthState state)
|
||||
{
|
||||
retro_atomic_ptr_t* const slot = m_depth.Slot(state.hex);
|
||||
retro_atomic_ptr_t* const slot = atomic_slot_map_slot(m_depth, &state.hex, sizeof(state.hex));
|
||||
if (auto* cached = static_cast<ID3D11DepthStencilState*>(retro_atomic_load_acquire_ptr(slot)))
|
||||
return cached;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include "Common/AtomicHashMap.h"
|
||||
#include "Common/AtomicMap.h"
|
||||
#include "Common/BitField.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoBackends/D3D/D3DBase.h"
|
||||
@@ -39,11 +39,11 @@ public:
|
||||
|
||||
private:
|
||||
// Filled from the video thread and the shader compile workers alike; each slot holds one
|
||||
// reference to its state object.
|
||||
Common::AtomicSlotMap<u32> m_depth;
|
||||
Common::AtomicSlotMap<u32> m_raster;
|
||||
Common::AtomicSlotMap<u32> m_blend;
|
||||
Common::AtomicSlotMap<SamplerState> m_sampler;
|
||||
// reference to its state object. Keyed by the state's hex word(s).
|
||||
atomic_slot_map_t* m_depth = atomic_slot_map_new(1024);
|
||||
atomic_slot_map_t* m_raster = atomic_slot_map_new(1024);
|
||||
atomic_slot_map_t* m_blend = atomic_slot_map_new(1024);
|
||||
atomic_slot_map_t* m_sampler = atomic_slot_map_new(1024);
|
||||
};
|
||||
|
||||
namespace D3D
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "VideoBackends/OGL/ProgramShaderCache.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -49,10 +48,10 @@ static std::unique_ptr<StreamBuffer> s_buffer;
|
||||
static int num_failures = 0;
|
||||
|
||||
static GLuint CurrentProgram = 0;
|
||||
ProgramShaderCache::PipelineProgramMap ProgramShaderCache::s_pipeline_programs;
|
||||
atomic_slot_map_t* ProgramShaderCache::s_pipeline_programs;
|
||||
quiesce_t ProgramShaderCache::s_pipeline_program_quiesce;
|
||||
static std::string s_glsl_header;
|
||||
static std::atomic<u64> s_shader_counter{0};
|
||||
static retro_atomic_64_t s_shader_counter;
|
||||
static thread_local bool s_is_shared_context = false;
|
||||
|
||||
static std::string GetGLSLVersionString()
|
||||
@@ -203,12 +202,11 @@ bool PipelineProgramKey::operator<(const PipelineProgramKey& rhs) const
|
||||
std::tie(rhs.vertex_shader_id, rhs.geometry_shader_id, rhs.pixel_shader_id);
|
||||
}
|
||||
|
||||
std::size_t PipelineProgramKeyHash::operator()(const PipelineProgramKey& key) const
|
||||
static_assert(sizeof(PipelineProgramKey) == 3 * sizeof(u64), "the key's bytes are the key");
|
||||
|
||||
static void AssertSlotEmpty(retro_atomic_ptr_t* slot, void* ctx)
|
||||
{
|
||||
// We would really want std::hash_combine for this..
|
||||
std::hash<u64> hasher;
|
||||
return hasher(key.vertex_shader_id) + hasher(key.geometry_shader_id) +
|
||||
hasher(key.pixel_shader_id);
|
||||
DEBUG_ASSERT(!retro_atomic_load_acquire_ptr(slot));
|
||||
}
|
||||
|
||||
StreamBuffer* ProgramShaderCache::GetUniformBuffer()
|
||||
@@ -438,6 +436,8 @@ bool ProgramShaderCache::CheckProgramLinkResult(GLuint id, std::string_view vcod
|
||||
|
||||
void ProgramShaderCache::Init()
|
||||
{
|
||||
s_pipeline_programs = atomic_slot_map_new(1024);
|
||||
|
||||
// We have to get the UBO alignment here because
|
||||
// if we generate a buffer that isn't aligned
|
||||
// then the UBO will fail.
|
||||
@@ -472,9 +472,9 @@ void ProgramShaderCache::Shutdown()
|
||||
|
||||
// All pipeline programs should have been released.
|
||||
// Every pipeline is gone by now, and with it every program.
|
||||
s_pipeline_programs.ForEach([](retro_atomic_ptr_t* slot) {
|
||||
DEBUG_ASSERT(!retro_atomic_load_acquire_ptr(slot));
|
||||
});
|
||||
atomic_slot_map_for_each(s_pipeline_programs, AssertSlotEmpty, nullptr);
|
||||
atomic_slot_map_free(s_pipeline_programs);
|
||||
s_pipeline_programs = nullptr;
|
||||
quiesce_deinit(&s_pipeline_program_quiesce);
|
||||
}
|
||||
|
||||
@@ -543,7 +543,7 @@ PipelineProgram* ProgramShaderCache::GetPipelineProgram(const GLVertexFormat* ve
|
||||
PipelineProgramKey key = {vertex_shader ? vertex_shader->GetID() : 0,
|
||||
geometry_shader ? geometry_shader->GetID() : 0,
|
||||
pixel_shader ? pixel_shader->GetID() : 0};
|
||||
retro_atomic_ptr_t* const slot = s_pipeline_programs.Slot(key);
|
||||
retro_atomic_ptr_t* const slot = atomic_slot_map_slot(s_pipeline_programs, &key, sizeof(key));
|
||||
{
|
||||
quiesce_enter(&s_pipeline_program_quiesce);
|
||||
auto* existing = static_cast<PipelineProgram*>(retro_atomic_load_acquire_ptr(slot));
|
||||
@@ -652,13 +652,14 @@ PipelineProgram* ProgramShaderCache::GetPipelineProgram(const GLVertexFormat* ve
|
||||
|
||||
bool ProgramShaderCache::TryReference(PipelineProgram* prog)
|
||||
{
|
||||
std::size_t count = prog->reference_count.load();
|
||||
while (count != 0)
|
||||
for (;;)
|
||||
{
|
||||
if (prog->reference_count.compare_exchange_weak(count, count + 1))
|
||||
const int count = retro_atomic_load_acquire_int(&prog->reference_count);
|
||||
if (count == 0)
|
||||
return false;
|
||||
if (retro_atomic_cas_int(&prog->reference_count, count, count + 1))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProgramShaderCache::FreePipelineProgram(quiesce_node_t* node)
|
||||
@@ -668,13 +669,14 @@ void ProgramShaderCache::FreePipelineProgram(quiesce_node_t* node)
|
||||
|
||||
void ProgramShaderCache::ReleasePipelineProgram(PipelineProgram* prog)
|
||||
{
|
||||
if (--prog->reference_count > 0)
|
||||
if (retro_atomic_fetch_sub_int(&prog->reference_count, 1) > 1)
|
||||
return;
|
||||
|
||||
prog->shader.Destroy();
|
||||
|
||||
// Unless a newer program already took its slot.
|
||||
retro_atomic_cas_ptr(s_pipeline_programs.Slot(prog->key), prog, nullptr);
|
||||
retro_atomic_cas_ptr(atomic_slot_map_slot(s_pipeline_programs, &prog->key, sizeof(prog->key)),
|
||||
prog, nullptr);
|
||||
quiesce_retire(&s_pipeline_program_quiesce, &prog->node, FreePipelineProgram);
|
||||
}
|
||||
|
||||
@@ -910,7 +912,7 @@ void ProgramShaderCache::CreateHeader()
|
||||
|
||||
u64 ProgramShaderCache::GenerateShaderID()
|
||||
{
|
||||
return s_shader_counter++;
|
||||
return static_cast<u64>(retro_atomic_fetch_add_64(&s_shader_counter, 1));
|
||||
}
|
||||
|
||||
bool SharedContextAsyncShaderCompiler::WorkerThreadInitMainThread(void** param)
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Common/AtomicHashMap.h"
|
||||
#include "Common/Quiesce.h"
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#include "Common/AtomicMap.h"
|
||||
#include "Common/GL/GLExtensions/GLExtensions.h"
|
||||
#include "Common/Quiesce.h"
|
||||
#include "VideoCommon/AsyncShaderCompiler.h"
|
||||
|
||||
namespace OGL
|
||||
@@ -52,17 +53,12 @@ struct PipelineProgramKey
|
||||
bool operator<(const PipelineProgramKey& rhs) const;
|
||||
};
|
||||
|
||||
struct PipelineProgramKeyHash
|
||||
{
|
||||
std::size_t operator()(const PipelineProgramKey& key) const;
|
||||
};
|
||||
|
||||
struct PipelineProgram
|
||||
{
|
||||
quiesce_node_t node{}; // first: the free callback casts it back
|
||||
PipelineProgramKey key{};
|
||||
SHADER shader;
|
||||
std::atomic_size_t reference_count{1};
|
||||
retro_atomic_int_t reference_count{1};
|
||||
bool binary_retrieved = false;
|
||||
};
|
||||
|
||||
@@ -109,14 +105,12 @@ private:
|
||||
// Programs are made on the video thread and on the compile workers' shared contexts: one
|
||||
// slot per key, holding the live program. A program whose count fell to 0 is dead and may
|
||||
// be replaced; it is freed once no thread is looking at it.
|
||||
using PipelineProgramMap =
|
||||
Common::AtomicSlotMap<PipelineProgramKey, 1024, PipelineProgramKeyHash>;
|
||||
static bool TryReference(PipelineProgram* prog);
|
||||
static void FreePipelineProgram(quiesce_node_t* node);
|
||||
|
||||
static void CreateAttributelessVAO();
|
||||
|
||||
static PipelineProgramMap s_pipeline_programs;
|
||||
static atomic_slot_map_t* s_pipeline_programs; // keyed by the PipelineProgramKey bytes
|
||||
static quiesce_t s_pipeline_program_quiesce;
|
||||
|
||||
static u32 s_ubo_buffer_size;
|
||||
|
||||
@@ -35,8 +35,43 @@ std::size_t GetAssetSize(const CustomTextureData& data)
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// The maps' values: each begins with the node the map frees it through.
|
||||
struct AssetMapValue
|
||||
{
|
||||
quiesce_node_t node;
|
||||
Assets::AssetMap map;
|
||||
};
|
||||
|
||||
struct AssetIDValue
|
||||
{
|
||||
quiesce_node_t node;
|
||||
CustomAssetLibrary::AssetID asset_id;
|
||||
};
|
||||
|
||||
void FreeAssetMapValue(quiesce_node_t* node)
|
||||
{
|
||||
delete reinterpret_cast<AssetMapValue*>(node);
|
||||
}
|
||||
|
||||
void FreeAssetIDValue(quiesce_node_t* node)
|
||||
{
|
||||
delete reinterpret_cast<AssetIDValue*>(node);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DirectFilesystemAssetLibrary::DirectFilesystemAssetLibrary()
|
||||
: m_asset_id_to_asset_map_path(atomic_value_map_new(4096, FreeAssetMapValue)),
|
||||
m_path_to_asset_id(atomic_value_map_new(4096, FreeAssetIDValue))
|
||||
{
|
||||
}
|
||||
|
||||
DirectFilesystemAssetLibrary::~DirectFilesystemAssetLibrary()
|
||||
{
|
||||
atomic_value_map_free(m_path_to_asset_id);
|
||||
atomic_value_map_free(m_asset_id_to_asset_map_path);
|
||||
}
|
||||
|
||||
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
|
||||
CustomTextureData* data)
|
||||
{
|
||||
@@ -70,31 +105,67 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
|
||||
void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id,
|
||||
VideoCommon::Assets::AssetMap asset_path_map)
|
||||
{
|
||||
if (const auto previous_asset_map = m_asset_id_to_asset_map_path.Get(asset_id))
|
||||
AssetMapValue* value;
|
||||
|
||||
atomic_value_map_enter(m_asset_id_to_asset_map_path);
|
||||
value = reinterpret_cast<AssetMapValue*>(
|
||||
atomic_value_map_get(m_asset_id_to_asset_map_path, asset_id.data(), asset_id.size()));
|
||||
if (value)
|
||||
{
|
||||
for (const auto& [name, path] : *previous_asset_map)
|
||||
m_path_to_asset_id.Erase(PathToString(path));
|
||||
for (const auto& [name, path] : value->map)
|
||||
{
|
||||
const std::string key = PathToString(path);
|
||||
atomic_value_map_set(m_path_to_asset_id, key.data(), key.size(), nullptr);
|
||||
}
|
||||
}
|
||||
atomic_value_map_exit(m_asset_id_to_asset_map_path);
|
||||
|
||||
for (const auto& [name, path] : asset_path_map)
|
||||
m_path_to_asset_id.Set(PathToString(path), asset_id);
|
||||
{
|
||||
const std::string key = PathToString(path);
|
||||
AssetIDValue* id_value = new AssetIDValue{{}, asset_id};
|
||||
atomic_value_map_set(m_path_to_asset_id, key.data(), key.size(), &id_value->node);
|
||||
}
|
||||
|
||||
m_asset_id_to_asset_map_path.Set(asset_id, std::move(asset_path_map));
|
||||
value = new AssetMapValue{{}, std::move(asset_path_map)};
|
||||
atomic_value_map_set(m_asset_id_to_asset_map_path, asset_id.data(), asset_id.size(),
|
||||
&value->node);
|
||||
}
|
||||
|
||||
void DirectFilesystemAssetLibrary::PathModified(std::string_view path)
|
||||
{
|
||||
if (const auto asset_id = m_path_to_asset_id.Get(path))
|
||||
AssetID asset_id;
|
||||
bool found = false;
|
||||
|
||||
atomic_value_map_enter(m_path_to_asset_id);
|
||||
const AssetIDValue* value = reinterpret_cast<const AssetIDValue*>(
|
||||
atomic_value_map_get(m_path_to_asset_id, path.data(), path.size()));
|
||||
if (value)
|
||||
{
|
||||
asset_id = value->asset_id;
|
||||
found = true;
|
||||
}
|
||||
atomic_value_map_exit(m_path_to_asset_id);
|
||||
|
||||
if (found)
|
||||
{
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& resource_manager = system.GetCustomResourceManager();
|
||||
resource_manager.MarkAssetDirty(*asset_id);
|
||||
resource_manager.MarkAssetDirty(asset_id);
|
||||
}
|
||||
}
|
||||
|
||||
VideoCommon::Assets::AssetMap
|
||||
DirectFilesystemAssetLibrary::GetAssetMapForID(const AssetID& asset_id) const
|
||||
{
|
||||
return m_asset_id_to_asset_map_path.Get(asset_id).value_or(VideoCommon::Assets::AssetMap{});
|
||||
VideoCommon::Assets::AssetMap result;
|
||||
|
||||
atomic_value_map_enter(m_asset_id_to_asset_map_path);
|
||||
const AssetMapValue* value = reinterpret_cast<const AssetMapValue*>(
|
||||
atomic_value_map_get(m_asset_id_to_asset_map_path, asset_id.data(), asset_id.size()));
|
||||
if (value)
|
||||
result = value->map;
|
||||
atomic_value_map_exit(m_asset_id_to_asset_map_path);
|
||||
return result;
|
||||
}
|
||||
} // namespace VideoCommon
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "VideoCommon/Assets/Types.h"
|
||||
#include "VideoCommon/Assets/WatchableFilesystemAssetLibrary.h"
|
||||
|
||||
#include "Common/AtomicHashMap.h"
|
||||
#include "Common/AtomicMap.h"
|
||||
|
||||
namespace VideoCommon
|
||||
{
|
||||
@@ -21,6 +21,11 @@ namespace VideoCommon
|
||||
class DirectFilesystemAssetLibrary final : public WatchableFilesystemAssetLibrary
|
||||
{
|
||||
public:
|
||||
DirectFilesystemAssetLibrary();
|
||||
~DirectFilesystemAssetLibrary() override;
|
||||
DirectFilesystemAssetLibrary(const DirectFilesystemAssetLibrary&) = delete;
|
||||
DirectFilesystemAssetLibrary& operator=(const DirectFilesystemAssetLibrary&) = delete;
|
||||
|
||||
LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override;
|
||||
|
||||
// Assigns the asset id to a map of files, how this map is read is dependent on the data
|
||||
@@ -35,10 +40,8 @@ private:
|
||||
Assets::AssetMap GetAssetMapForID(const AssetID& asset_id) const;
|
||||
|
||||
// Written while mods and texture packs load, read by the loader threads and the file
|
||||
// watcher: lock-free maps, each value swapped whole.
|
||||
Common::AtomicHashMap<AssetID, Assets::AssetMap, 4096, std::hash<std::string_view>>
|
||||
m_asset_id_to_asset_map_path;
|
||||
Common::AtomicHashMap<std::string, AssetID, 4096, std::hash<std::string_view>>
|
||||
m_path_to_asset_id;
|
||||
// watcher: lock-free maps keyed by the string's bytes, each value swapped whole.
|
||||
atomic_value_map_t* m_asset_id_to_asset_map_path; // AssetMapValue
|
||||
atomic_value_map_t* m_path_to_asset_id; // AssetIDValue
|
||||
};
|
||||
} // namespace VideoCommon
|
||||
Reference in new issue
Block a user