From 8a33d74c131462f0c283ba82797771415adad64d Mon Sep 17 00:00:00 2001
From: Jack Andersen <jackoalan@gmail.com>
Date: Fri, 18 Dec 2015 11:33:53 -1000
Subject: [PATCH] OS X thread-local fixes

---
 include/boo/graphicsdev/GL.hpp                |  2 +-
 .../boo/graphicsdev/IGraphicsDataFactory.hpp  | 23 ++++++
 include/boo/graphicsdev/Metal.hpp             |  6 +-
 lib/graphicsdev/GL.cpp                        | 78 +++++++++----------
 lib/graphicsdev/Metal.mm                      | 70 +++++++++++------
 5 files changed, 115 insertions(+), 64 deletions(-)

diff --git a/include/boo/graphicsdev/GL.hpp b/include/boo/graphicsdev/GL.hpp
index 845f98d..6e43b98 100644
--- a/include/boo/graphicsdev/GL.hpp
+++ b/include/boo/graphicsdev/GL.hpp
@@ -15,7 +15,7 @@ class GLDataFactory : public IGraphicsDataFactory
 {
     friend struct GLCommandQueue;
     IGraphicsContext* m_parent;
-    static thread_local struct GLData* m_deferredData;
+    static ThreadLocalPtr<struct GLData> m_deferredData;
     std::unordered_set<struct GLData*> m_committedData;
     std::mutex m_committedMutex;
     std::vector<int> m_texUnis;
diff --git a/include/boo/graphicsdev/IGraphicsDataFactory.hpp b/include/boo/graphicsdev/IGraphicsDataFactory.hpp
index c13d3f1..14f90e0 100644
--- a/include/boo/graphicsdev/IGraphicsDataFactory.hpp
+++ b/include/boo/graphicsdev/IGraphicsDataFactory.hpp
@@ -3,6 +3,7 @@
 
 #include <memory>
 #include <stdint.h>
+#include <pthread.h>
 #include "boo/System.hpp"
 
 namespace boo
@@ -226,6 +227,28 @@ private:
     virtual void destroyAllData()=0;
 };
 
+/** Multiplatform TLS-pointer wrapper (for compilers without proper thread_local support) */
+template <class T>
+class ThreadLocalPtr
+{
+#if _WIN32
+    DWORD m_key;
+public:
+    ThreadLocalPtr() {m_key = TlsAlloc();}
+    ~ThreadLocalPtr() {TlsFree(m_key);}
+    T* get() {return static_cast<T*>(TlsGetValue(m_key));}
+    void reset(T* v=nullptr) {TlsSetValue(m_key, v);}
+#else
+    pthread_key_t m_key;
+public:
+    ThreadLocalPtr() {pthread_key_create(&m_key, nullptr);}
+    ~ThreadLocalPtr() {pthread_key_delete(m_key);}
+    T* get() {return static_cast<T*>(pthread_getspecific(m_key));}
+    void reset(T* v=nullptr) {pthread_setspecific(m_key, v);}
+#endif
+    T* operator->() {return get();}
+};
+
 /** Ownership token for maintaining lifetime of factory-created resources
  *  deletion of this token triggers mass-deallocation of the factory's
  *  IGraphicsData (please don't delete and draw contained resources in the same frame). */
diff --git a/include/boo/graphicsdev/Metal.hpp b/include/boo/graphicsdev/Metal.hpp
index fec21eb..d08f9de 100644
--- a/include/boo/graphicsdev/Metal.hpp
+++ b/include/boo/graphicsdev/Metal.hpp
@@ -11,6 +11,7 @@
 #include "IGraphicsCommandQueue.hpp"
 #include "boo/IGraphicsContext.hpp"
 #include <vector>
+#include <mutex>
 #include <unordered_set>
 #include <unordered_map>
 
@@ -22,8 +23,9 @@ class MetalDataFactory : public IGraphicsDataFactory
 {
     friend struct MetalCommandQueue;
     IGraphicsContext* m_parent;
-    struct MetalData* m_deferredData = nullptr;
+    static ThreadLocalPtr<struct MetalData> m_deferredData;
     std::unordered_set<struct MetalData*> m_committedData;
+    std::mutex m_committedMutex;
     struct MetalContext* m_ctx;
     
     void destroyData(IGraphicsData*);
@@ -64,7 +66,7 @@ public:
                          size_t texCount, ITexture** texs);
     
     void reset();
-    IGraphicsDataToken commit();
+    GraphicsDataToken commit();
 };
 
 }
