diff --git a/Makefile.common b/Makefile.common index 0a2f7830df..5cbed8d5f9 100644 --- a/Makefile.common +++ b/Makefile.common @@ -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 \ diff --git a/Source/Core/Common/AtomicHashMap.h b/Source/Core/Common/AtomicHashMap.h deleted file mode 100644 index 5453af2d68..0000000000 --- a/Source/Core/Common/AtomicHashMap.h +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2026 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include - -#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 > -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(retro_atomic_load_acquire_ptr(&bucket)); - while (node) - { - Node* const next = node->next; - delete static_cast(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 - std::optional Get(const Key& key) const - { - quiesce_enter(&m_quiesce); - std::optional result; - if (const Node* node = Find(Bucket(key), key, nullptr)) - { - if (const auto* value = static_cast(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 - 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 - retro_atomic_ptr_t* Bucket(const Key& key) const - { - return &m_buckets[Hash{}(key) % BUCKETS]; - } - - template - static Node* Find(retro_atomic_ptr_t* bucket, const Key& key, const Node* stop) - { - for (auto* node = static_cast(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(node); } - - void Retire(void* value) - { - if (value) - quiesce_retire(&m_quiesce, &static_cast(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(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(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 > -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(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(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(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 - void ForEach(Fn&& fn) - { - for (auto& bucket : m_buckets) - { - for (auto* node = static_cast(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 diff --git a/Source/Core/Common/AtomicMap.c b/Source/Core/Common/AtomicMap.c new file mode 100644 index 0000000000..8746cbf7c0 --- /dev/null +++ b/Source/Core/Common/AtomicMap.c @@ -0,0 +1,229 @@ +/* Copyright 2026 Dolphin Emulator Project + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "Common/AtomicMap.h" + +#include +#include + +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); +} diff --git a/Source/Core/Common/AtomicMap.h b/Source/Core/Common/AtomicMap.h new file mode 100644 index 0000000000..957aeba0a9 --- /dev/null +++ b/Source/Core/Common/AtomicMap.h @@ -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 + +#include + +#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 diff --git a/Source/Core/VideoBackends/D3D/D3DState.cpp b/Source/Core/VideoBackends/D3D/D3DState.cpp index 6b6fc4b888..3b09833a15 100644 --- a/Source/Core/VideoBackends/D3D/D3DState.cpp +++ b/Source/Core/VideoBackends/D3D/D3DState.cpp @@ -357,21 +357,28 @@ static T* Publish(retro_atomic_ptr_t* slot, ComPtr&& state) return static_cast(retro_atomic_load_acquire_ptr(slot)); } +static void ReleaseSlot(retro_atomic_ptr_t* slot, void* ctx) +{ + IUnknown* state = static_cast(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(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(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(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(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(retro_atomic_load_acquire_ptr(slot))) return cached; diff --git a/Source/Core/VideoBackends/D3D/D3DState.h b/Source/Core/VideoBackends/D3D/D3DState.h index 07004bf228..fddc54daed 100644 --- a/Source/Core/VideoBackends/D3D/D3DState.h +++ b/Source/Core/VideoBackends/D3D/D3DState.h @@ -11,7 +11,7 @@ #include -#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 m_depth; - Common::AtomicSlotMap m_raster; - Common::AtomicSlotMap m_blend; - Common::AtomicSlotMap 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 diff --git a/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp b/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp index 18e60bb84f..39d7480e5c 100644 --- a/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp +++ b/Source/Core/VideoBackends/OGL/ProgramShaderCache.cpp @@ -4,7 +4,6 @@ #include "VideoBackends/OGL/ProgramShaderCache.h" #include -#include #include #include #include @@ -49,10 +48,10 @@ static std::unique_ptr 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 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 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(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(retro_atomic_fetch_add_64(&s_shader_counter, 1)); } bool SharedContextAsyncShaderCompiler::WorkerThreadInitMainThread(void** param) diff --git a/Source/Core/VideoBackends/OGL/ProgramShaderCache.h b/Source/Core/VideoBackends/OGL/ProgramShaderCache.h index 0f097f82a0..e4005cb7f0 100644 --- a/Source/Core/VideoBackends/OGL/ProgramShaderCache.h +++ b/Source/Core/VideoBackends/OGL/ProgramShaderCache.h @@ -3,14 +3,15 @@ #pragma once -#include #include #include #include -#include "Common/AtomicHashMap.h" -#include "Common/Quiesce.h" +#include + +#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; 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; diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp index 727a41c1b9..e42cc349ac 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp @@ -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(node); +} + +void FreeAssetIDValue(quiesce_node_t* node) +{ + delete reinterpret_cast(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( + 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( + 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( + 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 diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h index 34ae1bb461..e8c05104c8 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h @@ -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> - m_asset_id_to_asset_map_path; - Common::AtomicHashMap> - 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