From 681c78d3485357ff9fae6b98ad2c20c83c767aba Mon Sep 17 00:00:00 2001
From: Chris Robinson <chris.kcat@gmail.com>
Date: Wed, 11 Sep 2019 12:28:33 -0700
Subject: Allocate buffer batches separately from buffers

---
 al/buffer.cpp | 82 ++++++++++++++++++++++++++++++-----------------------------
 1 file changed, 42 insertions(+), 40 deletions(-)

diff --git a/al/buffer.cpp b/al/buffer.cpp
index 03b68038..3cf9a60f 100644
--- a/al/buffer.cpp
+++ b/al/buffer.cpp
@@ -34,6 +34,7 @@
 #include <memory>
 #include <mutex>
 #include <new>
+#include <numeric>
 #include <utility>
 
 #include "AL/al.h"
@@ -244,42 +245,41 @@ constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_M
     AL_MAP_PERSISTENT_BIT_SOFT)};
 
 
-ALbuffer *AllocBuffer(ALCcontext *context)
+bool EnsureBuffers(ALCdevice *device, size_t needed)
 {
-    ALCdevice *device{context->mDevice.get()};
-    std::lock_guard<std::mutex> _{device->BufferLock};
-    auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(),
-        [](const BufferSubList &entry) noexcept -> bool
-        { return entry.FreeMask != 0; }
-    );
+    size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), size_t{0},
+        [](size_t cur, const BufferSubList &sublist) noexcept -> size_t
+        { return cur + static_cast<ALuint>(POPCNT64(~sublist.FreeMask)); }
+    )};
 
-    auto lidx = static_cast<ALsizei>(std::distance(device->BufferList.begin(), sublist));
-    ALsizei slidx{0};
-    if LIKELY(sublist != device->BufferList.end())
-        slidx = CTZ64(sublist->FreeMask);
-    else
+    while(needed > count)
     {
-        /* Don't allocate so many list entries that the 32-bit ID could
-         * overflow...
-         */
         if UNLIKELY(device->BufferList.size() >= 1<<25)
-        {
-            context->setError(AL_OUT_OF_MEMORY, "Too many buffers allocated");
-            return nullptr;
-        }
+            return false;
+
         device->BufferList.emplace_back();
-        sublist = device->BufferList.end() - 1;
+        auto sublist = device->BufferList.end() - 1;
         sublist->FreeMask = ~0_u64;
-        sublist->Buffers = reinterpret_cast<ALbuffer*>(al_calloc(16, sizeof(ALbuffer)*64));
+        sublist->Buffers = static_cast<ALbuffer*>(al_calloc(alignof(ALbuffer), sizeof(ALbuffer)*64));
         if UNLIKELY(!sublist->Buffers)
         {
             device->BufferList.pop_back();
-            context->setError(AL_OUT_OF_MEMORY, "Failed to allocate buffer batch");
-            return nullptr;
+            return false;
         }
-
-        slidx = 0;
+        count += 64;
     }
+    return true;
+}
+
+ALbuffer *AllocBuffer(ALCdevice *device)
+{
+    auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(),
+        [](const BufferSubList &entry) noexcept -> bool
+        { return entry.FreeMask != 0; }
+    );
+
+    auto lidx = static_cast<ALuint>(std::distance(device->BufferList.begin(), sublist));
+    auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
 
     ALbuffer *buffer{::new (sublist->Buffers + slidx) ALbuffer{}};
 
@@ -293,9 +293,9 @@ ALbuffer *AllocBuffer(ALCcontext *context)
 
 void FreeBuffer(ALCdevice *device, ALbuffer *buffer)
 {
-    ALuint id{buffer->id - 1};
-    ALsizei lidx = id >> 6;
-    ALsizei slidx = id & 0x3f;
+    const ALuint id{buffer->id - 1};
+    const size_t lidx{id >> 6};
+    const ALuint slidx{id & 0x3f};
 
     al::destroy_at(buffer);
 
@@ -304,8 +304,8 @@ void FreeBuffer(ALCdevice *device, ALbuffer *buffer)
 
 inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id)
 {
-    ALuint lidx = (id-1) >> 6;
-    ALsizei slidx = (id-1) & 0x3f;
+    const size_t lidx{(id-1) >> 6};
+    const ALuint slidx{(id-1) & 0x3f};
 
     if UNLIKELY(lidx >= device->BufferList.size())
         return nullptr;
@@ -606,11 +606,19 @@ START_API_FUNC
         context->setError(AL_INVALID_VALUE, "Generating %d buffers", n);
     if UNLIKELY(n <= 0) return;
 
+    ALCdevice *device{context->mDevice.get()};
+    std::unique_lock<std::mutex> buflock{device->BufferLock};
+    if(!EnsureBuffers(device, static_cast<ALuint>(n)))
+    {
+        context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, (n==1)?"":"s");
+        return;
+    }
+
     if LIKELY(n == 1)
     {
         /* Special handling for the easy and normal case. */
-        ALbuffer *buffer = AllocBuffer(context.get());
-        if(buffer) buffers[0] = buffer->id;
+        ALbuffer *buffer{AllocBuffer(device)};
+        buffers[0] = buffer->id;
     }
     else
     {
@@ -618,15 +626,9 @@ START_API_FUNC
          * modifying the user storage in case of failure.
          */
         al::vector<ALuint> ids;
-        ids.reserve(n);
+        ids.reserve(static_cast<ALuint>(n));
         do {
-            ALbuffer *buffer = AllocBuffer(context.get());
-            if(!buffer)
-            {
-                alDeleteBuffers(static_cast<ALsizei>(ids.size()), ids.data());
-                return;
-            }
-
+            ALbuffer *buffer{AllocBuffer(device)};
             ids.emplace_back(buffer->id);
         } while(--n);
         std::copy(ids.begin(), ids.end(), buffers);
-- 
cgit v1.2.3