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