This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/intern/gawain/src/gwn_vertex_array_id.cpp

175 lines
4.1 KiB
C++
Raw Normal View History

// Gawain vertex array IDs
//
// This code is part of the Gawain library, with modifications
// specific to integration with Blender.
//
// Copyright 2016 Mike Erwin, Clément Foucault
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.#include "buffer_id.h"
#include "gwn_batch_private.h"
#include "gwn_vertex_array_id.h"
#include "gwn_context.h"
#include <vector>
#include <string.h>
#include <pthread.h>
#include <mutex>
#include <unordered_set>
#if TRUST_NO_ONE
2018-07-03 13:53:20 +02:00
#if 0
extern "C" {
extern int BLI_thread_is_main(void); // Blender-specific function
}
static bool thread_is_main()
{
// "main" here means the GL context's thread
return BLI_thread_is_main();
}
#endif
2018-07-03 13:53:20 +02:00
#endif
struct Gwn_Context {
GLuint default_vao;
std::unordered_set<Gwn_Batch*> batches; // Batches that have VAOs from this context
std::vector<GLuint> orphaned_vertarray_ids;
std::mutex orphans_mutex; // todo: try spinlock instead
#if TRUST_NO_ONE
pthread_t thread; // Thread on which this context is active.
2018-02-21 18:58:29 -03:00
bool thread_is_used;
Gwn_Context()
{
thread_is_used = false;
}
#endif
};
#if defined(_MSC_VER) && (_MSC_VER == 1800)
#define thread_local __declspec(thread)
thread_local Gwn_Context* active_ctx = NULL;
#else
static thread_local Gwn_Context* active_ctx = NULL;
#endif
static void clear_orphans(Gwn_Context* ctx)
{
ctx->orphans_mutex.lock();
if (!ctx->orphaned_vertarray_ids.empty())
{
unsigned orphan_ct = (unsigned)ctx->orphaned_vertarray_ids.size();
glDeleteVertexArrays(orphan_ct, ctx->orphaned_vertarray_ids.data());
ctx->orphaned_vertarray_ids.clear();
}
ctx->orphans_mutex.unlock();
}
Gwn_Context* GWN_context_create(void)
{
#if TRUST_NO_ONE
// assert(thread_is_main());
#endif
Gwn_Context* ctx = new Gwn_Context;
glGenVertexArrays(1, &ctx->default_vao);
GWN_context_active_set(ctx);
return ctx;
}
// to be called after GWN_context_active_set(ctx_to_destroy)
void GWN_context_discard(Gwn_Context* ctx)
{
#if TRUST_NO_ONE
// Make sure no other thread has locked it.
assert(ctx == active_ctx);
2018-02-21 18:58:29 -03:00
assert(pthread_equal(pthread_self(), ctx->thread));
assert(ctx->orphaned_vertarray_ids.empty());
#endif
// delete remaining vaos
while (!ctx->batches.empty())
{
// this removes the array entry
gwn_batch_vao_cache_clear(*ctx->batches.begin());
}
glDeleteVertexArrays(1, &ctx->default_vao);
delete ctx;
active_ctx = NULL;
}
// ctx can be NULL
void GWN_context_active_set(Gwn_Context* ctx)
{
#if TRUST_NO_ONE
if (active_ctx)
2018-02-21 18:58:29 -03:00
active_ctx->thread_is_used = false;
// Make sure no other context is already bound to this thread.
if (ctx)
{
// Make sure no other thread has locked it.
2018-02-21 18:58:29 -03:00
assert(ctx->thread_is_used == false);
ctx->thread = pthread_self();
2018-02-21 18:58:29 -03:00
ctx->thread_is_used = true;
}
#endif
if (ctx)
clear_orphans(ctx);
active_ctx = ctx;
}
Gwn_Context* GWN_context_active_get(void)
{
return active_ctx;
}
GLuint GWN_vao_default(void)
{
#if TRUST_NO_ONE
assert(active_ctx); // need at least an active context
2018-02-21 18:58:29 -03:00
assert(pthread_equal(pthread_self(), active_ctx->thread)); // context has been activated by another thread!
#endif
return active_ctx->default_vao;
}
Gawain: Refactor: VAOs caching AND use new VAOs manager. A major bottleneck of current implementation is the call to create_bindings() for basically every drawcalls. This is due to the VAO being tagged dirty when assigning a new shader to the Batch, defeating the purpose of the Batch (reuse it for drawing). Since managing hundreds of batches in DrawManager and DrawCache seems not fun enough to me, I prefered rewritting the batches itself. --- Batch changes --- For this to happen I needed to change the Instancing to be part of the Batch rather than being another batch supplied at drawtime. The Gwn_VertBuffers are copied from the batch to be instanciated and a new Gwn_VertBuffer is supplied for instancing attribs. This mean a VAO can be generated and cached for this instancing case. A Batch can be rendered with instancing, without instancing attribs and without the need for a new VAO using the GWN_batch_draw_range_ex with the force_instance parameter set to true. --- Draw manager changes --- The downside with this approach is that we must track the validity of the instanced batch (the original one). For this the only way (I could think of) is to set a callback for when the batch is getting free. This means a bit of refactor in the DrawManager with the separation of batching and instancing Batches. --- VAO cache --- Each VAO is generated for a given ShaderInterface. This means we can keep it alive as long as the shader interface lives. If a ShaderInterface is discarded, it needs to destroy every VAO associated to it. Otherwise, a new ShaderInterface with the same adress could be generated and reuse the same VAO with incorrect bindings. The VAO cache itself is using a mix between a static array of VAO and a dynamic array if the is not enough space in the static. Using this hybrid approach is a bit more performant than the dynamic array alone. The array will not resize down but empty entries will be filled up again. It's unlikely we get a buffer overflow from this. Resizing could be done on next allocation if needed. --- Results --- Using Cached VAOs means that we are not querying each vertex attrib for each vbo for each drawcall, every redraw! In a CPU limited test scene (10000 cubes in Clay engine) I get a reduction of CPU drawing time from ~20ms to 13ms. The only area that is not caching VAOs is the instancing from particles (see comment DRW_shgroup_instance_batch).
2018-02-20 01:55:19 +01:00
GLuint GWN_vao_alloc(void)
{
#if TRUST_NO_ONE
assert(active_ctx); // need at least an active context
2018-02-21 18:58:29 -03:00
assert(pthread_equal(pthread_self(), active_ctx->thread)); // context has been activated by another thread!
#endif
clear_orphans(active_ctx);
GLuint new_vao_id = 0;
glGenVertexArrays(1, &new_vao_id);
return new_vao_id;
}
// this can be called from multiple thread
Gawain: Refactor: VAOs caching AND use new VAOs manager. A major bottleneck of current implementation is the call to create_bindings() for basically every drawcalls. This is due to the VAO being tagged dirty when assigning a new shader to the Batch, defeating the purpose of the Batch (reuse it for drawing). Since managing hundreds of batches in DrawManager and DrawCache seems not fun enough to me, I prefered rewritting the batches itself. --- Batch changes --- For this to happen I needed to change the Instancing to be part of the Batch rather than being another batch supplied at drawtime. The Gwn_VertBuffers are copied from the batch to be instanciated and a new Gwn_VertBuffer is supplied for instancing attribs. This mean a VAO can be generated and cached for this instancing case. A Batch can be rendered with instancing, without instancing attribs and without the need for a new VAO using the GWN_batch_draw_range_ex with the force_instance parameter set to true. --- Draw manager changes --- The downside with this approach is that we must track the validity of the instanced batch (the original one). For this the only way (I could think of) is to set a callback for when the batch is getting free. This means a bit of refactor in the DrawManager with the separation of batching and instancing Batches. --- VAO cache --- Each VAO is generated for a given ShaderInterface. This means we can keep it alive as long as the shader interface lives. If a ShaderInterface is discarded, it needs to destroy every VAO associated to it. Otherwise, a new ShaderInterface with the same adress could be generated and reuse the same VAO with incorrect bindings. The VAO cache itself is using a mix between a static array of VAO and a dynamic array if the is not enough space in the static. Using this hybrid approach is a bit more performant than the dynamic array alone. The array will not resize down but empty entries will be filled up again. It's unlikely we get a buffer overflow from this. Resizing could be done on next allocation if needed. --- Results --- Using Cached VAOs means that we are not querying each vertex attrib for each vbo for each drawcall, every redraw! In a CPU limited test scene (10000 cubes in Clay engine) I get a reduction of CPU drawing time from ~20ms to 13ms. The only area that is not caching VAOs is the instancing from particles (see comment DRW_shgroup_instance_batch).
2018-02-20 01:55:19 +01:00
void GWN_vao_free(GLuint vao_id, Gwn_Context* ctx)
{
2018-02-21 18:58:29 -03:00
#if TRUST_NO_ONE
assert(ctx);
#endif
if (ctx == active_ctx)
glDeleteVertexArrays(1, &vao_id);
else
{
ctx->orphans_mutex.lock();
ctx->orphaned_vertarray_ids.emplace_back(vao_id);
ctx->orphans_mutex.unlock();
}
}
void gwn_context_add_batch(Gwn_Context* ctx, Gwn_Batch* batch)
{
ctx->batches.emplace(batch);
}
void gwn_context_remove_batch(Gwn_Context* ctx, Gwn_Batch* batch)
{
ctx->orphans_mutex.lock();
ctx->batches.erase(batch);
ctx->orphans_mutex.unlock();
}