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
13namespace 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, &currentFBO);
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