--------------------------------------------------------------------------------------------------- Threading Rules --------------------------------------------------------------------------------------------------- There are two rules to keep in mind: 1. Dedicated rendering thread Only 1 thread must use a Rasterizer at any given time. This should be obvious, as the rasterizer keeps a state (two threads could not render different things at the same time since each needs to assign the vertex buffers, index buffer and so on - and these are not thread-local fields). 2. Free-threaded resource loading Resources can be created and prepared in any thread, including the rendering thread, of course. This enables background streaming of resources in rasterizers that support it. 3. Limited resource changing Only one thread may change a resource at any given time. During a call to Rasterize() or RasterizeIndexed(), the currently selected resources must not be changed by another thread. This is basically just sane application design - if a buffer is written to while rendering takes place, you might render a half-changed buffer, you might get a shared violation exception (if the graphics API checks that) or you might get an access violation / segmentation fault from this very library because the graphics API object needed to be recreated for the change and the rasterizer accessed it at an unfortunate time. If you want to distribute geometry generation over multiple threads, you have these choices: - Use VertexBuffer::AccessMemory() and partition it into equal sections, then let std::thread::hardware_concurrency threads work in it. When *all* threads are finished, call MarkDirty() from a single thread. - Use a staging buffer where your threads work in and let the last thread to finish issue a single Write() call copying the entire staging buffer into your vertex buffer. --------------------------------------------------------------------------------------------------- Assumptions about std::shared_ptr --------------------------------------------------------------------------------------------------- We stretched the std::shared_ptr threading guarantees a bit by making the following assumption: Copying or destroying an std::shared_ptr in multiple threads is allowed, as long as it is guaranteed that: - Destroying the std::shared_ptr will not decrement the reference count to 0 - When copying the std::shared_ptr, the std::shared_ptr being copied can not have its reference count reach 0. In other words, we assume that modifying the reference count in an std::shared_ptr is an atomic operation. --------------------------------------------------------------------------------------------------- Internal workings --------------------------------------------------------------------------------------------------- Inside the library, some operations exist which need to be synchronized (mainly where the rasterizer and its resource management system overlap). This is how it works: 1. Each resource type has its own observer manager, which in turn owns a mutex that needs to be locked when doing critical operations on resources: - Preparing a resource requires entering the mutex of its observer manager because the manager uses std::set and other non-threadsafe containers. Other threads preparing different resources (including the rendering thread) can try to prepare resources at the same time.