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 "BsGLTexture.h" |
4 | #include "BsGLSupport.h" |
5 | #include "BsGLPixelFormat.h" |
6 | #include "BsGLPixelBuffer.h" |
7 | #include "Error/BsException.h" |
8 | #include "Utility/BsBitwise.h" |
9 | #include "CoreThread/BsCoreThread.h" |
10 | #include "Managers/BsTextureManager.h" |
11 | #include "BsGLRenderTexture.h" |
12 | #include "BsGLTextureView.h" |
13 | #include "Profiling/BsRenderStats.h" |
14 | #include "BsGLCommandBuffer.h" |
15 | |
16 | namespace bs { namespace ct |
17 | { |
18 | GLTexture::GLTexture(GLSupport& support, const TEXTURE_DESC& desc, const SPtr<PixelData>& initialData, |
19 | GpuDeviceFlags deviceMask) |
20 | : Texture(desc, initialData, deviceMask), mGLSupport(support) |
21 | { |
22 | assert((deviceMask == GDF_DEFAULT || deviceMask == GDF_PRIMARY) && "Multiple GPUs not supported natively on OpenGL." ); |
23 | } |
24 | |
25 | GLTexture::~GLTexture() |
26 | { |
27 | mSurfaceList.clear(); |
28 | glDeleteTextures(1, &mTextureID); |
29 | BS_CHECK_GL_ERROR(); |
30 | |
31 | clearBufferViews(); |
32 | |
33 | BS_INC_RENDER_STAT_CAT(ResDestroyed, RenderStatObject_Texture); |
34 | } |
35 | |
36 | void GLTexture::initialize() |
37 | { |
38 | UINT32 width = mProperties.getWidth(); |
39 | UINT32 height = mProperties.getHeight(); |
40 | UINT32 depth = mProperties.getDepth(); |
41 | TextureType texType = mProperties.getTextureType(); |
42 | int usage = mProperties.getUsage(); |
43 | UINT32 numMips = mProperties.getNumMipmaps(); |
44 | UINT32 numFaces = mProperties.getNumFaces(); |
45 | |
46 | PixelFormat pixFormat = mProperties.getFormat(); |
47 | mInternalFormat = GLPixelUtil::getClosestSupportedPF(pixFormat, texType, usage); |
48 | |
49 | if (pixFormat != mInternalFormat) |
50 | { |
51 | LOGWRN(StringUtil::format("Provided pixel format is not supported by the driver: {0}. Falling back on: {1}." , |
52 | pixFormat, mInternalFormat)); |
53 | } |
54 | |
55 | // Check requested number of mipmaps |
56 | UINT32 maxMips = PixelUtil::getMaxMipmaps(width, height, depth, mProperties.getFormat()); |
57 | if (numMips > maxMips) |
58 | { |
59 | LOGERR("Invalid number of mipmaps. Maximum allowed is: " + toString(maxMips)); |
60 | numMips = maxMips; |
61 | } |
62 | |
63 | if ((usage & TU_DEPTHSTENCIL) != 0) |
64 | { |
65 | if (texType != TEX_TYPE_2D && texType != TEX_TYPE_CUBE_MAP) |
66 | { |
67 | LOGERR("Only 2D and cubemap depth stencil textures are supported. Ignoring depth-stencil flag." ); |
68 | usage &= ~TU_DEPTHSTENCIL; |
69 | } |
70 | } |
71 | |
72 | // Include the base mip level |
73 | numMips += 1; |
74 | |
75 | // Generate texture handle |
76 | glGenTextures(1, &mTextureID); |
77 | BS_CHECK_GL_ERROR(); |
78 | |
79 | // Set texture type |
80 | glBindTexture(getGLTextureTarget(), mTextureID); |
81 | BS_CHECK_GL_ERROR(); |
82 | |
83 | if(mProperties.getNumSamples() <= 1) |
84 | { |
85 | // This needs to be set otherwise the texture doesn't get rendered |
86 | glTexParameteri(getGLTextureTarget(), GL_TEXTURE_MAX_LEVEL, numMips - 1); |
87 | BS_CHECK_GL_ERROR(); |
88 | } |
89 | |
90 | // Allocate internal buffer so that glTexSubImageXD can be used |
91 | mGLFormat = GLPixelUtil::getGLInternalFormat(mInternalFormat, mProperties.isHardwareGammaEnabled()); |
92 | |
93 | UINT32 sampleCount = mProperties.getNumSamples(); |
94 | if((usage & (TU_RENDERTARGET | TU_DEPTHSTENCIL)) != 0 && mProperties.getTextureType() == TEX_TYPE_2D && sampleCount > 1) |
95 | { |
96 | if (numFaces <= 1) |
97 | { |
98 | // Create immutable storage if available, fallback to mutable |
99 | #if BS_OPENGL_4_3 || BS_OPENGLES_3_1 |
100 | glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, mGLFormat, width, height, GL_TRUE); |
101 | BS_CHECK_GL_ERROR(); |
102 | #else |
103 | glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, mGLFormat, width, height, GL_TRUE); |
104 | BS_CHECK_GL_ERROR(); |
105 | #endif |
106 | } |
107 | else |
108 | { |
109 | // Create immutable storage if available, fallback to mutable |
110 | #if BS_OPENGL_4_3 || BS_OPENGLES_3_2 |
111 | glTexStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, sampleCount, mGLFormat, width, height, numFaces, GL_TRUE); |
112 | BS_CHECK_GL_ERROR(); |
113 | #else |
114 | glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, sampleCount, mGLFormat, width, height, numFaces, GL_TRUE); |
115 | BS_CHECK_GL_ERROR(); |
116 | #endif |
117 | } |
118 | } |
119 | else |
120 | { |
121 | // Create immutable storage if available, fallback to mutable |
122 | #if BS_OPENGL_4_2 || BS_OPENGLES_3_1 |
123 | switch (texType) |
124 | { |
125 | case TEX_TYPE_1D: |
126 | { |
127 | if (numFaces <= 1) |
128 | { |
129 | glTexStorage1D(GL_TEXTURE_1D, numMips, mGLFormat, width); |
130 | BS_CHECK_GL_ERROR(); |
131 | } |
132 | else |
133 | { |
134 | glTexStorage2D(GL_TEXTURE_1D_ARRAY, numMips, mGLFormat, width, numFaces); |
135 | BS_CHECK_GL_ERROR(); |
136 | } |
137 | } |
138 | break; |
139 | case TEX_TYPE_2D: |
140 | { |
141 | if (numFaces <= 1) |
142 | { |
143 | glTexStorage2D(GL_TEXTURE_2D, numMips, mGLFormat, width, height); |
144 | BS_CHECK_GL_ERROR(); |
145 | } |
146 | else |
147 | { |
148 | glTexStorage3D(GL_TEXTURE_2D_ARRAY, numMips, mGLFormat, width, height, numFaces); |
149 | BS_CHECK_GL_ERROR(); |
150 | } |
151 | } |
152 | break; |
153 | case TEX_TYPE_3D: |
154 | glTexStorage3D(GL_TEXTURE_3D, numMips, mGLFormat, width, height, depth); |
155 | BS_CHECK_GL_ERROR(); |
156 | break; |
157 | case TEX_TYPE_CUBE_MAP: |
158 | { |
159 | if (numFaces <= 6) |
160 | { |
161 | glTexStorage2D(GL_TEXTURE_CUBE_MAP, numMips, mGLFormat, width, height); |
162 | BS_CHECK_GL_ERROR(); |
163 | } |
164 | else |
165 | { |
166 | glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, numMips, mGLFormat, width, height, numFaces); |
167 | BS_CHECK_GL_ERROR(); |
168 | } |
169 | } |
170 | break; |
171 | } |
172 | #else |
173 | if((usage & TU_DEPTHSTENCIL) != 0) |
174 | { |
175 | GLenum depthStencilType = GLPixelUtil::getDepthStencilTypeFromPF(mInternalFormat); |
176 | GLenum depthStencilFormat = GLPixelUtil::getDepthStencilFormatFromPF(mInternalFormat); |
177 | |
178 | if(texType == TEX_TYPE_2D) |
179 | { |
180 | if (numFaces <= 1) |
181 | { |
182 | glTexImage2D(GL_TEXTURE_2D, 0, mGLFormat, width, height, 0, |
183 | depthStencilFormat, depthStencilType, nullptr); |
184 | BS_CHECK_GL_ERROR(); |
185 | } |
186 | else |
187 | { |
188 | glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, mGLFormat, width, height, numFaces, 0, |
189 | depthStencilFormat, depthStencilType, nullptr); |
190 | BS_CHECK_GL_ERROR(); |
191 | } |
192 | } |
193 | else if(texType == TEX_TYPE_CUBE_MAP) |
194 | { |
195 | if (numFaces <= 6) |
196 | { |
197 | for (UINT32 face = 0; face < 6; face++) |
198 | { |
199 | glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, mGLFormat, |
200 | width, height, 0, depthStencilFormat, depthStencilType, nullptr); |
201 | BS_CHECK_GL_ERROR(); |
202 | } |
203 | } |
204 | else |
205 | { |
206 | glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, mGLFormat, |
207 | width, height, numFaces, 0, depthStencilFormat, depthStencilType, nullptr); |
208 | BS_CHECK_GL_ERROR(); |
209 | } |
210 | } |
211 | else |
212 | { |
213 | LOGERR("Unsupported texture type for depth-stencil attachment usage." ); |
214 | } |
215 | } |
216 | else |
217 | { |
218 | GLenum baseFormat = GLPixelUtil::getGLOriginFormat(mInternalFormat); |
219 | GLenum baseDataType = GLPixelUtil::getGLOriginDataType(mInternalFormat); |
220 | |
221 | for (UINT32 mip = 0; mip < numMips; mip++) |
222 | { |
223 | switch (texType) |
224 | { |
225 | case TEX_TYPE_1D: |
226 | { |
227 | if (numFaces <= 1) |
228 | { |
229 | glTexImage1D(GL_TEXTURE_1D, mip, mGLFormat, width, 0, baseFormat, baseDataType, nullptr); |
230 | BS_CHECK_GL_ERROR(); |
231 | } |
232 | else |
233 | { |
234 | glTexImage2D(GL_TEXTURE_1D_ARRAY, mip, mGLFormat, width, numFaces, 0, baseFormat, baseDataType, nullptr); |
235 | BS_CHECK_GL_ERROR(); |
236 | } |
237 | } |
238 | break; |
239 | case TEX_TYPE_2D: |
240 | { |
241 | if (numFaces <= 1) |
242 | { |
243 | glTexImage2D(GL_TEXTURE_2D, mip, mGLFormat, width, height, 0, baseFormat, baseDataType, nullptr); |
244 | BS_CHECK_GL_ERROR(); |
245 | } |
246 | else |
247 | { |
248 | glTexImage3D(GL_TEXTURE_2D_ARRAY, mip, mGLFormat, width, height, numFaces, 0, baseFormat, baseDataType, nullptr); |
249 | BS_CHECK_GL_ERROR(); |
250 | } |
251 | } |
252 | break; |
253 | case TEX_TYPE_3D: |
254 | glTexImage3D(GL_TEXTURE_3D, mip, mGLFormat, width, height, |
255 | depth, 0, baseFormat, baseDataType, nullptr); |
256 | BS_CHECK_GL_ERROR(); |
257 | break; |
258 | case TEX_TYPE_CUBE_MAP: |
259 | { |
260 | if (numFaces <= 6) |
261 | { |
262 | for (UINT32 face = 0; face < 6; face++) |
263 | { |
264 | glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mip, mGLFormat, |
265 | width, height, 0, baseFormat, baseDataType, nullptr); |
266 | BS_CHECK_GL_ERROR(); |
267 | } |
268 | } |
269 | else |
270 | { |
271 | glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mip, mGLFormat, |
272 | width, height, numFaces, 0, baseFormat, baseDataType, nullptr); |
273 | BS_CHECK_GL_ERROR(); |
274 | } |
275 | } |
276 | break; |
277 | } |
278 | |
279 | if(width > 1) |
280 | width = width/2; |
281 | |
282 | if(height > 1) |
283 | height = height/2; |
284 | |
285 | if(depth > 1) |
286 | depth = depth/2; |
287 | } |
288 | } |
289 | #endif |
290 | } |
291 | |
292 | createSurfaceList(); |
293 | |
294 | BS_INC_RENDER_STAT_CAT(ResCreated, RenderStatObject_Texture); |
295 | Texture::initialize(); |
296 | } |
297 | |
298 | GLenum GLTexture::getGLTextureTarget() const |
299 | { |
300 | return getGLTextureTarget(mProperties.getTextureType(), mProperties.getNumSamples(), mProperties.getNumFaces()); |
301 | } |
302 | |
303 | GLuint GLTexture::getGLID() const |
304 | { |
305 | THROW_IF_NOT_CORE_THREAD; |
306 | |
307 | return mTextureID; |
308 | } |
309 | |
310 | GLenum GLTexture::getGLTextureTarget(TextureType type, UINT32 numSamples, UINT32 numFaces) |
311 | { |
312 | switch (type) |
313 | { |
314 | case TEX_TYPE_1D: |
315 | if (numFaces <= 1) |
316 | return GL_TEXTURE_1D; |
317 | else |
318 | return GL_TEXTURE_1D_ARRAY; |
319 | case TEX_TYPE_2D: |
320 | if (numSamples > 1) |
321 | { |
322 | if (numFaces <= 1) |
323 | return GL_TEXTURE_2D_MULTISAMPLE; |
324 | else |
325 | return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; |
326 | } |
327 | else |
328 | { |
329 | if (numFaces <= 1) |
330 | return GL_TEXTURE_2D; |
331 | else |
332 | return GL_TEXTURE_2D_ARRAY; |
333 | } |
334 | case TEX_TYPE_3D: |
335 | return GL_TEXTURE_3D; |
336 | case TEX_TYPE_CUBE_MAP: |
337 | if (numFaces <= 6) |
338 | return GL_TEXTURE_CUBE_MAP; |
339 | else |
340 | return GL_TEXTURE_CUBE_MAP_ARRAY; |
341 | default: |
342 | return 0; |
343 | }; |
344 | } |
345 | |
346 | GLenum GLTexture::getGLTextureTarget(GpuParamObjectType type) |
347 | { |
348 | switch(type) |
349 | { |
350 | case GPOT_TEXTURE1D: |
351 | return GL_TEXTURE_1D; |
352 | case GPOT_TEXTURE2D: |
353 | return GL_TEXTURE_2D; |
354 | case GPOT_TEXTURE2DMS: |
355 | return GL_TEXTURE_2D_MULTISAMPLE; |
356 | case GPOT_TEXTURE3D: |
357 | return GL_TEXTURE_3D; |
358 | case GPOT_TEXTURECUBE: |
359 | return GL_TEXTURE_CUBE_MAP; |
360 | case GPOT_TEXTURE1DARRAY: |
361 | return GL_TEXTURE_1D_ARRAY; |
362 | case GPOT_TEXTURE2DARRAY: |
363 | return GL_TEXTURE_2D_ARRAY; |
364 | case GPOT_TEXTURE2DMSARRAY: |
365 | return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; |
366 | case GPOT_TEXTURECUBEARRAY: |
367 | return GL_TEXTURE_CUBE_MAP_ARRAY; |
368 | default: |
369 | return GL_TEXTURE_2D; |
370 | } |
371 | } |
372 | |
373 | PixelData GLTexture::lockImpl(GpuLockOptions options, UINT32 mipLevel, UINT32 face, UINT32 deviceIdx, |
374 | UINT32 queueIdx) |
375 | { |
376 | if (mProperties.getNumSamples() > 1) |
377 | BS_EXCEPT(InvalidStateException, "Multisampled textures cannot be accessed from the CPU directly." ); |
378 | |
379 | if(mLockedBuffer != nullptr) |
380 | BS_EXCEPT(InternalErrorException, "Trying to lock a buffer that's already locked." ); |
381 | |
382 | UINT32 mipWidth = std::max(1u, mProperties.getWidth() >> mipLevel); |
383 | UINT32 mipHeight = std::max(1u, mProperties.getHeight() >> mipLevel); |
384 | UINT32 mipDepth = std::max(1u, mProperties.getDepth() >> mipLevel); |
385 | |
386 | PixelData lockedArea(mipWidth, mipHeight, mipDepth, mProperties.getFormat()); |
387 | |
388 | mLockedBuffer = getBuffer(face, mipLevel); |
389 | lockedArea.setExternalBuffer((UINT8*)mLockedBuffer->lock(options)); |
390 | |
391 | return lockedArea; |
392 | } |
393 | |
394 | void GLTexture::unlockImpl() |
395 | { |
396 | if (mLockedBuffer == nullptr) |
397 | { |
398 | LOGERR("Trying to unlock a buffer that's not locked." ); |
399 | return; |
400 | } |
401 | |
402 | mLockedBuffer->unlock(); |
403 | mLockedBuffer = nullptr; |
404 | } |
405 | |
406 | void GLTexture::readDataImpl(PixelData& dest, UINT32 mipLevel, UINT32 face, UINT32 deviceIdx, UINT32 queueIdx) |
407 | { |
408 | if (mProperties.getNumSamples() > 1) |
409 | { |
410 | LOGERR("Multisampled textures cannot be accessed from the CPU directly." ); |
411 | return; |
412 | } |
413 | |
414 | if(dest.getFormat() != mInternalFormat) |
415 | { |
416 | PixelData temp(dest.getExtents(), mInternalFormat); |
417 | temp.allocateInternalBuffer(); |
418 | |
419 | getBuffer(face, mipLevel)->download(temp); |
420 | PixelUtil::bulkPixelConversion(temp, dest); |
421 | } |
422 | else |
423 | getBuffer(face, mipLevel)->download(dest); |
424 | } |
425 | |
426 | void GLTexture::writeDataImpl(const PixelData& src, UINT32 mipLevel, UINT32 face, bool discardWholeBuffer, |
427 | UINT32 queueIdx) |
428 | { |
429 | if (mProperties.getNumSamples() > 1) |
430 | { |
431 | LOGERR("Multisampled textures cannot be accessed from the CPU directly." ); |
432 | return; |
433 | } |
434 | |
435 | if (src.getFormat() != mInternalFormat) |
436 | { |
437 | PixelData temp(src.getExtents(), mInternalFormat); |
438 | temp.allocateInternalBuffer(); |
439 | |
440 | PixelUtil::bulkPixelConversion(src, temp); |
441 | getBuffer(face, mipLevel)->upload(temp, temp.getExtents()); |
442 | } |
443 | else |
444 | getBuffer(face, mipLevel)->upload(src, src.getExtents()); |
445 | } |
446 | |
447 | void GLTexture::copyImpl(const SPtr<Texture>& target, const TEXTURE_COPY_DESC& desc, |
448 | const SPtr<CommandBuffer>& commandBuffer) |
449 | { |
450 | auto executeRef = [this](const SPtr<Texture>& target, const TEXTURE_COPY_DESC& desc) |
451 | { |
452 | GLTexture* destTex = static_cast<GLTexture*>(target.get()); |
453 | GLTextureBuffer* dest = static_cast<GLTextureBuffer*>(destTex->getBuffer(desc.dstFace, desc.dstMip).get()); |
454 | GLTextureBuffer* src = static_cast<GLTextureBuffer*>(getBuffer(desc.srcFace, desc.srcMip).get()); |
455 | |
456 | bool copyEntireSurface = desc.srcVolume.getWidth() == 0 || |
457 | desc.srcVolume.getHeight() == 0 || |
458 | desc.srcVolume.getDepth() == 0; |
459 | |
460 | PixelVolume srcVolume = desc.srcVolume; |
461 | |
462 | PixelVolume dstVolume; |
463 | dstVolume.left = (UINT32)desc.dstPosition.x; |
464 | dstVolume.top = (UINT32)desc.dstPosition.y; |
465 | dstVolume.front = (UINT32)desc.dstPosition.z; |
466 | |
467 | if(copyEntireSurface) |
468 | { |
469 | srcVolume.right = srcVolume.left + src->getWidth(); |
470 | srcVolume.bottom = srcVolume.top + src->getHeight(); |
471 | srcVolume.back = srcVolume.front + src->getDepth(); |
472 | |
473 | dstVolume.right = dstVolume.left + src->getWidth(); |
474 | dstVolume.bottom = dstVolume.top + src->getHeight(); |
475 | dstVolume.back = dstVolume.front + src->getDepth(); |
476 | } |
477 | else |
478 | { |
479 | dstVolume.right = dstVolume.left + desc.srcVolume.getWidth(); |
480 | dstVolume.bottom = dstVolume.top + desc.srcVolume.getHeight(); |
481 | dstVolume.back = dstVolume.front + desc.srcVolume.getDepth(); |
482 | } |
483 | |
484 | dest->blitFromTexture(src, srcVolume, dstVolume); |
485 | }; |
486 | |
487 | if (commandBuffer == nullptr) |
488 | executeRef(target, desc); |
489 | else |
490 | { |
491 | auto execute = [=]() { executeRef(target, desc); }; |
492 | |
493 | SPtr<GLCommandBuffer> cb = std::static_pointer_cast<GLCommandBuffer>(commandBuffer); |
494 | cb->queueCommand(execute); |
495 | } |
496 | } |
497 | |
498 | void GLTexture::createSurfaceList() |
499 | { |
500 | mSurfaceList.clear(); |
501 | |
502 | for (UINT32 face = 0; face < mProperties.getNumFaces(); face++) |
503 | { |
504 | for (UINT32 mip = 0; mip <= mProperties.getNumMipmaps(); mip++) |
505 | { |
506 | GLPixelBuffer *buf = bs_new<GLTextureBuffer>(getGLTextureTarget(), mTextureID, face, mip, mInternalFormat, |
507 | static_cast<GpuBufferUsage>(mProperties.getUsage()), |
508 | mProperties.isHardwareGammaEnabled(), |
509 | mProperties.getNumSamples()); |
510 | |
511 | mSurfaceList.push_back(bs_shared_ptr<GLPixelBuffer>(buf)); |
512 | if(buf->getWidth() == 0 || buf->getHeight() == 0 || buf->getDepth() == 0) |
513 | { |
514 | BS_EXCEPT(RenderingAPIException, |
515 | "Zero sized texture surface on texture face " + toString(face) + " mipmap " + toString(mip) |
516 | + ". Probably, the GL driver refused to create the texture." ); |
517 | } |
518 | } |
519 | } |
520 | } |
521 | |
522 | SPtr<GLPixelBuffer> GLTexture::getBuffer(UINT32 face, UINT32 mipmap) |
523 | { |
524 | THROW_IF_NOT_CORE_THREAD; |
525 | |
526 | if(face >= mProperties.getNumFaces()) |
527 | BS_EXCEPT(InvalidParametersException, "Face index out of range" ); |
528 | |
529 | if (mipmap > mProperties.getNumMipmaps()) |
530 | BS_EXCEPT(InvalidParametersException, "Mipmap index out of range" ); |
531 | |
532 | unsigned int idx = face * (mProperties.getNumMipmaps() + 1) + mipmap; |
533 | assert(idx < mSurfaceList.size()); |
534 | return mSurfaceList[idx]; |
535 | } |
536 | |
537 | SPtr<TextureView> GLTexture::createView(const TEXTURE_VIEW_DESC& desc) |
538 | { |
539 | return bs_shared_ptr<GLTextureView>(new (bs_alloc<GLTextureView>()) GLTextureView(this, desc)); |
540 | } |
541 | }} |
542 | |