1 /*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/gpu/gl/GrGLOpsRenderPass.h"
9
10 #include "src/gpu/GrProgramInfo.h"
11 #include "src/gpu/GrRenderTarget.h"
12
13 #ifdef SK_DEBUG
14 #include "include/gpu/GrDirectContext.h"
15 #include "src/gpu/GrDirectContextPriv.h"
16 #endif
17
18 #define GL_CALL(X) GR_GL_CALL(fGpu->glInterface(), X)
19
set(GrRenderTarget * rt,bool useMSAASurface,const SkIRect & contentBounds,GrSurfaceOrigin origin,const LoadAndStoreInfo & colorInfo,const StencilLoadAndStoreInfo & stencilInfo)20 void GrGLOpsRenderPass::set(GrRenderTarget* rt, bool useMSAASurface, const SkIRect& contentBounds,
21 GrSurfaceOrigin origin, const LoadAndStoreInfo& colorInfo,
22 const StencilLoadAndStoreInfo& stencilInfo) {
23 SkASSERT(fGpu);
24 SkASSERT(!fRenderTarget);
25 SkASSERT(fGpu == rt->getContext()->priv().getGpu());
26
27 this->INHERITED::set(rt, origin);
28 fUseMultisampleFBO = useMSAASurface;
29 fContentBounds = contentBounds;
30 fColorLoadAndStoreInfo = colorInfo;
31 fStencilLoadAndStoreInfo = stencilInfo;
32 }
33
onBegin()34 void GrGLOpsRenderPass::onBegin() {
35 auto glRT = static_cast<GrGLRenderTarget*>(fRenderTarget);
36 if (fUseMultisampleFBO && fColorLoadAndStoreInfo.fLoadOp == GrLoadOp::kLoad &&
37 glRT->hasDynamicMSAAAttachment()) {
38 // Blit the single sample fbo into the dmsaa attachment.
39 auto nativeBounds = GrNativeRect::MakeRelativeTo(fOrigin, fRenderTarget->height(),
40 fContentBounds);
41 fGpu->resolveRenderFBOs(glRT, nativeBounds.asSkIRect(),
42 GrGLGpu::ResolveDirection::kSingleToMSAA);
43 }
44
45 fGpu->beginCommandBuffer(glRT, fUseMultisampleFBO, fContentBounds, fOrigin,
46 fColorLoadAndStoreInfo, fStencilLoadAndStoreInfo);
47 }
48
onEnd()49 void GrGLOpsRenderPass::onEnd() {
50 auto glRT = static_cast<GrGLRenderTarget*>(fRenderTarget);
51 fGpu->endCommandBuffer(glRT, fUseMultisampleFBO, fColorLoadAndStoreInfo,
52 fStencilLoadAndStoreInfo);
53
54 if (fUseMultisampleFBO && fColorLoadAndStoreInfo.fStoreOp == GrStoreOp::kStore &&
55 glRT->hasDynamicMSAAAttachment()) {
56 // Blit the msaa attachment into the single sample fbo.
57 auto nativeBounds = GrNativeRect::MakeRelativeTo(fOrigin, fRenderTarget->height(),
58 fContentBounds);
59 fGpu->resolveRenderFBOs(glRT, nativeBounds.asSkIRect(),
60 GrGLGpu::ResolveDirection::kMSAAToSingle,
61 true /*invalidateReadBufferAfterBlit*/);
62 }
63 }
64
onBindPipeline(const GrProgramInfo & programInfo,const SkRect & drawBounds)65 bool GrGLOpsRenderPass::onBindPipeline(const GrProgramInfo& programInfo,
66 const SkRect& drawBounds) {
67 fPrimitiveType = programInfo.primitiveType();
68 return fGpu->flushGLState(fRenderTarget, fUseMultisampleFBO, programInfo);
69 }
70
onSetScissorRect(const SkIRect & scissor)71 void GrGLOpsRenderPass::onSetScissorRect(const SkIRect& scissor) {
72 fGpu->flushScissorRect(scissor, fRenderTarget->height(), fOrigin);
73 }
74
onBindTextures(const GrGeometryProcessor & geomProc,const GrSurfaceProxy * const geomProcTextures[],const GrPipeline & pipeline)75 bool GrGLOpsRenderPass::onBindTextures(const GrGeometryProcessor& geomProc,
76 const GrSurfaceProxy* const geomProcTextures[],
77 const GrPipeline& pipeline) {
78 GrGLProgram* program = fGpu->currentProgram();
79 SkASSERT(program);
80 program->bindTextures(geomProc, geomProcTextures, pipeline);
81 return true;
82 }
83
onBindBuffers(sk_sp<const GrBuffer> indexBuffer,sk_sp<const GrBuffer> instanceBuffer,sk_sp<const GrBuffer> vertexBuffer,GrPrimitiveRestart primitiveRestart)84 void GrGLOpsRenderPass::onBindBuffers(sk_sp<const GrBuffer> indexBuffer,
85 sk_sp<const GrBuffer> instanceBuffer,
86 sk_sp<const GrBuffer> vertexBuffer,
87 GrPrimitiveRestart primitiveRestart) {
88 SkASSERT((primitiveRestart == GrPrimitiveRestart::kNo) || indexBuffer);
89 GrGLProgram* program = fGpu->currentProgram();
90 SkASSERT(program);
91
92 #ifdef SK_DEBUG
93 fDidBindInstanceBuffer = false;
94 fDidBindVertexBuffer = false;
95 #endif
96
97 int numAttribs = program->numVertexAttributes() + program->numInstanceAttributes();
98 fAttribArrayState = fGpu->bindInternalVertexArray(indexBuffer.get(), numAttribs,
99 primitiveRestart);
100
101 if (indexBuffer) {
102 if (indexBuffer->isCpuBuffer()) {
103 auto* cpuIndexBuffer = static_cast<const GrCpuBuffer*>(indexBuffer.get());
104 fIndexPointer = reinterpret_cast<const uint16_t*>(cpuIndexBuffer->data());
105 } else {
106 fIndexPointer = nullptr;
107 }
108 }
109
110 // If this platform does not support baseInstance, defer binding of the instance buffer.
111 if (fGpu->glCaps().baseVertexBaseInstanceSupport()) {
112 this->bindInstanceBuffer(instanceBuffer.get(), 0);
113 SkDEBUGCODE(fDidBindInstanceBuffer = true;)
114 }
115 fActiveInstanceBuffer = std::move(instanceBuffer);
116
117 // We differ binding the vertex buffer for one of two situations:
118 // 1) This platform does not support baseVertex with indexed draws.
119 // 2) There is a driver bug affecting glDrawArrays.
120 if ((indexBuffer && fGpu->glCaps().baseVertexBaseInstanceSupport()) ||
121 (!indexBuffer && !fGpu->glCaps().drawArraysBaseVertexIsBroken())) {
122 this->bindVertexBuffer(vertexBuffer.get(), 0);
123 SkDEBUGCODE(fDidBindVertexBuffer = true;)
124 }
125 fActiveVertexBuffer = std::move(vertexBuffer);
126 fActiveIndexBuffer = std::move(indexBuffer);
127 }
128
bindInstanceBuffer(const GrBuffer * instanceBuffer,int baseInstance)129 void GrGLOpsRenderPass::bindInstanceBuffer(const GrBuffer* instanceBuffer, int baseInstance) {
130 GrGLProgram* program = fGpu->currentProgram();
131 SkASSERT(program);
132 if (int instanceStride = program->instanceStride()) {
133 SkASSERT(instanceBuffer);
134 SkASSERT(instanceBuffer->isCpuBuffer() ||
135 !static_cast<const GrGpuBuffer*>(instanceBuffer)->isMapped());
136 size_t bufferOffset = baseInstance * static_cast<size_t>(instanceStride);
137 int attribIdx = program->numVertexAttributes();
138 for (int i = 0; i < program->numInstanceAttributes(); ++i, ++attribIdx) {
139 const auto& attrib = program->instanceAttribute(i);
140 static constexpr int kDivisor = 1;
141 fAttribArrayState->set(fGpu, attrib.fLocation, instanceBuffer, attrib.fCPUType,
142 attrib.fGPUType, instanceStride, bufferOffset + attrib.fOffset,
143 kDivisor);
144 }
145 }
146 }
147
bindVertexBuffer(const GrBuffer * vertexBuffer,int baseVertex)148 void GrGLOpsRenderPass::bindVertexBuffer(const GrBuffer* vertexBuffer, int baseVertex) {
149 GrGLProgram* program = fGpu->currentProgram();
150 SkASSERT(program);
151 if (int vertexStride = program->vertexStride()) {
152 SkASSERT(vertexBuffer);
153 SkASSERT(vertexBuffer->isCpuBuffer() ||
154 !static_cast<const GrGpuBuffer*>(vertexBuffer)->isMapped());
155 size_t bufferOffset = baseVertex * static_cast<size_t>(vertexStride);
156 for (int i = 0; i < program->numVertexAttributes(); ++i) {
157 const auto& attrib = program->vertexAttribute(i);
158 static constexpr int kDivisor = 0;
159 fAttribArrayState->set(fGpu, attrib.fLocation, vertexBuffer, attrib.fCPUType,
160 attrib.fGPUType, vertexStride, bufferOffset + attrib.fOffset,
161 kDivisor);
162 }
163 }
164 }
165
onDraw(int vertexCount,int baseVertex)166 void GrGLOpsRenderPass::onDraw(int vertexCount, int baseVertex) {
167 SkASSERT(fDidBindVertexBuffer || fGpu->glCaps().drawArraysBaseVertexIsBroken());
168 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
169 if (fGpu->glCaps().drawArraysBaseVertexIsBroken()) {
170 this->bindVertexBuffer(fActiveVertexBuffer.get(), baseVertex);
171 baseVertex = 0;
172 }
173 GL_CALL(DrawArrays(glPrimType, baseVertex, vertexCount));
174 }
175
onDrawIndexed(int indexCount,int baseIndex,uint16_t minIndexValue,uint16_t maxIndexValue,int baseVertex)176 void GrGLOpsRenderPass::onDrawIndexed(int indexCount, int baseIndex, uint16_t minIndexValue,
177 uint16_t maxIndexValue, int baseVertex) {
178 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
179 if (fGpu->glCaps().baseVertexBaseInstanceSupport()) {
180 SkASSERT(fGpu->glCaps().drawInstancedSupport());
181 SkASSERT(fDidBindVertexBuffer);
182 if (baseVertex != 0) {
183 GL_CALL(DrawElementsInstancedBaseVertexBaseInstance(
184 glPrimType, indexCount, GR_GL_UNSIGNED_SHORT,
185 this->offsetForBaseIndex(baseIndex), 1, baseVertex, 0));
186 return;
187 }
188 } else {
189 this->bindVertexBuffer(fActiveVertexBuffer.get(), baseVertex);
190 }
191
192 if (fGpu->glCaps().drawRangeElementsSupport()) {
193 GL_CALL(DrawRangeElements(glPrimType, minIndexValue, maxIndexValue, indexCount,
194 GR_GL_UNSIGNED_SHORT, this->offsetForBaseIndex(baseIndex)));
195 } else {
196 GL_CALL(DrawElements(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT,
197 this->offsetForBaseIndex(baseIndex)));
198 }
199 }
200
onDrawInstanced(int instanceCount,int baseInstance,int vertexCount,int baseVertex)201 void GrGLOpsRenderPass::onDrawInstanced(int instanceCount, int baseInstance, int vertexCount,
202 int baseVertex) {
203 SkASSERT(fDidBindVertexBuffer || fGpu->glCaps().drawArraysBaseVertexIsBroken());
204 if (fGpu->glCaps().drawArraysBaseVertexIsBroken()) {
205 // We weren't able to bind the vertex buffer during onBindBuffers because of a driver bug
206 // affecting glDrawArrays.
207 this->bindVertexBuffer(fActiveVertexBuffer.get(), 0);
208 }
209 int maxInstances = fGpu->glCaps().maxInstancesPerDrawWithoutCrashing(instanceCount);
210 for (int i = 0; i < instanceCount; i += maxInstances) {
211 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
212 int instanceCountForDraw = std::min(instanceCount - i, maxInstances);
213 int baseInstanceForDraw = baseInstance + i;
214 if (fGpu->glCaps().baseVertexBaseInstanceSupport()) {
215 SkASSERT(fDidBindInstanceBuffer);
216 GL_CALL(DrawArraysInstancedBaseInstance(glPrimType, baseVertex, vertexCount,
217 instanceCountForDraw, baseInstanceForDraw));
218 } else {
219 this->bindInstanceBuffer(fActiveInstanceBuffer.get(), baseInstanceForDraw);
220 GL_CALL(DrawArraysInstanced(glPrimType, baseVertex, vertexCount, instanceCountForDraw));
221 }
222 }
223 }
224
onDrawIndexedInstanced(int indexCount,int baseIndex,int instanceCount,int baseInstance,int baseVertex)225 void GrGLOpsRenderPass::onDrawIndexedInstanced(int indexCount, int baseIndex, int instanceCount,
226 int baseInstance, int baseVertex) {
227 int maxInstances = fGpu->glCaps().maxInstancesPerDrawWithoutCrashing(instanceCount);
228 for (int i = 0; i < instanceCount; i += maxInstances) {
229 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
230 int instanceCountForDraw = std::min(instanceCount - i, maxInstances);
231 int baseInstanceForDraw = baseInstance + i;
232 if (fGpu->glCaps().baseVertexBaseInstanceSupport()) {
233 SkASSERT(fDidBindInstanceBuffer);
234 SkASSERT(fDidBindVertexBuffer);
235 GL_CALL(DrawElementsInstancedBaseVertexBaseInstance(
236 glPrimType, indexCount, GR_GL_UNSIGNED_SHORT,
237 this->offsetForBaseIndex(baseIndex), instanceCountForDraw, baseVertex,
238 baseInstanceForDraw));
239 } else {
240 this->bindInstanceBuffer(fActiveInstanceBuffer.get(), baseInstanceForDraw);
241 this->bindVertexBuffer(fActiveVertexBuffer.get(), baseVertex);
242 GL_CALL(DrawElementsInstanced(glPrimType, indexCount, GR_GL_UNSIGNED_SHORT,
243 this->offsetForBaseIndex(baseIndex), instanceCountForDraw));
244 }
245 }
246 }
247
buffer_offset_to_gl_address(const GrBuffer * drawIndirectBuffer,size_t offset)248 static const void* buffer_offset_to_gl_address(const GrBuffer* drawIndirectBuffer, size_t offset) {
249 if (drawIndirectBuffer->isCpuBuffer()) {
250 return static_cast<const GrCpuBuffer*>(drawIndirectBuffer)->data() + offset;
251 } else {
252 return (offset) ? reinterpret_cast<const void*>(offset) : nullptr;
253 }
254 }
255
onDrawIndirect(const GrBuffer * drawIndirectBuffer,size_t offset,int drawCount)256 void GrGLOpsRenderPass::onDrawIndirect(const GrBuffer* drawIndirectBuffer, size_t offset,
257 int drawCount) {
258 using MultiDrawType = GrGLCaps::MultiDrawType;
259
260 SkASSERT(fGpu->caps()->nativeDrawIndirectSupport());
261 SkASSERT(fGpu->glCaps().baseVertexBaseInstanceSupport());
262 SkASSERT(fDidBindVertexBuffer || fGpu->glCaps().drawArraysBaseVertexIsBroken());
263
264 if (fGpu->glCaps().drawArraysBaseVertexIsBroken()) {
265 // We weren't able to bind the vertex buffer during onBindBuffers because of a driver bug
266 // affecting glDrawArrays.
267 this->bindVertexBuffer(fActiveVertexBuffer.get(), 0);
268 }
269
270 if (fGpu->glCaps().multiDrawType() == MultiDrawType::kANGLEOrWebGL) {
271 // ANGLE and WebGL don't support glDrawElementsIndirect. We draw everything as a multi draw.
272 this->multiDrawArraysANGLEOrWebGL(drawIndirectBuffer, offset, drawCount);
273 return;
274 }
275
276 fGpu->bindBuffer(GrGpuBufferType::kDrawIndirect, drawIndirectBuffer);
277
278 if (drawCount > 1 && fGpu->glCaps().multiDrawType() == MultiDrawType::kMultiDrawIndirect) {
279 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
280 GL_CALL(MultiDrawArraysIndirect(glPrimType,
281 buffer_offset_to_gl_address(drawIndirectBuffer, offset),
282 drawCount, sizeof(GrDrawIndirectCommand)));
283 return;
284 }
285
286 for (int i = 0; i < drawCount; ++i) {
287 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
288 GL_CALL(DrawArraysIndirect(glPrimType,
289 buffer_offset_to_gl_address(drawIndirectBuffer, offset)));
290 offset += sizeof(GrDrawIndirectCommand);
291 }
292 }
293
multiDrawArraysANGLEOrWebGL(const GrBuffer * drawIndirectBuffer,size_t offset,int drawCount)294 void GrGLOpsRenderPass::multiDrawArraysANGLEOrWebGL(const GrBuffer* drawIndirectBuffer,
295 size_t offset, int drawCount) {
296 SkASSERT(fGpu->glCaps().multiDrawType() == GrGLCaps::MultiDrawType::kANGLEOrWebGL);
297 SkASSERT(drawIndirectBuffer->isCpuBuffer());
298
299 constexpr static int kMaxDrawCountPerBatch = 128;
300 GrGLint fFirsts[kMaxDrawCountPerBatch];
301 GrGLsizei fCounts[kMaxDrawCountPerBatch];
302 GrGLsizei fInstanceCounts[kMaxDrawCountPerBatch];
303 GrGLuint fBaseInstances[kMaxDrawCountPerBatch];
304
305 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
306 auto* cpuBuffer = static_cast<const GrCpuBuffer*>(drawIndirectBuffer);
307 auto* cmds = reinterpret_cast<const GrDrawIndirectCommand*>(cpuBuffer->data() + offset);
308
309 while (drawCount) {
310 int countInBatch = std::min(drawCount, kMaxDrawCountPerBatch);
311 for (int i = 0; i < countInBatch; ++i) {
312 auto [vertexCount, instanceCount, baseVertex, baseInstance] = cmds[i];
313 fFirsts[i] = baseVertex;
314 fCounts[i] = vertexCount;
315 fInstanceCounts[i] = instanceCount;
316 fBaseInstances[i] = baseInstance;
317 }
318 if (countInBatch == 1) {
319 GL_CALL(DrawArraysInstancedBaseInstance(glPrimType, fFirsts[0], fCounts[0],
320 fInstanceCounts[0], fBaseInstances[0]));
321 } else {
322 GL_CALL(MultiDrawArraysInstancedBaseInstance(glPrimType, fFirsts, fCounts,
323 fInstanceCounts, fBaseInstances,
324 countInBatch));
325 }
326 drawCount -= countInBatch;
327 cmds += countInBatch;
328 }
329 }
330
onDrawIndexedIndirect(const GrBuffer * drawIndirectBuffer,size_t offset,int drawCount)331 void GrGLOpsRenderPass::onDrawIndexedIndirect(const GrBuffer* drawIndirectBuffer, size_t offset,
332 int drawCount) {
333 using MultiDrawType = GrGLCaps::MultiDrawType;
334
335 SkASSERT(fGpu->caps()->nativeDrawIndirectSupport());
336 SkASSERT(!fGpu->caps()->nativeDrawIndexedIndirectIsBroken());
337 SkASSERT(fGpu->glCaps().baseVertexBaseInstanceSupport());
338 // The vertex buffer should have already gotten bound (as opposed us stashing it away during
339 // onBindBuffers and not expecting to bind it until this point).
340 SkASSERT(fDidBindVertexBuffer);
341
342 if (fGpu->glCaps().multiDrawType() == MultiDrawType::kANGLEOrWebGL) {
343 // ANGLE and WebGL don't support glDrawElementsIndirect. We draw everything as a multi draw.
344 this->multiDrawElementsANGLEOrWebGL(drawIndirectBuffer, offset, drawCount);
345 return;
346 }
347
348 fGpu->bindBuffer(GrGpuBufferType::kDrawIndirect, drawIndirectBuffer);
349
350 if (drawCount > 1 && fGpu->glCaps().multiDrawType() == MultiDrawType::kMultiDrawIndirect) {
351 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
352 GL_CALL(MultiDrawElementsIndirect(glPrimType, GR_GL_UNSIGNED_SHORT,
353 buffer_offset_to_gl_address(drawIndirectBuffer, offset),
354 drawCount, sizeof(GrDrawIndexedIndirectCommand)));
355 return;
356 }
357
358 for (int i = 0; i < drawCount; ++i) {
359 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
360 GL_CALL(DrawElementsIndirect(glPrimType, GR_GL_UNSIGNED_SHORT,
361 buffer_offset_to_gl_address(drawIndirectBuffer, offset)));
362 offset += sizeof(GrDrawIndexedIndirectCommand);
363 }
364 }
365
multiDrawElementsANGLEOrWebGL(const GrBuffer * drawIndirectBuffer,size_t offset,int drawCount)366 void GrGLOpsRenderPass::multiDrawElementsANGLEOrWebGL(const GrBuffer* drawIndirectBuffer,
367 size_t offset, int drawCount) {
368 SkASSERT(fGpu->glCaps().multiDrawType() == GrGLCaps::MultiDrawType::kANGLEOrWebGL);
369 SkASSERT(drawIndirectBuffer->isCpuBuffer());
370
371 constexpr static int kMaxDrawCountPerBatch = 128;
372 GrGLint fCounts[kMaxDrawCountPerBatch];
373 const void* fIndices[kMaxDrawCountPerBatch];
374 GrGLsizei fInstanceCounts[kMaxDrawCountPerBatch];
375 GrGLint fBaseVertices[kMaxDrawCountPerBatch];
376 GrGLuint fBaseInstances[kMaxDrawCountPerBatch];
377
378 GrGLenum glPrimType = fGpu->prepareToDraw(fPrimitiveType);
379 auto* cpuBuffer = static_cast<const GrCpuBuffer*>(drawIndirectBuffer);
380 auto* cmds = reinterpret_cast<const GrDrawIndexedIndirectCommand*>(cpuBuffer->data() + offset);
381
382 while (drawCount) {
383 int countInBatch = std::min(drawCount, kMaxDrawCountPerBatch);
384 for (int i = 0; i < countInBatch; ++i) {
385 auto [indexCount, instanceCount, baseIndex, baseVertex, baseInstance] = cmds[i];
386 fCounts[i] = indexCount;
387 fIndices[i] = this->offsetForBaseIndex(baseIndex);
388 fInstanceCounts[i] = instanceCount;
389 fBaseVertices[i] = baseVertex;
390 fBaseInstances[i] = baseInstance;
391 }
392 if (countInBatch == 1) {
393 GL_CALL(DrawElementsInstancedBaseVertexBaseInstance(glPrimType, fCounts[0],
394 GR_GL_UNSIGNED_SHORT, fIndices[0],
395 fInstanceCounts[0],
396 fBaseVertices[0],
397 fBaseInstances[0]));
398 } else {
399 GL_CALL(MultiDrawElementsInstancedBaseVertexBaseInstance(glPrimType, fCounts,
400 GR_GL_UNSIGNED_SHORT, fIndices,
401 fInstanceCounts, fBaseVertices,
402 fBaseInstances, countInBatch));
403 }
404 drawCount -= countInBatch;
405 cmds += countInBatch;
406 }
407 }
408
onClear(const GrScissorState & scissor,std::array<float,4> color)409 void GrGLOpsRenderPass::onClear(const GrScissorState& scissor, std::array<float, 4> color) {
410 fGpu->clear(scissor, color, fRenderTarget, fUseMultisampleFBO, fOrigin);
411 }
412
onClearStencilClip(const GrScissorState & scissor,bool insideStencilMask)413 void GrGLOpsRenderPass::onClearStencilClip(const GrScissorState& scissor, bool insideStencilMask) {
414 fGpu->clearStencilClip(scissor, insideStencilMask, fRenderTarget, fUseMultisampleFBO, fOrigin);
415 }
416