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