This was caused by an incorrect bound check. Since we now use different data type for indexed and non-indexed drawcalls, we need to check if the next drawcall is going to overflow the buffer.
240 lines
6.9 KiB
C++
240 lines
6.9 KiB
C++
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2016 by Mike Erwin.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup gpu
|
|
*
|
|
* Implementation of Multi Draw Indirect using OpenGL.
|
|
* Fallback if the needed extensions are not supported.
|
|
*/
|
|
|
|
#include "BLI_assert.h"
|
|
|
|
#include "GPU_batch.h"
|
|
#include "GPU_capabilities.h"
|
|
|
|
#include "glew-mx.h"
|
|
|
|
#include "gpu_context_private.hh"
|
|
#include "gpu_drawlist_private.hh"
|
|
#include "gpu_vertex_buffer_private.hh"
|
|
|
|
#include "gl_backend.hh"
|
|
#include "gl_drawlist.hh"
|
|
#include "gl_primitive.hh"
|
|
|
|
#include <climits>
|
|
|
|
using namespace blender::gpu;
|
|
|
|
struct GLDrawCommand {
|
|
GLuint v_count;
|
|
GLuint i_count;
|
|
GLuint v_first;
|
|
GLuint i_first;
|
|
};
|
|
|
|
struct GLDrawCommandIndexed {
|
|
GLuint v_count;
|
|
GLuint i_count;
|
|
GLuint v_first;
|
|
GLuint base_index;
|
|
GLuint i_first;
|
|
};
|
|
|
|
#define MDI_ENABLED (buffer_size_ != 0)
|
|
#define MDI_DISABLED (buffer_size_ == 0)
|
|
#define MDI_INDEXED (base_index_ != UINT_MAX)
|
|
|
|
GLDrawList::GLDrawList(int length)
|
|
{
|
|
BLI_assert(length > 0);
|
|
batch_ = nullptr;
|
|
buffer_id_ = 0;
|
|
command_len_ = 0;
|
|
command_offset_ = 0;
|
|
data_size_ = 0;
|
|
data_ = nullptr;
|
|
|
|
if (GLContext::multi_draw_indirect_support) {
|
|
/* Alloc the biggest possible command list, which is indexed. */
|
|
buffer_size_ = sizeof(GLDrawCommandIndexed) * length;
|
|
}
|
|
else {
|
|
/* Indicates MDI is not supported. */
|
|
buffer_size_ = 0;
|
|
}
|
|
/* Force buffer specification on first init. */
|
|
data_offset_ = buffer_size_;
|
|
}
|
|
|
|
GLDrawList::~GLDrawList()
|
|
{
|
|
GLContext::buf_free(buffer_id_);
|
|
}
|
|
|
|
void GLDrawList::init()
|
|
{
|
|
BLI_assert(GLContext::get());
|
|
BLI_assert(MDI_ENABLED);
|
|
BLI_assert(data_ == nullptr);
|
|
batch_ = nullptr;
|
|
command_len_ = 0;
|
|
|
|
if (buffer_id_ == 0) {
|
|
/* Allocate on first use. */
|
|
glGenBuffers(1, &buffer_id_);
|
|
context_ = GLContext::get();
|
|
}
|
|
|
|
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buffer_id_);
|
|
/* If buffer is full, orphan buffer data and start fresh. */
|
|
size_t command_size = MDI_INDEXED ? sizeof(GLDrawCommandIndexed) : sizeof(GLDrawCommand);
|
|
if (data_offset_ + command_size > buffer_size_) {
|
|
glBufferData(GL_DRAW_INDIRECT_BUFFER, buffer_size_, nullptr, GL_DYNAMIC_DRAW);
|
|
data_offset_ = 0;
|
|
}
|
|
/* Map the remaining range. */
|
|
GLbitfield flag = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT;
|
|
data_size_ = buffer_size_ - data_offset_;
|
|
data_ = (GLbyte *)glMapBufferRange(GL_DRAW_INDIRECT_BUFFER, data_offset_, data_size_, flag);
|
|
command_offset_ = 0;
|
|
}
|
|
|
|
void GLDrawList::append(GPUBatch *gpu_batch, int i_first, int i_count)
|
|
{
|
|
/* Fallback when MultiDrawIndirect is not supported/enabled. */
|
|
if (MDI_DISABLED) {
|
|
GPU_batch_draw_advanced(gpu_batch, 0, 0, i_first, i_count);
|
|
return;
|
|
}
|
|
|
|
if (data_ == nullptr) {
|
|
this->init();
|
|
}
|
|
|
|
GLBatch *batch = static_cast<GLBatch *>(gpu_batch);
|
|
if (batch != batch_) {
|
|
// BLI_assert(batch->flag | GPU_BATCH_INIT);
|
|
this->submit();
|
|
batch_ = batch;
|
|
/* Cached for faster access. */
|
|
GLIndexBuf *el = batch_->elem_();
|
|
base_index_ = el ? el->index_base_ : UINT_MAX;
|
|
v_first_ = el ? el->index_start_ : 0;
|
|
v_count_ = el ? el->index_len_ : batch->verts_(0)->vertex_len;
|
|
}
|
|
|
|
if (v_count_ == 0) {
|
|
/* Nothing to draw. */
|
|
return;
|
|
}
|
|
|
|
if (MDI_INDEXED) {
|
|
GLDrawCommandIndexed *cmd = reinterpret_cast<GLDrawCommandIndexed *>(data_ + command_offset_);
|
|
cmd->v_first = v_first_;
|
|
cmd->v_count = v_count_;
|
|
cmd->i_count = i_count;
|
|
cmd->base_index = base_index_;
|
|
cmd->i_first = i_first;
|
|
}
|
|
else {
|
|
GLDrawCommand *cmd = reinterpret_cast<GLDrawCommand *>(data_ + command_offset_);
|
|
cmd->v_first = v_first_;
|
|
cmd->v_count = v_count_;
|
|
cmd->i_count = i_count;
|
|
cmd->i_first = i_first;
|
|
}
|
|
|
|
size_t command_size = MDI_INDEXED ? sizeof(GLDrawCommandIndexed) : sizeof(GLDrawCommand);
|
|
|
|
command_offset_ += command_size;
|
|
command_len_++;
|
|
|
|
/* Check if we can fit at least one other command. */
|
|
if (command_offset_ + command_size > data_size_) {
|
|
this->submit();
|
|
}
|
|
}
|
|
|
|
void GLDrawList::submit()
|
|
{
|
|
if (command_len_ == 0) {
|
|
return;
|
|
}
|
|
/* Something's wrong if we get here without MDI support. */
|
|
BLI_assert(MDI_ENABLED);
|
|
BLI_assert(data_);
|
|
BLI_assert(GLContext::get()->shader != nullptr);
|
|
|
|
size_t command_size = MDI_INDEXED ? sizeof(GLDrawCommandIndexed) : sizeof(GLDrawCommand);
|
|
|
|
/* Only do multi-draw indirect if doing more than 2 drawcall. This avoids the overhead of
|
|
* buffer mapping if scene is not very instance friendly. BUT we also need to take into
|
|
* account the case where only a few instances are needed to finish filling a call buffer. */
|
|
const bool is_finishing_a_buffer = (command_offset_ + command_size > data_size_);
|
|
if (command_len_ > 2 || is_finishing_a_buffer) {
|
|
GLenum prim = to_gl(batch_->prim_type);
|
|
void *offset = (void *)data_offset_;
|
|
|
|
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buffer_id_);
|
|
glFlushMappedBufferRange(GL_DRAW_INDIRECT_BUFFER, 0, command_offset_);
|
|
glUnmapBuffer(GL_DRAW_INDIRECT_BUFFER);
|
|
data_ = nullptr; /* Unmapped */
|
|
data_offset_ += command_offset_;
|
|
|
|
batch_->bind(0);
|
|
|
|
if (MDI_INDEXED) {
|
|
GLenum gl_type = to_gl(batch_->elem_()->index_type_);
|
|
glMultiDrawElementsIndirect(prim, gl_type, offset, command_len_, 0);
|
|
}
|
|
else {
|
|
glMultiDrawArraysIndirect(prim, offset, command_len_, 0);
|
|
}
|
|
}
|
|
else {
|
|
/* Fallback do simple drawcalls, and don't unmap the buffer. */
|
|
if (MDI_INDEXED) {
|
|
GLDrawCommandIndexed *cmd = (GLDrawCommandIndexed *)data_;
|
|
for (int i = 0; i < command_len_; i++, cmd++) {
|
|
/* Index start was already added. Avoid counting it twice. */
|
|
cmd->v_first -= v_first_;
|
|
batch_->draw(cmd->v_first, cmd->v_count, cmd->i_first, cmd->i_count);
|
|
}
|
|
/* Reuse the same data. */
|
|
command_offset_ -= command_len_ * sizeof(GLDrawCommandIndexed);
|
|
}
|
|
else {
|
|
GLDrawCommand *cmd = (GLDrawCommand *)data_;
|
|
for (int i = 0; i < command_len_; i++, cmd++) {
|
|
batch_->draw(cmd->v_first, cmd->v_count, cmd->i_first, cmd->i_count);
|
|
}
|
|
/* Reuse the same data. */
|
|
command_offset_ -= command_len_ * sizeof(GLDrawCommand);
|
|
}
|
|
}
|
|
/* Do not submit this buffer again. */
|
|
command_len_ = 0;
|
|
/* Avoid keeping reference to the batch. */
|
|
batch_ = nullptr;
|
|
}
|
|
|
|
/** \} */
|