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 angle::Result TransformFeedback::setLabel(const Context *context, const std::string &label)
124 {
125 mState.mLabel = label;
126
127 if (mImplementation)
128 {
129 return mImplementation->onLabelUpdate(context);
130 }
131 return angle::Result::Continue;
132 }
133
getLabel() const134 const std::string &TransformFeedback::getLabel() const
135 {
136 return mState.mLabel;
137 }
138
begin(const Context * context,PrimitiveMode primitiveMode,Program * program)139 angle::Result TransformFeedback::begin(const Context *context,
140 PrimitiveMode primitiveMode,
141 Program *program)
142 {
143 // TODO: http://anglebug.com/42264023: This method should take in as parameter a
144 // ProgramExecutable instead of a Program.
145
146 ANGLE_TRY(mImplementation->begin(context, primitiveMode));
147 mState.mActive = true;
148 mState.mPrimitiveMode = primitiveMode;
149 mState.mPaused = false;
150 mState.mVerticesDrawn = 0;
151 bindProgram(context, program);
152
153 // In one of the angle_unittests - "TransformFeedbackTest.SideEffectsOfStartAndStop"
154 // there is a code path where <context> is a nullptr, account for that possiblity.
155 const ProgramExecutable *programExecutable =
156 context ? context->getState().getLinkedProgramExecutable(context) : nullptr;
157 if (programExecutable)
158 {
159 // Compute the number of vertices we can draw before overflowing the bound buffers.
160 auto strides = programExecutable->getTransformFeedbackStrides();
161 ASSERT(strides.size() <= mState.mIndexedBuffers.size() && !strides.empty());
162 GLsizeiptr minCapacity = std::numeric_limits<GLsizeiptr>::max();
163 for (size_t index = 0; index < strides.size(); index++)
164 {
165 GLsizeiptr capacity =
166 GetBoundBufferAvailableSize(mState.mIndexedBuffers[index]) / strides[index];
167 minCapacity = std::min(minCapacity, capacity);
168 }
169 mState.mVertexCapacity = minCapacity;
170 }
171 else
172 {
173 mState.mVertexCapacity = 0;
174 }
175 return angle::Result::Continue;
176 }
177
end(const Context * context)178 angle::Result TransformFeedback::end(const Context *context)
179 {
180 ANGLE_TRY(mImplementation->end(context));
181 mState.mActive = false;
182 mState.mPrimitiveMode = PrimitiveMode::InvalidEnum;
183 mState.mPaused = false;
184 mState.mVerticesDrawn = 0;
185 mState.mVertexCapacity = 0;
186 if (mState.mProgram)
187 {
188 mState.mProgram->release(context);
189 mState.mProgram = nullptr;
190 }
191 return angle::Result::Continue;
192 }
193
pause(const Context * context)194 angle::Result TransformFeedback::pause(const Context *context)
195 {
196 ANGLE_TRY(mImplementation->pause(context));
197 mState.mPaused = true;
198 return angle::Result::Continue;
199 }
200
resume(const Context * context)201 angle::Result TransformFeedback::resume(const Context *context)
202 {
203 ANGLE_TRY(mImplementation->resume(context));
204 mState.mPaused = false;
205 return angle::Result::Continue;
206 }
207
isPaused() const208 bool TransformFeedback::isPaused() const
209 {
210 return mState.mPaused;
211 }
212
getPrimitiveMode() const213 PrimitiveMode TransformFeedback::getPrimitiveMode() const
214 {
215 return mState.mPrimitiveMode;
216 }
217
checkBufferSpaceForDraw(GLsizei count,GLsizei primcount) const218 bool TransformFeedback::checkBufferSpaceForDraw(GLsizei count, GLsizei primcount) const
219 {
220 auto vertices =
221 mState.mVerticesDrawn + GetVerticesNeededForDraw(mState.mPrimitiveMode, count, primcount);
222 return vertices.IsValid() && vertices.ValueOrDie() <= mState.mVertexCapacity;
223 }
224
onVerticesDrawn(const Context * context,GLsizei count,GLsizei primcount)225 void TransformFeedback::onVerticesDrawn(const Context *context, GLsizei count, GLsizei primcount)
226 {
227 ASSERT(mState.mActive && !mState.mPaused);
228 // All draws should be validated with checkBufferSpaceForDraw so ValueOrDie should never fail.
229 mState.mVerticesDrawn =
230 (mState.mVerticesDrawn + GetVerticesNeededForDraw(mState.mPrimitiveMode, count, primcount))
231 .ValueOrDie();
232
233 for (auto &buffer : mState.mIndexedBuffers)
234 {
235 if (buffer.get() != nullptr)
236 {
237 buffer->onDataChanged();
238 }
239 }
240 }
241
bindProgram(const Context * context,Program * program)242 void TransformFeedback::bindProgram(const Context *context, Program *program)
243 {
244 if (mState.mProgram != program)
245 {
246 if (mState.mProgram != nullptr)
247 {
248 mState.mProgram->release(context);
249 }
250 mState.mProgram = program;
251 if (mState.mProgram != nullptr)
252 {
253 mState.mProgram->addRef();
254 }
255 }
256 }
257
hasBoundProgram(ShaderProgramID program) const258 bool TransformFeedback::hasBoundProgram(ShaderProgramID program) const
259 {
260 return mState.mProgram != nullptr && mState.mProgram->id().value == program.value;
261 }
262
detachBuffer(const Context * context,BufferID bufferID)263 angle::Result TransformFeedback::detachBuffer(const Context *context, BufferID bufferID)
264 {
265 bool isBound = context->isCurrentTransformFeedback(this);
266 for (size_t index = 0; index < mState.mIndexedBuffers.size(); index++)
267 {
268 if (mState.mIndexedBuffers[index].id() == bufferID)
269 {
270 if (isBound)
271 {
272 mState.mIndexedBuffers[index]->onTFBindingChanged(context, false, true);
273 }
274 mState.mIndexedBuffers[index].set(context, nullptr, 0, 0);
275 ANGLE_TRY(
276 mImplementation->bindIndexedBuffer(context, index, mState.mIndexedBuffers[index]));
277 }
278 }
279
280 return angle::Result::Continue;
281 }
282
bindIndexedBuffer(const Context * context,size_t index,Buffer * buffer,size_t offset,size_t size)283 angle::Result TransformFeedback::bindIndexedBuffer(const Context *context,
284 size_t index,
285 Buffer *buffer,
286 size_t offset,
287 size_t size)
288 {
289 ASSERT(index < mState.mIndexedBuffers.size());
290 bool isBound = context && context->isCurrentTransformFeedback(this);
291 if (isBound && mState.mIndexedBuffers[index].get())
292 {
293 mState.mIndexedBuffers[index]->onTFBindingChanged(context, false, true);
294 }
295 mState.mIndexedBuffers[index].set(context, buffer, offset, size);
296 if (isBound && buffer)
297 {
298 buffer->onTFBindingChanged(context, true, true);
299 }
300
301 return mImplementation->bindIndexedBuffer(context, index, mState.mIndexedBuffers[index]);
302 }
303
getIndexedBuffer(size_t index) const304 const OffsetBindingPointer<Buffer> &TransformFeedback::getIndexedBuffer(size_t index) const
305 {
306 ASSERT(index < mState.mIndexedBuffers.size());
307 return mState.mIndexedBuffers[index];
308 }
309
getIndexedBufferCount() const310 size_t TransformFeedback::getIndexedBufferCount() const
311 {
312 return mState.mIndexedBuffers.size();
313 }
314
buffersBoundForOtherUseInWebGL() const315 bool TransformFeedback::buffersBoundForOtherUseInWebGL() const
316 {
317 for (auto &buffer : mState.mIndexedBuffers)
318 {
319 if (buffer.get() && buffer->hasWebGLXFBBindingConflict(true))
320 {
321 return true;
322 }
323 }
324 return false;
325 }
326
getImplementation() const327 rx::TransformFeedbackImpl *TransformFeedback::getImplementation() const
328 {
329 return mImplementation;
330 }
331
onBindingChanged(const Context * context,bool bound)332 void TransformFeedback::onBindingChanged(const Context *context, bool bound)
333 {
334 for (auto &buffer : mState.mIndexedBuffers)
335 {
336 if (buffer.get())
337 {
338 buffer->onTFBindingChanged(context, bound, true);
339 }
340 }
341 }
342
getIndexedBuffers() const343 const std::vector<OffsetBindingPointer<Buffer>> &TransformFeedback::getIndexedBuffers() const
344 {
345 return mState.mIndexedBuffers;
346 }
347 } // namespace gl
348