1 //
2 // Copyright 2014 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6
7 #include "libANGLE/TransformFeedback.h"
8
9 #include "common/mathutil.h"
10 #include "libANGLE/Buffer.h"
11 #include "libANGLE/Caps.h"
12 #include "libANGLE/Context.h"
13 #include "libANGLE/Program.h"
14 #include "libANGLE/State.h"
15 #include "libANGLE/renderer/GLImplFactory.h"
16 #include "libANGLE/renderer/TransformFeedbackImpl.h"
17
18 #include <limits>
19
20 namespace gl
21 {
22
GetVerticesNeededForDraw(PrimitiveMode primitiveMode,GLsizei count,GLsizei primcount)23 angle::CheckedNumeric<GLsizeiptr> GetVerticesNeededForDraw(PrimitiveMode primitiveMode,
24 GLsizei count,
25 GLsizei primcount)
26 {
27 if (count < 0 || primcount < 0)
28 {
29 return 0;
30 }
31 // Transform feedback only outputs complete primitives, so we need to round down to the nearest
32 // complete primitive before multiplying by the number of instances.
33 angle::CheckedNumeric<GLsizeiptr> checkedCount = count;
34 angle::CheckedNumeric<GLsizeiptr> checkedPrimcount = primcount;
35 switch (primitiveMode)
36 {
37 case PrimitiveMode::Triangles:
38 return checkedPrimcount * (checkedCount - checkedCount % 3);
39 case PrimitiveMode::Lines:
40 return checkedPrimcount * (checkedCount - checkedCount % 2);
41 case PrimitiveMode::Points:
42 return checkedPrimcount * checkedCount;
43 default:
44 UNREACHABLE();
45 return checkedPrimcount * checkedCount;
46 }
47 }
48
TransformFeedbackState(size_t maxIndexedBuffers)49 TransformFeedbackState::TransformFeedbackState(size_t maxIndexedBuffers)
50 : mLabel(),
51 mActive(false),
52 mPrimitiveMode(PrimitiveMode::InvalidEnum),
53 mPaused(false),
54 mVerticesDrawn(0),
55 mVertexCapacity(0),
56 mProgram(nullptr),
57 mIndexedBuffers(maxIndexedBuffers)
58 {}
59
~TransformFeedbackState()60 TransformFeedbackState::~TransformFeedbackState() {}
61
getIndexedBuffer(size_t idx) const62 const OffsetBindingPointer<Buffer> &TransformFeedbackState::getIndexedBuffer(size_t idx) const
63 {
64 return mIndexedBuffers[idx];
65 }
66
getIndexedBuffers() const67 const std::vector<OffsetBindingPointer<Buffer>> &TransformFeedbackState::getIndexedBuffers() const
68 {
69 return mIndexedBuffers;
70 }
71
getPrimitivesDrawn() const72 GLsizeiptr TransformFeedbackState::getPrimitivesDrawn() const
73 {
74 switch (mPrimitiveMode)
75 {
76 case gl::PrimitiveMode::Points:
77 return mVerticesDrawn;
78 case gl::PrimitiveMode::Lines:
79 return mVerticesDrawn / 2;
80 case gl::PrimitiveMode::Triangles:
81 return mVerticesDrawn / 3;
82 default:
83 return 0;
84 }
85 }
86
TransformFeedback(rx::GLImplFactory * implFactory,TransformFeedbackID id,const Caps & caps)87 TransformFeedback::TransformFeedback(rx::GLImplFactory *implFactory,
88 TransformFeedbackID id,
89 const Caps &caps)
90 : RefCountObject(implFactory->generateSerial(), id),
91 mState(caps.maxTransformFeedbackSeparateAttributes),
92 mImplementation(implFactory->createTransformFeedback(mState))
93 {
94 ASSERT(mImplementation != nullptr);
95 }
96
onDestroy(const Context * context)97 void TransformFeedback::onDestroy(const Context *context)
98 {
99 ASSERT(!context || !context->isCurrentTransformFeedback(this));
100 if (mState.mProgram)
101 {
102 mState.mProgram->release(context);
103 mState.mProgram = nullptr;
104 }
105
106 ASSERT(!mState.mProgram);
107 for (size_t i = 0; i < mState.mIndexedBuffers.size(); i++)
108 {
109 mState.mIndexedBuffers[i].set(context, nullptr, 0, 0);
110 }
111
112 if (mImplementation)
113 {
114 mImplementation->onDestroy(context);
115 }
116 }
117
~TransformFeedback()118 TransformFeedback::~TransformFeedback()
119 {
120 SafeDelete(mImplementation);
121 }
122
setLabel(const Context * context,const std::string & label)123 void TransformFeedback::setLabel(const Context *context, const std::string &label)
124 {
125 mState.mLabel = label;
126 }
127
getLabel() const128 const std::string &TransformFeedback::getLabel() const
129 {
130 return mState.mLabel;
131 }
132
begin(const Context * context,PrimitiveMode primitiveMode,Program * program)133 angle::Result TransformFeedback::begin(const Context *context,
134 PrimitiveMode primitiveMode,
135 Program *program)
136 {
137 // TODO: http://anglebug.com/5486: This method should take in as parameter a
138 // ProgramExecutable instead of a Program.
139
140 ANGLE_TRY(mImplementation->begin(context, primitiveMode));
141 mState.mActive = true;
142 mState.mPrimitiveMode = primitiveMode;
143 mState.mPaused = false;
144 mState.mVerticesDrawn = 0;
145 bindProgram(context, program);
146
147 // In one of the angle_unittests - "TransformFeedbackTest.SideEffectsOfStartAndStop"
148 // there is a code path where <context> is a nullptr, account for that possiblity.
149 const ProgramExecutable *programExecutable =
150 context ? context->getState().getProgramExecutable() : nullptr;
151 if (programExecutable)
152 {
153 // Compute the number of vertices we can draw before overflowing the bound buffers.
154 auto strides = programExecutable->getTransformFeedbackStrides();
155 ASSERT(strides.size() <= mState.mIndexedBuffers.size() && !strides.empty());
156 GLsizeiptr minCapacity = std::numeric_limits<GLsizeiptr>::max();
157 for (size_t index = 0; index < strides.size(); index++)
158 {
159 GLsizeiptr capacity =
160 GetBoundBufferAvailableSize(mState.mIndexedBuffers[index]) / strides[index];
161 minCapacity = std::min(minCapacity, capacity);
162 }
163 mState.mVertexCapacity = minCapacity;
164 }
165 else
166 {
167 mState.mVertexCapacity = 0;
168 }
169 return angle::Result::Continue;
170 }
171
end(const Context * context)172 angle::Result TransformFeedback::end(const Context *context)
173 {
174 ANGLE_TRY(mImplementation->end(context));
175 mState.mActive = false;
176 mState.mPrimitiveMode = PrimitiveMode::InvalidEnum;
177 mState.mPaused = false;
178 mState.mVerticesDrawn = 0;
179 mState.mVertexCapacity = 0;
180 if (mState.mProgram)
181 {
182 mState.mProgram->release(context);
183 mState.mProgram = nullptr;
184 }
185 return angle::Result::Continue;
186 }
187
pause(const Context * context)188 angle::Result TransformFeedback::pause(const Context *context)
189 {
190 ANGLE_TRY(mImplementation->pause(context));
191 mState.mPaused = true;
192 return angle::Result::Continue;
193 }
194
resume(const Context * context)195 angle::Result TransformFeedback::resume(const Context *context)
196 {
197 ANGLE_TRY(mImplementation->resume(context));
198 mState.mPaused = false;
199 return angle::Result::Continue;
200 }
201
isPaused() const202 bool TransformFeedback::isPaused() const
203 {
204 return mState.mPaused;
205 }
206
getPrimitiveMode() const207 PrimitiveMode TransformFeedback::getPrimitiveMode() const
208 {
209 return mState.mPrimitiveMode;
210 }
211
checkBufferSpaceForDraw(GLsizei count,GLsizei primcount) const212 bool TransformFeedback::checkBufferSpaceForDraw(GLsizei count, GLsizei primcount) const
213 {
214 auto vertices =
215 mState.mVerticesDrawn + GetVerticesNeededForDraw(mState.mPrimitiveMode, count, primcount);
216 return vertices.IsValid() && vertices.ValueOrDie() <= mState.mVertexCapacity;
217 }
218
onVerticesDrawn(const Context * context,GLsizei count,GLsizei primcount)219 void TransformFeedback::onVerticesDrawn(const Context *context, GLsizei count, GLsizei primcount)
220 {
221 ASSERT(mState.mActive && !mState.mPaused);
222 // All draws should be validated with checkBufferSpaceForDraw so ValueOrDie should never fail.
223 mState.mVerticesDrawn =
224 (mState.mVerticesDrawn + GetVerticesNeededForDraw(mState.mPrimitiveMode, count, primcount))
225 .ValueOrDie();
226
227 for (auto &buffer : mState.mIndexedBuffers)
228 {
229 if (buffer.get() != nullptr)
230 {
231 buffer->onDataChanged();
232 }
233 }
234 }
235
bindProgram(const Context * context,Program * program)236 void TransformFeedback::bindProgram(const Context *context, Program *program)
237 {
238 if (mState.mProgram != program)
239 {
240 if (mState.mProgram != nullptr)
241 {
242 mState.mProgram->release(context);
243 }
244 mState.mProgram = program;
245 if (mState.mProgram != nullptr)
246 {
247 mState.mProgram->addRef();
248 }
249 }
250 }
251
hasBoundProgram(ShaderProgramID program) const252 bool TransformFeedback::hasBoundProgram(ShaderProgramID program) const
253 {
254 return mState.mProgram != nullptr && mState.mProgram->id().value == program.value;
255 }
256
detachBuffer(const Context * context,BufferID bufferID)257 angle::Result TransformFeedback::detachBuffer(const Context *context, BufferID bufferID)
258 {
259 bool isBound = context->isCurrentTransformFeedback(this);
260 for (size_t index = 0; index < mState.mIndexedBuffers.size(); index++)
261 {
262 if (mState.mIndexedBuffers[index].id() == bufferID)
263 {
264 if (isBound)
265 {
266 mState.mIndexedBuffers[index]->onTFBindingChanged(context, false, true);
267 }
268 mState.mIndexedBuffers[index].set(context, nullptr, 0, 0);
269 ANGLE_TRY(
270 mImplementation->bindIndexedBuffer(context, index, mState.mIndexedBuffers[index]));
271 }
272 }
273
274 return angle::Result::Continue;
275 }
276
bindIndexedBuffer(const Context * context,size_t index,Buffer * buffer,size_t offset,size_t size)277 angle::Result TransformFeedback::bindIndexedBuffer(const Context *context,
278 size_t index,
279 Buffer *buffer,
280 size_t offset,
281 size_t size)
282 {
283 ASSERT(index < mState.mIndexedBuffers.size());
284 bool isBound = context && context->isCurrentTransformFeedback(this);
285 if (isBound && mState.mIndexedBuffers[index].get())
286 {
287 mState.mIndexedBuffers[index]->onTFBindingChanged(context, false, true);
288 }
289 mState.mIndexedBuffers[index].set(context, buffer, offset, size);
290 if (isBound && buffer)
291 {
292 buffer->onTFBindingChanged(context, true, true);
293 }
294
295 return mImplementation->bindIndexedBuffer(context, index, mState.mIndexedBuffers[index]);
296 }
297
getIndexedBuffer(size_t index) const298 const OffsetBindingPointer<Buffer> &TransformFeedback::getIndexedBuffer(size_t index) const
299 {
300 ASSERT(index < mState.mIndexedBuffers.size());
301 return mState.mIndexedBuffers[index];
302 }
303
getIndexedBufferCount() const304 size_t TransformFeedback::getIndexedBufferCount() const
305 {
306 return mState.mIndexedBuffers.size();
307 }
308
buffersBoundForOtherUse() const309 bool TransformFeedback::buffersBoundForOtherUse() const
310 {
311 for (auto &buffer : mState.mIndexedBuffers)
312 {
313 if (buffer.get() && buffer->isBoundForTransformFeedbackAndOtherUse())
314 {
315 return true;
316 }
317 }
318 return false;
319 }
320
getImplementation() const321 rx::TransformFeedbackImpl *TransformFeedback::getImplementation() const
322 {
323 return mImplementation;
324 }
325
onBindingChanged(const Context * context,bool bound)326 void TransformFeedback::onBindingChanged(const Context *context, bool bound)
327 {
328 for (auto &buffer : mState.mIndexedBuffers)
329 {
330 if (buffer.get())
331 {
332 buffer->onTFBindingChanged(context, bound, true);
333 }
334 }
335 }
336
getIndexedBuffers() const337 const std::vector<OffsetBindingPointer<Buffer>> &TransformFeedback::getIndexedBuffers() const
338 {
339 return mState.mIndexedBuffers;
340 }
341 } // namespace gl
342