diff --git a/lib/graphicsdev/GL.cpp b/lib/graphicsdev/GL.cpp
index 4270dc7..9596b30 100644
--- a/lib/graphicsdev/GL.cpp
+++ b/lib/graphicsdev/GL.cpp
@@ -16,7 +16,7 @@ namespace boo
 {
 static LogVisor::LogModule Log("boo::GL");
 
-thread_local struct GLData* GLDataFactory::m_deferredData;
+ThreadLocalPtr<struct GLData> GLDataFactory::m_deferredData;
 struct GLData : IGraphicsData
 {
     std::vector<std::unique_ptr<class GLShaderPipeline>> m_SPs;
@@ -94,9 +94,9 @@ IGraphicsBufferS*
 GLDataFactory::newStaticBuffer(BufferUse use, const void* data, size_t stride, size_t count)
 {
     GLGraphicsBufferS* retval = new GLGraphicsBufferS(use, data, stride * count);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_SBufs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_SBufs.emplace_back(retval);
     return retval;
 }
 
@@ -105,9 +105,9 @@ GLDataFactory::newStaticBuffer(BufferUse use, std::unique_ptr<uint8_t[]>&& data,
 {
     std::unique_ptr<uint8_t[]> d = std::move(data);
     GLGraphicsBufferS* retval = new GLGraphicsBufferS(use, d.get(), stride * count);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_SBufs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_SBufs.emplace_back(retval);
     return retval;
 }
 
@@ -249,9 +249,9 @@ GLDataFactory::newStaticTexture(size_t width, size_t height, size_t mips, Textur
                                    const void* data, size_t sz)
 {
     GLTextureS* retval = new GLTextureS(width, height, mips, fmt, data, sz);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_STexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_STexs.emplace_back(retval);
     return retval;
 }
 
@@ -261,9 +261,9 @@ GLDataFactory::newStaticTexture(size_t width, size_t height, size_t mips, Textur
 {
     std::unique_ptr<uint8_t[]> d = std::move(data);
     GLTextureS* retval = new GLTextureS(width, height, mips, fmt, d.get(), sz);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_STexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_STexs.emplace_back(retval);
     return retval;
 }
 
@@ -272,9 +272,9 @@ GLDataFactory::newStaticArrayTexture(size_t width, size_t height, size_t layers,
                                      const void *data, size_t sz)
 {
     GLTextureSA* retval = new GLTextureSA(width, height, layers, fmt, data, sz);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_SATexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_SATexs.emplace_back(retval);
     return retval;
 }
 
@@ -473,9 +473,9 @@ IShaderPipeline* GLDataFactory::newShaderPipeline
     }
 
     GLShaderPipeline* retval = new GLShaderPipeline(std::move(shader));
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_SPs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_SPs.emplace_back(retval);
     return retval;
 }
 
@@ -569,9 +569,9 @@ GLDataFactory::newShaderDataBinding(IShaderPipeline* pipeline,
 {
     GLShaderDataBinding* retval =
     new GLShaderDataBinding(pipeline, vtxFormat, ubufCount, ubufs, texCount, texs);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_SBinds.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_SBinds.emplace_back(retval);
     return retval;
 }
 
@@ -580,21 +580,21 @@ GLDataFactory::GLDataFactory(IGraphicsContext* parent)
 
 void GLDataFactory::reset()
 {
-    delete static_cast<GLData*>(m_deferredData);
-    m_deferredData = nullptr;
+    delete m_deferredData.get();
+    m_deferredData.reset();
 }
 
 GraphicsDataToken GLDataFactory::commit()
 {
-    if (!m_deferredData)
+    if (!m_deferredData.get())
         return GraphicsDataToken(this, nullptr);
     std::unique_lock<std::mutex> lk(m_committedMutex);
-    GLData* retval = m_deferredData;
+    GLData* retval = m_deferredData.get();
 #ifndef NDEBUG
     for (std::unique_ptr<GLShaderDataBinding>& b : retval->m_SBinds)
         b->m_committed = true;
 #endif
-    m_deferredData = nullptr;
+    m_deferredData.reset();
     m_committedData.insert(retval);
     /* Let's go ahead and flush to ensure our data gets to the GPU
        While this isn't strictly required, some drivers might behave
@@ -1158,9 +1158,9 @@ GLDataFactory::newDynamicBuffer(BufferUse use, size_t stride, size_t count)
 {
     GLCommandQueue* q = static_cast<GLCommandQueue*>(m_parent->getCommandQueue());
     GLGraphicsBufferD* retval = new GLGraphicsBufferD(q, use, stride * count);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_DBufs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_DBufs.emplace_back(retval);
     return retval;
 }
 
@@ -1235,9 +1235,9 @@ GLDataFactory::newDynamicTexture(size_t width, size_t height, TextureFormat fmt)
 {
     GLCommandQueue* q = static_cast<GLCommandQueue*>(m_parent->getCommandQueue());
     GLTextureD* retval = new GLTextureD(q, width, height, fmt);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_DTexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_DTexs.emplace_back(retval);
     return retval;
 }
 
@@ -1259,9 +1259,9 @@ GLDataFactory::newRenderTexture(size_t width, size_t height, size_t samples)
     GLCommandQueue* q = static_cast<GLCommandQueue*>(m_parent->getCommandQueue());
     GLTextureR* retval = new GLTextureR(q, width, height, samples);
     q->resizeRenderTexture(retval, width, height);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_RTexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_RTexs.emplace_back(retval);
     return retval;
 }
 
@@ -1282,9 +1282,9 @@ IVertexFormat* GLDataFactory::newVertexFormat
 {
     GLCommandQueue* q = static_cast<GLCommandQueue*>(m_parent->getCommandQueue());
     GLVertexFormat* retval = new struct GLVertexFormat(q, elementCount, elements);
-    if (!m_deferredData)
-        m_deferredData = new struct GLData();
-    static_cast<GLData*>(m_deferredData)->m_VFmts.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct GLData());
+    m_deferredData->m_VFmts.emplace_back(retval);
     return retval;
 }
 
diff --git a/lib/graphicsdev/Metal.mm b/lib/graphicsdev/Metal.mm
index f73cfcb..ec38360 100644
--- a/lib/graphicsdev/Metal.mm
+++ b/lib/graphicsdev/Metal.mm
@@ -13,6 +13,7 @@ namespace boo
 static LogVisor::LogModule Log("boo::Metal");
 struct MetalCommandQueue;
 
+ThreadLocalPtr<struct MetalData> MetalDataFactory::m_deferredData;
 struct MetalData : IGraphicsData
 {
     std::vector<std::unique_ptr<class MetalShaderPipeline>> m_SPs;
@@ -717,6 +718,7 @@ struct MetalCommandQueue : IGraphicsCommandQueue
         
         /* Update dynamic data here */
         MetalDataFactory* gfxF = static_cast<MetalDataFactory*>(m_parent->getDataFactory());
+        std::unique_lock<std::mutex> datalk(gfxF->m_committedMutex);
         for (MetalData* d : gfxF->m_committedData)
         {
             for (std::unique_ptr<MetalGraphicsBufferD>& b : d->m_DBufs)
@@ -724,10 +726,7 @@ struct MetalCommandQueue : IGraphicsCommandQueue
             for (std::unique_ptr<MetalTextureD>& t : d->m_DTexs)
                 t->update(m_fillBuf);
         }
-        for (std::unique_ptr<MetalGraphicsBufferD>& b : gfxF->m_deferredData->m_DBufs)
-            b->update(m_fillBuf);
-        for (std::unique_ptr<MetalTextureD>& t : gfxF->m_deferredData->m_DTexs)
-            t->update(m_fillBuf);
+        datalk.unlock();
         
         @autoreleasepool
         {
@@ -815,26 +814,32 @@ void MetalTextureD::unmap()
 }
     
 MetalDataFactory::MetalDataFactory(IGraphicsContext* parent, MetalContext* ctx)
-: m_parent(parent), m_deferredData(new struct MetalData()), m_ctx(ctx) {}
+: m_parent(parent), m_ctx(ctx) {}
     
 IGraphicsBufferS* MetalDataFactory::newStaticBuffer(BufferUse use, const void* data, size_t stride, size_t count)
 {
     MetalGraphicsBufferS* retval = new MetalGraphicsBufferS(use, m_ctx, data, stride, count);
-    static_cast<MetalData*>(m_deferredData)->m_SBufs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_SBufs.emplace_back(retval);
     return retval;
 }
 IGraphicsBufferS* MetalDataFactory::newStaticBuffer(BufferUse use, std::unique_ptr<uint8_t[]>&& data, size_t stride, size_t count)
 {
     std::unique_ptr<uint8_t[]> d = std::move(data);
     MetalGraphicsBufferS* retval = new MetalGraphicsBufferS(use, m_ctx, d.get(), stride, count);
-    static_cast<MetalData*>(m_deferredData)->m_SBufs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_SBufs.emplace_back(retval);
     return retval;
 }
 IGraphicsBufferD* MetalDataFactory::newDynamicBuffer(BufferUse use, size_t stride, size_t count)
 {
     MetalCommandQueue* q = static_cast<MetalCommandQueue*>(m_parent->getCommandQueue());
     MetalGraphicsBufferD* retval = new MetalGraphicsBufferD(q, use, m_ctx, stride, count);
-    static_cast<MetalData*>(m_deferredData)->m_DBufs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_DBufs.emplace_back(retval);
     return retval;
 }
 
@@ -842,7 +847,9 @@ ITextureS* MetalDataFactory::newStaticTexture(size_t width, size_t height, size_
                                               const void* data, size_t sz)
 {
     MetalTextureS* retval = new MetalTextureS(m_ctx, width, height, mips, fmt, data, sz);
-    static_cast<MetalData*>(m_deferredData)->m_STexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_STexs.emplace_back(retval);
     return retval;
 }
 ITextureS* MetalDataFactory::newStaticTexture(size_t width, size_t height, size_t mips, TextureFormat fmt,
@@ -850,34 +857,44 @@ ITextureS* MetalDataFactory::newStaticTexture(size_t width, size_t height, size_
 {
     std::unique_ptr<uint8_t[]> d = std::move(data);
     MetalTextureS* retval = new MetalTextureS(m_ctx, width, height, mips, fmt, d.get(), sz);
-    static_cast<MetalData*>(m_deferredData)->m_STexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_STexs.emplace_back(retval);
     return retval;
 }
 ITextureSA* MetalDataFactory::newStaticArrayTexture(size_t width, size_t height, size_t layers, TextureFormat fmt,
                                                    const void* data, size_t sz)
 {
     MetalTextureSA* retval = new MetalTextureSA(m_ctx, width, height, layers, fmt, data, sz);
-    static_cast<MetalData*>(m_deferredData)->m_SATexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_SATexs.emplace_back(retval);
     return retval;
 }
 ITextureD* MetalDataFactory::newDynamicTexture(size_t width, size_t height, TextureFormat fmt)
 {
     MetalCommandQueue* q = static_cast<MetalCommandQueue*>(m_parent->getCommandQueue());
     MetalTextureD* retval = new MetalTextureD(q, m_ctx, width, height, fmt);
-    static_cast<MetalData*>(m_deferredData)->m_DTexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_DTexs.emplace_back(retval);
     return retval;
 }
 ITextureR* MetalDataFactory::newRenderTexture(size_t width, size_t height, size_t samples)
 {
     MetalTextureR* retval = new MetalTextureR(m_ctx, width, height, samples);
-    static_cast<MetalData*>(m_deferredData)->m_RTexs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_RTexs.emplace_back(retval);
     return retval;
 }
 
 IVertexFormat* MetalDataFactory::newVertexFormat(size_t elementCount, const VertexElementDescriptor* elements)
 {
     MetalVertexFormat* retval = new struct MetalVertexFormat(elementCount, elements);
-    static_cast<MetalData*>(m_deferredData)->m_VFmts.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_VFmts.emplace_back(retval);
     return retval;
 }
 
@@ -907,7 +924,9 @@ IShaderPipeline* MetalDataFactory::newShaderPipeline(const char* vertSource, con
     MetalShaderPipeline* retval = new MetalShaderPipeline(m_ctx, vertFunc.get(), fragFunc.get(),
                                                           static_cast<const MetalVertexFormat*>(vtxFmt), targetSamples,
                                                           srcFac, dstFac, depthTest, depthWrite, backfaceCulling);
-    static_cast<MetalData*>(m_deferredData)->m_SPs.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_SPs.emplace_back(retval);
     return retval;
 }
 
@@ -920,30 +939,37 @@ MetalDataFactory::newShaderDataBinding(IShaderPipeline* pipeline,
 {
     MetalShaderDataBinding* retval =
     new MetalShaderDataBinding(m_ctx, pipeline, vbuf, instVbo, ibuf, ubufCount, ubufs, texCount, texs);
-    static_cast<MetalData*>(m_deferredData)->m_SBinds.emplace_back(retval);
+    if (!m_deferredData.get())
+        m_deferredData.reset(new struct MetalData());
+    m_deferredData->m_SBinds.emplace_back(retval);
     return retval;
 }
 
 void MetalDataFactory::reset()
 {
-    delete static_cast<MetalData*>(m_deferredData);
-    m_deferredData = new struct MetalData();
+    delete m_deferredData.get();
+    m_deferredData.reset();
 }
-IGraphicsDataToken MetalDataFactory::commit()
+GraphicsDataToken MetalDataFactory::commit()
 {
-    MetalData* retval = static_cast<MetalData*>(m_deferredData);
-    m_deferredData = new struct MetalData();
+    if (!m_deferredData.get())
+        return GraphicsDataToken(this, nullptr);
+    std::unique_lock<std::mutex> lk(m_committedMutex);
+    MetalData* retval = m_deferredData.get();
+    m_deferredData.reset();
     m_committedData.insert(retval);
-    return IGraphicsDataToken(this, retval);
+    return GraphicsDataToken(this, retval);
 }
 void MetalDataFactory::destroyData(IGraphicsData* d)
 {
+    std::unique_lock<std::mutex> lk(m_committedMutex);
     MetalData* data = static_cast<MetalData*>(d);
     m_committedData.erase(data);
     delete data;
 }
 void MetalDataFactory::destroyAllData()
 {
+    std::unique_lock<std::mutex> lk(m_committedMutex);
     for (IGraphicsData* data : m_committedData)
         delete static_cast<MetalData*>(data);
     m_committedData.clear();