1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
3 | #include "BsGLPixelBuffer.h" |
4 | #include "BsGLTexture.h" |
5 | #include "BsGLSupport.h" |
6 | #include "BsGLPixelFormat.h" |
7 | #include "Error/BsException.h" |
8 | #include "Utility/BsBitwise.h" |
9 | #include "BsGLRenderTexture.h" |
10 | #include "Profiling/BsRenderStats.h" |
11 | #include "Math/BsMath.h" |
12 | |
13 | namespace bs { namespace ct |
14 | { |
15 | GLPixelBuffer::GLPixelBuffer(UINT32 inWidth, UINT32 inHeight, UINT32 inDepth, PixelFormat inFormat, GpuBufferUsage usage) |
16 | : mUsage(usage), mWidth(inWidth), mHeight(inHeight), mDepth(inDepth), mFormat(inFormat) |
17 | , mBuffer(inWidth, inHeight, inDepth, inFormat) |
18 | { |
19 | mSizeInBytes = mHeight*mWidth*PixelUtil::getNumElemBytes(mFormat); |
20 | mCurrentLockOptions = (GpuLockOptions)0; |
21 | } |
22 | |
23 | GLPixelBuffer::~GLPixelBuffer() |
24 | { |
25 | mBuffer.freeInternalBuffer(); |
26 | } |
27 | |
28 | void GLPixelBuffer::allocateBuffer() |
29 | { |
30 | if(mBuffer.getData()) |
31 | return; |
32 | |
33 | mBuffer.allocateInternalBuffer(); |
34 | // TODO: use PBO if we're HBU_DYNAMIC |
35 | } |
36 | |
37 | void GLPixelBuffer::freeBuffer() |
38 | { |
39 | if(mUsage & GBU_STATIC) |
40 | mBuffer.freeInternalBuffer(); |
41 | } |
42 | |
43 | void* GLPixelBuffer::lock(UINT32 offset, UINT32 length, GpuLockOptions options) |
44 | { |
45 | assert(!mIsLocked && "Cannot lock this buffer, it is already locked!" ); |
46 | assert(offset == 0 && length == mSizeInBytes && "Cannot lock memory region, most lock box or entire buffer" ); |
47 | |
48 | PixelVolume volume(0, 0, 0, mWidth, mHeight, mDepth); |
49 | const PixelData& lockedData = lock(volume, options); |
50 | return lockedData.getData(); |
51 | } |
52 | |
53 | const PixelData& GLPixelBuffer::lock(const PixelVolume& lockBox, GpuLockOptions options) |
54 | { |
55 | allocateBuffer(); |
56 | |
57 | if (options != GBL_WRITE_ONLY_DISCARD) |
58 | { |
59 | // Download the old contents of the texture |
60 | download(mBuffer); |
61 | } |
62 | |
63 | mCurrentLockOptions = options; |
64 | mLockedBox = lockBox; |
65 | |
66 | mCurrentLock = mBuffer.getSubVolume(lockBox); |
67 | mIsLocked = true; |
68 | |
69 | return mCurrentLock; |
70 | } |
71 | |
72 | void GLPixelBuffer::unlock() |
73 | { |
74 | assert(mIsLocked && "Cannot unlock this buffer, it is not locked!" ); |
75 | |
76 | if (mCurrentLockOptions != GBL_READ_ONLY) |
77 | { |
78 | // From buffer to card, only upload if was locked for writing |
79 | upload(mCurrentLock, mLockedBox); |
80 | } |
81 | |
82 | freeBuffer(); |
83 | mIsLocked = false; |
84 | } |
85 | |
86 | void GLPixelBuffer::upload(const PixelData& data, const PixelVolume& dest) |
87 | { |
88 | BS_EXCEPT(RenderingAPIException, "Upload not possible for this pixel buffer type" ); |
89 | } |
90 | |
91 | void GLPixelBuffer::download(const PixelData& data) |
92 | { |
93 | BS_EXCEPT(RenderingAPIException, "Download not possible for this pixel buffer type" ); |
94 | } |
95 | |
96 | void GLPixelBuffer::blitFromTexture(GLTextureBuffer* src) |
97 | { |
98 | blitFromTexture(src, |
99 | PixelVolume(0, 0, 0, src->getWidth(), src->getHeight(), src->getDepth()), |
100 | PixelVolume(0, 0, 0, mWidth, mHeight, mDepth) |
101 | ); |
102 | } |
103 | |
104 | void GLPixelBuffer::blitFromTexture(GLTextureBuffer* src, const PixelVolume& srcBox, const PixelVolume& dstBox) |
105 | { |
106 | BS_EXCEPT(RenderingAPIException, "BlitFromTexture not possible for this pixel buffer type" ); |
107 | } |
108 | |
109 | void GLPixelBuffer::bindToFramebuffer(GLenum attachment, UINT32 zoffset, bool allLayers) |
110 | { |
111 | BS_EXCEPT(RenderingAPIException, "Framebuffer bind not possible for this pixel buffer type" ); |
112 | } |
113 | |
114 | GLTextureBuffer::GLTextureBuffer(GLenum target, GLuint id, GLint face, GLint level, PixelFormat format, |
115 | GpuBufferUsage usage, bool hwGamma, UINT32 multisampleCount) |
116 | : GLPixelBuffer(0, 0, 0, format, usage), mTarget(target), mTextureID(id), mFace(face) |
117 | , mLevel(level), mMultisampleCount(multisampleCount), mHwGamma(hwGamma) |
118 | { |
119 | GLint value = 0; |
120 | |
121 | glBindTexture(mTarget, mTextureID); |
122 | BS_CHECK_GL_ERROR(); |
123 | |
124 | // Get face identifier |
125 | mFaceTarget = mTarget; |
126 | if(mTarget == GL_TEXTURE_CUBE_MAP) |
127 | mFaceTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + (face % 6); |
128 | |
129 | // Get width |
130 | glGetTexLevelParameteriv(mFaceTarget, level, GL_TEXTURE_WIDTH, &value); |
131 | BS_CHECK_GL_ERROR(); |
132 | |
133 | mWidth = value; |
134 | |
135 | // Get height |
136 | if(target == GL_TEXTURE_1D) |
137 | value = 1; // Height always 1 for 1D textures |
138 | else |
139 | { |
140 | glGetTexLevelParameteriv(mFaceTarget, level, GL_TEXTURE_HEIGHT, &value); |
141 | BS_CHECK_GL_ERROR(); |
142 | } |
143 | |
144 | mHeight = value; |
145 | |
146 | // Get depth |
147 | if(target != GL_TEXTURE_3D) |
148 | value = 1; // Depth always 1 for non-3D textures |
149 | else |
150 | { |
151 | glGetTexLevelParameteriv(mFaceTarget, level, GL_TEXTURE_DEPTH, &value); |
152 | BS_CHECK_GL_ERROR(); |
153 | } |
154 | |
155 | mDepth = value; |
156 | |
157 | // Default |
158 | mSizeInBytes = PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat); |
159 | |
160 | // Set up pixel box |
161 | mBuffer = PixelData(mWidth, mHeight, mDepth, mFormat); |
162 | } |
163 | |
164 | void GLTextureBuffer::upload(const PixelData& data, const PixelVolume& dest) |
165 | { |
166 | if ((mUsage & TU_DEPTHSTENCIL) != 0) |
167 | { |
168 | LOGERR("Writing to depth stencil texture from CPU not supported." ); |
169 | return; |
170 | } |
171 | |
172 | glBindTexture(mTarget, mTextureID); |
173 | BS_CHECK_GL_ERROR(); |
174 | |
175 | if(PixelUtil::isCompressed(data.getFormat())) |
176 | { |
177 | // Block-compressed data cannot be smaller than 4x4, and must be a multiple of 4 |
178 | const UINT32 actualWidth = Math::divideAndRoundUp(std::max(mWidth, 4U), 4U) * 4U; |
179 | const UINT32 actualHeight = Math::divideAndRoundUp(std::max(mHeight, 4U), 4U) * 4U; |
180 | |
181 | const UINT32 expectedRowPitch = actualWidth; |
182 | const UINT32 expectedSlicePitch = actualWidth * actualHeight; |
183 | |
184 | const bool isConsecutive = data.getRowPitch() == expectedRowPitch && data.getSlicePitch() == expectedSlicePitch; |
185 | if (data.getFormat() != mFormat || !isConsecutive) |
186 | { |
187 | LOGERR("Compressed images must be consecutive, in the source format" ); |
188 | return; |
189 | } |
190 | |
191 | GLenum format = GLPixelUtil::getGLInternalFormat(mFormat, mHwGamma); |
192 | switch(mTarget) |
193 | { |
194 | case GL_TEXTURE_1D: |
195 | glCompressedTexSubImage1D(GL_TEXTURE_1D, mLevel, |
196 | dest.left, |
197 | dest.getWidth(), |
198 | format, data.getConsecutiveSize(), |
199 | data.getData()); |
200 | BS_CHECK_GL_ERROR(); |
201 | break; |
202 | case GL_TEXTURE_2D: |
203 | case GL_TEXTURE_CUBE_MAP: |
204 | glCompressedTexSubImage2D(mFaceTarget, mLevel, |
205 | dest.left, dest.top, |
206 | dest.getWidth(), dest.getHeight(), |
207 | format, data.getConsecutiveSize(), |
208 | data.getData()); |
209 | BS_CHECK_GL_ERROR(); |
210 | break; |
211 | case GL_TEXTURE_3D: |
212 | glCompressedTexSubImage3D(GL_TEXTURE_3D, mLevel, |
213 | dest.left, dest.top, dest.front, |
214 | dest.getWidth(), dest.getHeight(), dest.getDepth(), |
215 | format, data.getConsecutiveSize(), |
216 | data.getData()); |
217 | BS_CHECK_GL_ERROR(); |
218 | break; |
219 | default: |
220 | break; |
221 | } |
222 | |
223 | } |
224 | else |
225 | { |
226 | if (data.getWidth() != data.getRowPitch()) |
227 | { |
228 | glPixelStorei(GL_UNPACK_ROW_LENGTH, data.getRowPitch()); |
229 | BS_CHECK_GL_ERROR(); |
230 | } |
231 | |
232 | if (data.getHeight()*data.getWidth() != data.getSlicePitch()) |
233 | { |
234 | glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, (data.getSlicePitch() / data.getWidth())); |
235 | BS_CHECK_GL_ERROR(); |
236 | } |
237 | |
238 | if (data.getLeft() > 0 || data.getTop() > 0 || data.getFront() > 0) |
239 | { |
240 | glPixelStorei( |
241 | GL_UNPACK_SKIP_PIXELS, |
242 | data.getLeft() + data.getRowPitch() * data.getTop() + data.getSlicePitch() * data.getFront()); |
243 | BS_CHECK_GL_ERROR(); |
244 | } |
245 | |
246 | if ((data.getWidth()*PixelUtil::getNumElemBytes(data.getFormat())) & 3) |
247 | { |
248 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
249 | BS_CHECK_GL_ERROR(); |
250 | } |
251 | |
252 | switch(mTarget) { |
253 | case GL_TEXTURE_1D: |
254 | glTexSubImage1D(GL_TEXTURE_1D, mLevel, |
255 | dest.left, |
256 | dest.getWidth(), |
257 | GLPixelUtil::getGLOriginFormat(data.getFormat()), GLPixelUtil::getGLOriginDataType(data.getFormat()), |
258 | data.getData()); |
259 | BS_CHECK_GL_ERROR(); |
260 | break; |
261 | case GL_TEXTURE_2D: |
262 | case GL_TEXTURE_CUBE_MAP: |
263 | glTexSubImage2D(mFaceTarget, mLevel, |
264 | dest.left, dest.top, |
265 | dest.getWidth(), dest.getHeight(), |
266 | GLPixelUtil::getGLOriginFormat(data.getFormat()), GLPixelUtil::getGLOriginDataType(data.getFormat()), |
267 | data.getData()); |
268 | BS_CHECK_GL_ERROR(); |
269 | break; |
270 | case GL_TEXTURE_2D_ARRAY: |
271 | case GL_TEXTURE_3D: |
272 | glTexSubImage3D( |
273 | mTarget, mLevel, |
274 | dest.left, dest.top, dest.front, |
275 | dest.getWidth(), dest.getHeight(), dest.getDepth(), |
276 | GLPixelUtil::getGLOriginFormat(data.getFormat()), GLPixelUtil::getGLOriginDataType(data.getFormat()), |
277 | data.getData()); |
278 | BS_CHECK_GL_ERROR(); |
279 | break; |
280 | } |
281 | } |
282 | |
283 | // Restore defaults |
284 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
285 | BS_CHECK_GL_ERROR(); |
286 | |
287 | glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); |
288 | BS_CHECK_GL_ERROR(); |
289 | |
290 | glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); |
291 | BS_CHECK_GL_ERROR(); |
292 | |
293 | glPixelStorei(GL_UNPACK_ALIGNMENT, 4); |
294 | BS_CHECK_GL_ERROR(); |
295 | |
296 | BS_INC_RENDER_STAT_CAT(ResWrite, RenderStatObject_Texture); |
297 | } |
298 | |
299 | void GLTextureBuffer::download(const PixelData &data) |
300 | { |
301 | if (data.getWidth() != getWidth() || data.getHeight() != getHeight() || data.getDepth() != getDepth()) |
302 | { |
303 | LOGERR("Only download of entire buffer is supported by OpenGL." ); |
304 | return; |
305 | } |
306 | |
307 | glBindTexture(mTarget, mTextureID); |
308 | BS_CHECK_GL_ERROR(); |
309 | |
310 | if(PixelUtil::isCompressed(data.getFormat())) |
311 | { |
312 | // Block-compressed data cannot be smaller than 4x4, and must be a multiple of 4 |
313 | const UINT32 actualWidth = Math::divideAndRoundUp(std::max(mWidth, 4U), 4U) * 4U; |
314 | const UINT32 actualHeight = Math::divideAndRoundUp(std::max(mHeight, 4U), 4U) * 4U; |
315 | |
316 | const UINT32 expectedRowPitch = actualWidth; |
317 | const UINT32 expectedSlicePitch = actualWidth * actualHeight; |
318 | |
319 | const bool isConsecutive = data.getRowPitch() == expectedRowPitch && data.getSlicePitch() == expectedSlicePitch; |
320 | if (data.getFormat() != mFormat || !isConsecutive) |
321 | { |
322 | LOGERR("Compressed images must be consecutive, in the source format" ); |
323 | return; |
324 | } |
325 | |
326 | // Data must be consecutive and at beginning of buffer as PixelStorei not allowed |
327 | // for compressed formate |
328 | glGetCompressedTexImage(mFaceTarget, mLevel, data.getData()); |
329 | BS_CHECK_GL_ERROR(); |
330 | } |
331 | else |
332 | { |
333 | if (data.getWidth() != data.getRowPitch()) |
334 | { |
335 | glPixelStorei(GL_PACK_ROW_LENGTH, data.getRowPitch()); |
336 | BS_CHECK_GL_ERROR(); |
337 | } |
338 | |
339 | if (data.getHeight()*data.getWidth() != data.getSlicePitch()) |
340 | { |
341 | glPixelStorei(GL_PACK_IMAGE_HEIGHT, (data.getSlicePitch() / data.getWidth())); |
342 | BS_CHECK_GL_ERROR(); |
343 | } |
344 | |
345 | if (data.getLeft() > 0 || data.getTop() > 0 || data.getFront() > 0) |
346 | { |
347 | glPixelStorei( |
348 | GL_PACK_SKIP_PIXELS, |
349 | data.getLeft() + data.getRowPitch() * data.getTop() + data.getSlicePitch() * data.getFront()); |
350 | BS_CHECK_GL_ERROR(); |
351 | } |
352 | |
353 | if ((data.getWidth()*PixelUtil::getNumElemBytes(data.getFormat())) & 3) |
354 | { |
355 | glPixelStorei(GL_PACK_ALIGNMENT, 1); |
356 | BS_CHECK_GL_ERROR(); |
357 | } |
358 | |
359 | // We can only get the entire texture |
360 | glGetTexImage(mFaceTarget, mLevel, GLPixelUtil::getGLOriginFormat(data.getFormat()), |
361 | GLPixelUtil::getGLOriginDataType(data.getFormat()), data.getData()); |
362 | BS_CHECK_GL_ERROR(); |
363 | |
364 | // Restore defaults |
365 | glPixelStorei(GL_PACK_ROW_LENGTH, 0); |
366 | BS_CHECK_GL_ERROR(); |
367 | |
368 | glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); |
369 | BS_CHECK_GL_ERROR(); |
370 | |
371 | glPixelStorei(GL_PACK_SKIP_PIXELS, 0); |
372 | BS_CHECK_GL_ERROR(); |
373 | |
374 | glPixelStorei(GL_PACK_ALIGNMENT, 4); |
375 | BS_CHECK_GL_ERROR(); |
376 | } |
377 | |
378 | BS_INC_RENDER_STAT_CAT(ResRead, RenderStatObject_Texture); |
379 | } |
380 | |
381 | void GLTextureBuffer::bindToFramebuffer(GLenum attachment, UINT32 zoffset, bool allLayers) |
382 | { |
383 | if(mTarget == GL_TEXTURE_1D || mTarget == GL_TEXTURE_2D) |
384 | allLayers = true; |
385 | |
386 | if(allLayers) |
387 | { |
388 | switch (mTarget) |
389 | { |
390 | case GL_TEXTURE_1D: |
391 | glFramebufferTexture1D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, mLevel); |
392 | BS_CHECK_GL_ERROR(); |
393 | break; |
394 | case GL_TEXTURE_2D: |
395 | glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, mLevel); |
396 | BS_CHECK_GL_ERROR(); |
397 | break; |
398 | case GL_TEXTURE_2D_MULTISAMPLE: |
399 | glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, 0); |
400 | BS_CHECK_GL_ERROR(); |
401 | break; |
402 | case GL_TEXTURE_CUBE_MAP: |
403 | case GL_TEXTURE_3D: |
404 | default: // Texture arrays (binding all layers) |
405 | glFramebufferTexture(GL_FRAMEBUFFER, attachment, mTextureID, mLevel); |
406 | BS_CHECK_GL_ERROR(); |
407 | break; |
408 | } |
409 | } |
410 | else |
411 | { |
412 | switch (mTarget) |
413 | { |
414 | case GL_TEXTURE_3D: |
415 | glFramebufferTexture3D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, mLevel, zoffset); |
416 | BS_CHECK_GL_ERROR(); |
417 | break; |
418 | case GL_TEXTURE_CUBE_MAP: |
419 | glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, mFaceTarget, mTextureID, mLevel); |
420 | BS_CHECK_GL_ERROR(); |
421 | break; |
422 | default: // Texture arrays |
423 | glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, mTextureID, mLevel, mFace); |
424 | BS_CHECK_GL_ERROR(); |
425 | break; |
426 | } |
427 | } |
428 | } |
429 | |
430 | void GLTextureBuffer::copyFromFramebuffer(UINT32 zoffset) |
431 | { |
432 | glBindTexture(mTarget, mTextureID); |
433 | BS_CHECK_GL_ERROR(); |
434 | |
435 | switch(mTarget) |
436 | { |
437 | case GL_TEXTURE_1D: |
438 | glCopyTexSubImage1D(mFaceTarget, mLevel, 0, 0, 0, mWidth); |
439 | BS_CHECK_GL_ERROR(); |
440 | break; |
441 | case GL_TEXTURE_2D: |
442 | case GL_TEXTURE_CUBE_MAP: |
443 | glCopyTexSubImage2D(mFaceTarget, mLevel, 0, 0, 0, 0, mWidth, mHeight); |
444 | BS_CHECK_GL_ERROR(); |
445 | break; |
446 | case GL_TEXTURE_3D: |
447 | glCopyTexSubImage3D(mFaceTarget, mLevel, 0, 0, zoffset, 0, 0, mWidth, mHeight); |
448 | BS_CHECK_GL_ERROR(); |
449 | break; |
450 | } |
451 | } |
452 | |
453 | void GLTextureBuffer::blitFromTexture(GLTextureBuffer* src) |
454 | { |
455 | GLPixelBuffer::blitFromTexture(src); |
456 | } |
457 | |
458 | void GLTextureBuffer::blitFromTexture(GLTextureBuffer* src, const PixelVolume& srcBox, const PixelVolume& dstBox) |
459 | { |
460 | // If supported, prefer direct image copy. If not supported, or if sample counts don't match, fall back to FB blit |
461 | #if BS_OPENGL_4_3 || BS_OPENGLES_3_2 |
462 | if (src->mMultisampleCount > 1 && mMultisampleCount <= 1) // Resolving MS texture |
463 | #endif |
464 | { |
465 | #if BS_OPENGL_4_3 || BS_OPENGLES_3_2 |
466 | if ( !(mTarget == GL_TEXTURE_2D || mTarget == GL_TEXTURE_2D_MULTISAMPLE) ) |
467 | BS_EXCEPT(InvalidParametersException, "Non-2D multisampled texture not supported." ); |
468 | #endif |
469 | |
470 | GLint currentFBO = 0; |
471 | glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tFBO); |
472 | BS_CHECK_GL_ERROR(); |
473 | |
474 | GLuint readFBO = GLRTTManager::instance().getBlitReadFBO(); |
475 | GLuint drawFBO = GLRTTManager::instance().getBlitDrawFBO(); |
476 | |
477 | // Attach source texture |
478 | glBindFramebuffer(GL_FRAMEBUFFER, readFBO); |
479 | BS_CHECK_GL_ERROR(); |
480 | |
481 | src->bindToFramebuffer(GL_COLOR_ATTACHMENT0, 0, false); |
482 | |
483 | // Attach destination texture |
484 | glBindFramebuffer(GL_FRAMEBUFFER, drawFBO); |
485 | BS_CHECK_GL_ERROR(); |
486 | |
487 | bindToFramebuffer(GL_COLOR_ATTACHMENT0, 0, false); |
488 | |
489 | // Perform blit |
490 | glBindFramebuffer(GL_READ_FRAMEBUFFER, readFBO); |
491 | BS_CHECK_GL_ERROR(); |
492 | |
493 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, drawFBO); |
494 | BS_CHECK_GL_ERROR(); |
495 | |
496 | glReadBuffer(GL_COLOR_ATTACHMENT0); |
497 | BS_CHECK_GL_ERROR(); |
498 | |
499 | glDrawBuffer(GL_COLOR_ATTACHMENT0); |
500 | BS_CHECK_GL_ERROR(); |
501 | |
502 | glBlitFramebuffer(srcBox.left, srcBox.top, srcBox.right, srcBox.bottom, |
503 | dstBox.left, dstBox.top, dstBox.right, dstBox.bottom, GL_COLOR_BUFFER_BIT, GL_NEAREST); |
504 | BS_CHECK_GL_ERROR(); |
505 | |
506 | // Restore the previously bound FBO |
507 | glBindFramebuffer(GL_FRAMEBUFFER, currentFBO); |
508 | BS_CHECK_GL_ERROR(); |
509 | } |
510 | #if BS_OPENGL_4_3 || BS_OPENGLES_3_2 |
511 | else // Just plain copy |
512 | { |
513 | if (mMultisampleCount != src->mMultisampleCount) |
514 | BS_EXCEPT(InvalidParametersException, "When copying textures their multisample counts must match." ); |
515 | |
516 | if (mTarget == GL_TEXTURE_3D) // 3D textures can't have arrays so their Z coordinate is handled differently |
517 | { |
518 | glCopyImageSubData(src->mTextureID, src->mTarget, src->mLevel, srcBox.left, srcBox.top, srcBox.front, |
519 | mTextureID, mTarget, mLevel, dstBox.left, dstBox.top, dstBox.front, srcBox.getWidth(), srcBox.getHeight(), srcBox.getDepth()); |
520 | BS_CHECK_GL_ERROR(); |
521 | } |
522 | else |
523 | { |
524 | glCopyImageSubData(src->mTextureID, src->mTarget, src->mLevel, srcBox.left, srcBox.top, src->mFace, |
525 | mTextureID, mTarget, mLevel, dstBox.left, dstBox.top, mFace, srcBox.getWidth(), srcBox.getHeight(), 1); |
526 | BS_CHECK_GL_ERROR(); |
527 | } |
528 | } |
529 | #endif |
530 | } |
531 | }} |
532 | |