1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2016 by Mike Erwin.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup gpu
22  *
23  * Implementation of Multi Draw Indirect using OpenGL.
24  * Fallback if the needed extensions are not supported.
25  */
26 
27 #include "BLI_assert.h"
28 
29 #include "GPU_batch.h"
30 #include "GPU_capabilities.h"
31 
32 #include "glew-mx.h"
33 
34 #include "gpu_context_private.hh"
35 #include "gpu_drawlist_private.hh"
36 #include "gpu_vertex_buffer_private.hh"
37 
38 #include "gl_backend.hh"
39 #include "gl_drawlist.hh"
40 #include "gl_primitive.hh"
41 
42 #include <limits.h>
43 
44 using namespace blender::gpu;
45 
46 typedef struct GLDrawCommand {
47   GLuint v_count;
48   GLuint i_count;
49   GLuint v_first;
50   GLuint i_first;
51 } GLDrawCommand;
52 
53 typedef struct GLDrawCommandIndexed {
54   GLuint v_count;
55   GLuint i_count;
56   GLuint v_first;
57   GLuint base_index;
58   GLuint i_first;
59 } GLDrawCommandIndexed;
60 
61 #define MDI_ENABLED (buffer_size_ != 0)
62 #define MDI_DISABLED (buffer_size_ == 0)
63 #define MDI_INDEXED (base_index_ != UINT_MAX)
64 
GLDrawList(int length)65 GLDrawList::GLDrawList(int length)
66 {
67   BLI_assert(length > 0);
68   batch_ = NULL;
69   buffer_id_ = 0;
70   command_len_ = 0;
71   command_offset_ = 0;
72   data_offset_ = 0;
73   data_size_ = 0;
74   data_ = NULL;
75 
76   if (GLContext::multi_draw_indirect_support) {
77     /* Alloc the biggest possible command list, which is indexed. */
78     buffer_size_ = sizeof(GLDrawCommandIndexed) * length;
79   }
80   else {
81     /* Indicates MDI is not supported. */
82     buffer_size_ = 0;
83   }
84 }
85 
~GLDrawList()86 GLDrawList::~GLDrawList()
87 {
88   GLContext::buf_free(buffer_id_);
89 }
90 
init(void)91 void GLDrawList::init(void)
92 {
93   BLI_assert(GLContext::get());
94   BLI_assert(MDI_ENABLED);
95   BLI_assert(data_ == NULL);
96   batch_ = NULL;
97   command_len_ = 0;
98 
99   if (buffer_id_ == 0) {
100     /* Allocate on first use. */
101     glGenBuffers(1, &buffer_id_);
102     context_ = GLContext::get();
103   }
104 
105   glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buffer_id_);
106   /* If buffer is full, orphan buffer data and start fresh. */
107   // if (command_offset_ >= data_size_) {
108   glBufferData(GL_DRAW_INDIRECT_BUFFER, buffer_size_, NULL, GL_DYNAMIC_DRAW);
109   data_offset_ = 0;
110   // }
111   /* Map the remaining range. */
112   GLbitfield flag = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT;
113   data_size_ = buffer_size_ - data_offset_;
114   data_ = (GLbyte *)glMapBufferRange(GL_DRAW_INDIRECT_BUFFER, data_offset_, data_size_, flag);
115   command_offset_ = 0;
116 }
117 
append(GPUBatch * gpu_batch,int i_first,int i_count)118 void GLDrawList::append(GPUBatch *gpu_batch, int i_first, int i_count)
119 {
120   /* Fallback when MultiDrawIndirect is not supported/enabled. */
121   if (MDI_DISABLED) {
122     GPU_batch_draw_advanced(gpu_batch, 0, 0, i_first, i_count);
123     return;
124   }
125 
126   if (data_ == NULL) {
127     this->init();
128   }
129 
130   GLBatch *batch = static_cast<GLBatch *>(gpu_batch);
131   if (batch != batch_) {
132     // BLI_assert(batch->flag | GPU_BATCH_INIT);
133     this->submit();
134     batch_ = batch;
135     /* Cached for faster access. */
136     GLIndexBuf *el = batch_->elem_();
137     base_index_ = el ? el->index_base_ : UINT_MAX;
138     v_first_ = el ? el->index_start_ : 0;
139     v_count_ = el ? el->index_len_ : batch->verts_(0)->vertex_len;
140   }
141 
142   if (v_count_ == 0) {
143     /* Nothing to draw. */
144     return;
145   }
146 
147   if (MDI_INDEXED) {
148     GLDrawCommandIndexed *cmd = reinterpret_cast<GLDrawCommandIndexed *>(data_ + command_offset_);
149     cmd->v_first = v_first_;
150     cmd->v_count = v_count_;
151     cmd->i_count = i_count;
152     cmd->base_index = base_index_;
153     cmd->i_first = i_first;
154     command_offset_ += sizeof(GLDrawCommandIndexed);
155   }
156   else {
157     GLDrawCommand *cmd = reinterpret_cast<GLDrawCommand *>(data_ + command_offset_);
158     cmd->v_first = v_first_;
159     cmd->v_count = v_count_;
160     cmd->i_count = i_count;
161     cmd->i_first = i_first;
162     command_offset_ += sizeof(GLDrawCommand);
163   }
164 
165   command_len_++;
166 
167   if (command_offset_ >= data_size_) {
168     this->submit();
169   }
170 }
171 
submit(void)172 void GLDrawList::submit(void)
173 {
174   if (command_len_ == 0) {
175     return;
176   }
177   /* Something's wrong if we get here without MDI support. */
178   BLI_assert(MDI_ENABLED);
179   BLI_assert(data_);
180   BLI_assert(GLContext::get()->shader != NULL);
181 
182   /* Only do multi-draw indirect if doing more than 2 drawcall. This avoids the overhead of
183    * buffer mapping if scene is not very instance friendly. BUT we also need to take into
184    * account the case where only a few instances are needed to finish filling a call buffer. */
185   const bool is_finishing_a_buffer = (command_offset_ >= data_size_);
186   if (command_len_ > 2 || is_finishing_a_buffer) {
187     GLenum prim = to_gl(batch_->prim_type);
188     void *offset = (void *)data_offset_;
189 
190     glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buffer_id_);
191     glFlushMappedBufferRange(GL_DRAW_INDIRECT_BUFFER, 0, command_offset_);
192     glUnmapBuffer(GL_DRAW_INDIRECT_BUFFER);
193     data_ = NULL; /* Unmapped */
194     data_offset_ += command_offset_;
195 
196     batch_->bind(0);
197 
198     if (MDI_INDEXED) {
199       GLenum gl_type = to_gl(batch_->elem_()->index_type_);
200       glMultiDrawElementsIndirect(prim, gl_type, offset, command_len_, 0);
201     }
202     else {
203       glMultiDrawArraysIndirect(prim, offset, command_len_, 0);
204     }
205   }
206   else {
207     /* Fallback do simple drawcalls, and don't unmap the buffer. */
208     if (MDI_INDEXED) {
209       GLDrawCommandIndexed *cmd = (GLDrawCommandIndexed *)data_;
210       for (int i = 0; i < command_len_; i++, cmd++) {
211         /* Index start was already added. Avoid counting it twice. */
212         cmd->v_first -= v_first_;
213         batch_->draw(cmd->v_first, cmd->v_count, cmd->i_first, cmd->i_count);
214       }
215       /* Reuse the same data. */
216       command_offset_ -= command_len_ * sizeof(GLDrawCommandIndexed);
217     }
218     else {
219       GLDrawCommand *cmd = (GLDrawCommand *)data_;
220       for (int i = 0; i < command_len_; i++, cmd++) {
221         batch_->draw(cmd->v_first, cmd->v_count, cmd->i_first, cmd->i_count);
222       }
223       /* Reuse the same data. */
224       command_offset_ -= command_len_ * sizeof(GLDrawCommand);
225     }
226   }
227   /* Do not submit this buffer again. */
228   command_len_ = 0;
229   /* Avoid keeping reference to the batch. */
230   batch_ = NULL;
231 }
232 
233 /** \} */
234