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 "Image/BsTexture.h"
4#include "Private/RTTI/BsTextureRTTI.h"
5#include "FileSystem/BsDataStream.h"
6#include "Error/BsException.h"
7#include "Debug/BsDebug.h"
8#include "CoreThread/BsCoreThread.h"
9#include "Threading/BsAsyncOp.h"
10#include "Resources/BsResources.h"
11#include "Image/BsPixelUtil.h"
12
13namespace bs
14{
15 TEXTURE_COPY_DESC TEXTURE_COPY_DESC::DEFAULT = TEXTURE_COPY_DESC();
16
17 TextureProperties::TextureProperties(const TEXTURE_DESC& desc)
18 :mDesc(desc)
19 {
20
21 }
22
23 bool TextureProperties::hasAlpha() const
24 {
25 return PixelUtil::hasAlpha(mDesc.format);
26 }
27
28 UINT32 TextureProperties::getNumFaces() const
29 {
30 UINT32 facesPerSlice = getTextureType() == TEX_TYPE_CUBE_MAP ? 6 : 1;
31
32 return facesPerSlice * mDesc.numArraySlices;
33 }
34
35 void TextureProperties::mapFromSubresourceIdx(UINT32 subresourceIdx, UINT32& face, UINT32& mip) const
36 {
37 UINT32 numMipmaps = getNumMipmaps() + 1;
38
39 face = Math::floorToInt((subresourceIdx) / (float)numMipmaps);
40 mip = subresourceIdx % numMipmaps;
41 }
42
43 UINT32 TextureProperties::mapToSubresourceIdx(UINT32 face, UINT32 mip) const
44 {
45 return face * (getNumMipmaps() + 1) + mip;
46 }
47
48 SPtr<PixelData> TextureProperties::allocBuffer(UINT32 face, UINT32 mipLevel) const
49 {
50 UINT32 width = getWidth();
51 UINT32 height = getHeight();
52 UINT32 depth = getDepth();
53
54 for (UINT32 j = 0; j < mipLevel; j++)
55 {
56 if (width != 1) width /= 2;
57 if (height != 1) height /= 2;
58 if (depth != 1) depth /= 2;
59 }
60
61 SPtr<PixelData> dst = bs_shared_ptr_new<PixelData>(width, height, depth, getFormat());
62 dst->allocateInternalBuffer();
63
64 return dst;
65 }
66
67 Texture::Texture(const TEXTURE_DESC& desc)
68 :mProperties(desc)
69 {
70
71 }
72
73 Texture::Texture(const TEXTURE_DESC& desc, const SPtr<PixelData>& pixelData)
74 : mProperties(desc), mInitData(pixelData)
75 {
76 if (mInitData != nullptr)
77 mInitData->_lock();
78 }
79
80 void Texture::initialize()
81 {
82 mSize = calculateSize();
83
84 // Allocate CPU buffers if needed
85 if ((mProperties.getUsage() & TU_CPUCACHED) != 0)
86 {
87 createCPUBuffers();
88
89 if (mInitData != nullptr)
90 updateCPUBuffers(0, *mInitData);
91 }
92
93 Resource::initialize();
94 }
95
96 SPtr<ct::CoreObject> Texture::createCore() const
97 {
98 const TextureProperties& props = getProperties();
99
100 SPtr<ct::CoreObject> coreObj = ct::TextureManager::instance().createTextureInternal(props.mDesc, mInitData);
101
102 if ((mProperties.getUsage() & TU_CPUCACHED) == 0)
103 mInitData = nullptr;
104
105 return coreObj;
106 }
107
108 AsyncOp Texture::writeData(const SPtr<PixelData>& data, UINT32 face, UINT32 mipLevel, bool discardEntireBuffer)
109 {
110 UINT32 subresourceIdx = mProperties.mapToSubresourceIdx(face, mipLevel);
111 updateCPUBuffers(subresourceIdx, *data);
112
113 data->_lock();
114
115 std::function<void(const SPtr<ct::Texture>&, UINT32, UINT32, const SPtr<PixelData>&, bool, AsyncOp&)> func =
116 [&](const SPtr<ct::Texture>& texture, UINT32 _face, UINT32 _mipLevel, const SPtr<PixelData>& _pixData,
117 bool _discardEntireBuffer, AsyncOp& asyncOp)
118 {
119 texture->writeData(*_pixData, _mipLevel, _face, _discardEntireBuffer);
120 _pixData->_unlock();
121 asyncOp._completeOperation();
122
123 };
124
125 return gCoreThread().queueReturnCommand(std::bind(func, getCore(), face, mipLevel,
126 data, discardEntireBuffer, std::placeholders::_1));
127 }
128
129 AsyncOp Texture::readData(const SPtr<PixelData>& data, UINT32 face, UINT32 mipLevel)
130 {
131 data->_lock();
132
133 std::function<void(const SPtr<ct::Texture>&, UINT32, UINT32, const SPtr<PixelData>&, AsyncOp&)> func =
134 [&](const SPtr<ct::Texture>& texture, UINT32 _face, UINT32 _mipLevel, const SPtr<PixelData>& _pixData,
135 AsyncOp& asyncOp)
136 {
137 // Make sure any queued command start executing before reading
138 ct::RenderAPI::instance().submitCommandBuffer(nullptr);
139
140 texture->readData(*_pixData, _mipLevel, _face);
141 _pixData->_unlock();
142 asyncOp._completeOperation();
143
144 };
145
146 return gCoreThread().queueReturnCommand(std::bind(func, getCore(), face, mipLevel,
147 data, std::placeholders::_1));
148 }
149
150 TAsyncOp<SPtr<PixelData>> Texture::readData(UINT32 face, UINT32 mipLevel)
151 {
152 TAsyncOp<SPtr<PixelData>> op;
153
154 auto func = [texture = getCore(), face, mipLevel, op]() mutable
155 {
156 // Make sure any queued command start executing before reading
157 ct::RenderAPI::instance().submitCommandBuffer(nullptr);
158
159 SPtr<PixelData> output = texture->getProperties().allocBuffer(face, mipLevel);
160 texture->readData(*output, mipLevel, face);
161
162 op._completeOperation(output);
163
164 };
165
166 gCoreThread().queueCommand(func);
167 return op;
168 }
169
170 UINT32 Texture::calculateSize() const
171 {
172 return mProperties.getNumFaces() * PixelUtil::getMemorySize(mProperties.getWidth(),
173 mProperties.getHeight(), mProperties.getDepth(), mProperties.getFormat());
174 }
175
176 void Texture::updateCPUBuffers(UINT32 subresourceIdx, const PixelData& pixelData)
177 {
178 if ((mProperties.getUsage() & TU_CPUCACHED) == 0)
179 return;
180
181 if (subresourceIdx >= (UINT32)mCPUSubresourceData.size())
182 {
183 LOGERR("Invalid subresource index: " + toString(subresourceIdx) + ". Supported range: 0 .. " + toString((UINT32)mCPUSubresourceData.size()));
184 return;
185 }
186
187 UINT32 mipLevel;
188 UINT32 face;
189 mProperties.mapFromSubresourceIdx(subresourceIdx, face, mipLevel);
190
191 UINT32 mipWidth, mipHeight, mipDepth;
192 PixelUtil::getSizeForMipLevel(mProperties.getWidth(), mProperties.getHeight(), mProperties.getDepth(),
193 mipLevel, mipWidth, mipHeight, mipDepth);
194
195 if (pixelData.getWidth() != mipWidth || pixelData.getHeight() != mipHeight ||
196 pixelData.getDepth() != mipDepth || pixelData.getFormat() != mProperties.getFormat())
197 {
198 LOGERR("Provided buffer is not of valid dimensions or format in order to update this texture.");
199 return;
200 }
201
202 if (mCPUSubresourceData[subresourceIdx]->getSize() != pixelData.getSize())
203 BS_EXCEPT(InternalErrorException, "Buffer sizes don't match.");
204
205 UINT8* dest = mCPUSubresourceData[subresourceIdx]->getData();
206 UINT8* src = pixelData.getData();
207
208 memcpy(dest, src, pixelData.getSize());
209 }
210
211 void Texture::readCachedData(PixelData& dest, UINT32 face, UINT32 mipLevel)
212 {
213 if ((mProperties.getUsage() & TU_CPUCACHED) == 0)
214 {
215 LOGERR("Attempting to read CPU data from a texture that is created without CPU caching.");
216 return;
217 }
218
219 UINT32 mipWidth, mipHeight, mipDepth;
220 PixelUtil::getSizeForMipLevel(mProperties.getWidth(), mProperties.getHeight(), mProperties.getDepth(),
221 mipLevel, mipWidth, mipHeight, mipDepth);
222
223 if (dest.getWidth() != mipWidth || dest.getHeight() != mipHeight ||
224 dest.getDepth() != mipDepth || dest.getFormat() != mProperties.getFormat())
225 {
226 LOGERR("Provided buffer is not of valid dimensions or format in order to read from this texture.");
227 return;
228 }
229
230 UINT32 subresourceIdx = mProperties.mapToSubresourceIdx(face, mipLevel);
231 if (subresourceIdx >= (UINT32)mCPUSubresourceData.size())
232 {
233 LOGERR("Invalid subresource index: " + toString(subresourceIdx) + ". Supported range: 0 .. " + toString((UINT32)mCPUSubresourceData.size()));
234 return;
235 }
236
237 if (mCPUSubresourceData[subresourceIdx]->getSize() != dest.getSize())
238 BS_EXCEPT(InternalErrorException, "Buffer sizes don't match.");
239
240 UINT8* srcPtr = mCPUSubresourceData[subresourceIdx]->getData();
241 UINT8* destPtr = dest.getData();
242
243 memcpy(destPtr, srcPtr, dest.getSize());
244 }
245
246 void Texture::createCPUBuffers()
247 {
248 UINT32 numFaces = mProperties.getNumFaces();
249 UINT32 numMips = mProperties.getNumMipmaps() + 1;
250
251 UINT32 numSubresources = numFaces * numMips;
252 mCPUSubresourceData.resize(numSubresources);
253
254 for (UINT32 i = 0; i < numFaces; i++)
255 {
256 UINT32 curWidth = mProperties.getWidth();
257 UINT32 curHeight = mProperties.getHeight();
258 UINT32 curDepth = mProperties.getDepth();
259
260 for (UINT32 j = 0; j < numMips; j++)
261 {
262 UINT32 subresourceIdx = mProperties.mapToSubresourceIdx(i, j);
263
264 mCPUSubresourceData[subresourceIdx] = bs_shared_ptr_new<PixelData>(curWidth, curHeight, curDepth, mProperties.getFormat());
265 mCPUSubresourceData[subresourceIdx]->allocateInternalBuffer();
266
267 if (curWidth > 1)
268 curWidth = curWidth / 2;
269
270 if (curHeight > 1)
271 curHeight = curHeight / 2;
272
273 if (curDepth > 1)
274 curDepth = curDepth / 2;
275 }
276 }
277 }
278
279 SPtr<ct::Texture> Texture::getCore() const
280 {
281 return std::static_pointer_cast<ct::Texture>(mCoreSpecific);
282 }
283
284 /************************************************************************/
285 /* SERIALIZATION */
286 /************************************************************************/
287
288 RTTITypeBase* Texture::getRTTIStatic()
289 {
290 return TextureRTTI::instance();
291 }
292
293 RTTITypeBase* Texture::getRTTI() const
294 {
295 return Texture::getRTTIStatic();
296 }
297
298 /************************************************************************/
299 /* STATICS */
300 /************************************************************************/
301 HTexture Texture::create(const TEXTURE_DESC& desc)
302 {
303 SPtr<Texture> texturePtr = _createPtr(desc);
304
305 return static_resource_cast<Texture>(gResources()._createResourceHandle(texturePtr));
306 }
307
308 HTexture Texture::create(const SPtr<PixelData>& pixelData, int usage, bool hwGammaCorrection)
309 {
310 SPtr<Texture> texturePtr = _createPtr(pixelData, usage, hwGammaCorrection);
311
312 return static_resource_cast<Texture>(gResources()._createResourceHandle(texturePtr));
313 }
314
315 SPtr<Texture> Texture::_createPtr(const TEXTURE_DESC& desc)
316 {
317 return TextureManager::instance().createTexture(desc);
318 }
319
320 SPtr<Texture> Texture::_createPtr(const SPtr<PixelData>& pixelData, int usage, bool hwGammaCorrection)
321 {
322 TEXTURE_DESC desc;
323 desc.type = pixelData->getDepth() > 1 ? TEX_TYPE_3D : TEX_TYPE_2D;
324 desc.width = pixelData->getWidth();
325 desc.height = pixelData->getHeight();
326 desc.depth = pixelData->getDepth();
327 desc.format = pixelData->getFormat();
328 desc.usage = usage;
329 desc.hwGamma = hwGammaCorrection;
330
331 return TextureManager::instance().createTexture(desc, pixelData);
332 }
333
334 namespace ct
335 {
336 SPtr<Texture> Texture::WHITE;
337 SPtr<Texture> Texture::BLACK;
338 SPtr<Texture> Texture::NORMAL;
339
340 Texture::Texture(const TEXTURE_DESC& desc, const SPtr<PixelData>& initData, GpuDeviceFlags deviceMask)
341 :mProperties(desc), mInitData(initData)
342 { }
343
344 void Texture::initialize()
345 {
346 if (mInitData != nullptr)
347 {
348 writeData(*mInitData, 0, 0, true);
349 mInitData->_unlock();
350 mInitData = nullptr;
351 }
352
353 CoreObject::initialize();
354 }
355
356 void Texture::writeData(const PixelData& src, UINT32 mipLevel, UINT32 face, bool discardEntireBuffer,
357 UINT32 queueIdx)
358 {
359 THROW_IF_NOT_CORE_THREAD;
360
361 if(discardEntireBuffer)
362 {
363 if((mProperties.getUsage() & TU_DYNAMIC) == 0)
364 {
365 // Buffer discard is enabled but buffer was not created as dynamic. Disabling discard.
366 discardEntireBuffer = false;
367 }
368 }
369
370 writeDataImpl(src, mipLevel, face, discardEntireBuffer, queueIdx);
371 }
372
373 void Texture::readData(PixelData& dest, UINT32 mipLevel, UINT32 face, UINT32 deviceIdx, UINT32 queueIdx)
374 {
375 THROW_IF_NOT_CORE_THREAD;
376
377 PixelData& pixelData = static_cast<PixelData&>(dest);
378
379 UINT32 mipWidth, mipHeight, mipDepth;
380 PixelUtil::getSizeForMipLevel(mProperties.getWidth(), mProperties.getHeight(), mProperties.getDepth(),
381 mipLevel, mipWidth, mipHeight, mipDepth);
382
383 if (pixelData.getWidth() != mipWidth || pixelData.getHeight() != mipHeight ||
384 pixelData.getDepth() != mipDepth || pixelData.getFormat() != mProperties.getFormat())
385 {
386 LOGERR("Provided buffer is not of valid dimensions or format in order to read from this texture.");
387 return;
388 }
389
390 readDataImpl(pixelData, mipLevel, face, deviceIdx, queueIdx);
391 }
392
393 PixelData Texture::lock(GpuLockOptions options, UINT32 mipLevel, UINT32 face, UINT32 deviceIdx, UINT32 queueIdx)
394 {
395 THROW_IF_NOT_CORE_THREAD;
396
397 if (mipLevel > mProperties.getNumMipmaps())
398 {
399 LOGERR("Invalid mip level: " + toString(mipLevel) + ". Min is 0, max is " + toString(mProperties.getNumMipmaps()));
400 return PixelData(0, 0, 0, PF_UNKNOWN);
401 }
402
403 if (face >= mProperties.getNumFaces())
404 {
405 LOGERR("Invalid face index: " + toString(face) + ". Min is 0, max is " + toString(mProperties.getNumFaces()));
406 return PixelData(0, 0, 0, PF_UNKNOWN);
407 }
408
409 return lockImpl(options, mipLevel, face, deviceIdx, queueIdx);
410 }
411
412 void Texture::unlock()
413 {
414 THROW_IF_NOT_CORE_THREAD;
415
416 unlockImpl();
417 }
418
419 void Texture::copy(const SPtr<Texture>& target, const TEXTURE_COPY_DESC& desc, const SPtr<CommandBuffer>& commandBuffer)
420 {
421 THROW_IF_NOT_CORE_THREAD;
422
423 if (target->mProperties.getTextureType() != mProperties.getTextureType())
424 {
425 LOGERR("Source and destination textures must be of same type.");
426 return;
427 }
428
429 if (mProperties.getFormat() != target->mProperties.getFormat()) // Note: It might be okay to use different formats of the same size
430 {
431 LOGERR("Source and destination texture formats must match.");
432 return;
433 }
434
435 if (target->mProperties.getNumSamples() > 1 && mProperties.getNumSamples() != target->mProperties.getNumSamples())
436 {
437 LOGERR("When copying to a multisampled texture, source texture must have the same number of samples.");
438 return;
439 }
440
441 if (desc.srcFace >= mProperties.getNumFaces())
442 {
443 LOGERR("Invalid source face index.");
444 return;
445 }
446
447 if (desc.dstFace >= target->mProperties.getNumFaces())
448 {
449 LOGERR("Invalid destination face index.");
450 return;
451 }
452
453 if (desc.srcMip > mProperties.getNumMipmaps())
454 {
455 LOGERR("Source mip level out of range. Valid range is [0, " + toString(mProperties.getNumMipmaps()) + "].");
456 return;
457 }
458
459 if (desc.dstMip > target->mProperties.getNumMipmaps())
460 {
461 LOGERR("Destination mip level out of range. Valid range is [0, " + toString(target->mProperties.getNumMipmaps()) + "].");
462 return;
463 }
464
465 UINT32 srcWidth, srcHeight, srcDepth;
466 PixelUtil::getSizeForMipLevel(
467 mProperties.getWidth(),
468 mProperties.getHeight(),
469 mProperties.getDepth(),
470 desc.srcMip,
471 srcWidth,
472 srcHeight,
473 srcDepth);
474
475 UINT32 dstWidth, dstHeight, dstDepth;
476 PixelUtil::getSizeForMipLevel(
477 target->mProperties.getWidth(),
478 target->mProperties.getHeight(),
479 target->mProperties.getDepth(),
480 desc.dstMip,
481 dstWidth,
482 dstHeight,
483 dstDepth);
484
485 if(desc.dstPosition.x < 0 || desc.dstPosition.x >= (INT32)dstWidth ||
486 desc.dstPosition.y < 0 || desc.dstPosition.y >= (INT32)dstHeight ||
487 desc.dstPosition.z < 0 || desc.dstPosition.z >= (INT32)dstDepth)
488 {
489 LOGERR("Destination position falls outside the destination texture.");
490 return;
491 }
492
493 bool entireSurface = desc.srcVolume.getWidth() == 0 ||
494 desc.srcVolume.getHeight() == 0 ||
495 desc.srcVolume.getDepth() == 0;
496
497 UINT32 dstRight = (UINT32)desc.dstPosition.x;
498 UINT32 dstBottom = (UINT32)desc.dstPosition.y;
499 UINT32 dstBack = (UINT32)desc.dstPosition.z;
500 if(!entireSurface)
501 {
502 if(desc.srcVolume.left >= srcWidth || desc.srcVolume.right > srcWidth ||
503 desc.srcVolume.top >= srcHeight || desc.srcVolume.bottom > srcHeight ||
504 desc.srcVolume.front >= srcDepth || desc.srcVolume.back > srcDepth)
505 {
506 LOGERR("Source volume falls outside the source texture.");
507 return;
508 }
509
510 dstRight += desc.srcVolume.getWidth();
511 dstBottom += desc.srcVolume.getHeight();
512 dstBack += desc.srcVolume.getDepth();
513 }
514 else
515 {
516 dstRight += srcWidth;
517 dstBottom += srcHeight;
518 dstBack += srcDepth;
519 }
520
521 if(dstRight > dstWidth || dstBottom > dstHeight || dstBack > dstDepth)
522 {
523 LOGERR("Destination volume falls outside the destination texture.");
524 return;
525 }
526
527 copyImpl(target, desc, commandBuffer);
528 }
529
530 void Texture::clear(const Color& value, UINT32 mipLevel, UINT32 face, UINT32 queueIdx)
531 {
532 THROW_IF_NOT_CORE_THREAD;
533
534 if (face >= mProperties.getNumFaces())
535 {
536 LOGERR("Invalid face index.");
537 return;
538 }
539
540 if (mipLevel > mProperties.getNumMipmaps())
541 {
542 LOGERR("Mip level out of range. Valid range is [0, " + toString(mProperties.getNumMipmaps()) + "].");
543 return;
544 }
545
546 clearImpl(value, mipLevel, face, queueIdx);
547 }
548
549 void Texture::clearImpl(const Color& value, UINT32 mipLevel, UINT32 face, UINT32 queueIdx)
550 {
551 SPtr<PixelData> data = mProperties.allocBuffer(face, mipLevel);
552 data->setColors(value);
553
554 writeData(*data, mipLevel, face, true, queueIdx);
555 }
556
557 /************************************************************************/
558 /* TEXTURE VIEW */
559 /************************************************************************/
560
561 SPtr<TextureView> Texture::createView(const TEXTURE_VIEW_DESC& desc)
562 {
563 return bs_shared_ptr<TextureView>(new (bs_alloc<TextureView>()) TextureView(desc));
564 }
565
566 void Texture::clearBufferViews()
567 {
568 mTextureViews.clear();
569 }
570
571 SPtr<TextureView> Texture::requestView(UINT32 mostDetailMip, UINT32 numMips, UINT32 firstArraySlice,
572 UINT32 numArraySlices, GpuViewUsage usage)
573 {
574 THROW_IF_NOT_CORE_THREAD;
575
576 const TextureProperties& texProps = getProperties();
577
578 TEXTURE_VIEW_DESC key;
579 key.mostDetailMip = mostDetailMip;
580 key.numMips = numMips == 0 ? (texProps.getNumMipmaps() + 1) : numMips;
581 key.firstArraySlice = firstArraySlice;
582 key.numArraySlices = numArraySlices == 0 ? texProps.getNumFaces() : numArraySlices;
583 key.usage = usage;
584
585 auto iterFind = mTextureViews.find(key);
586 if (iterFind == mTextureViews.end())
587 {
588 mTextureViews[key] = createView(key);
589
590 iterFind = mTextureViews.find(key);
591 }
592
593 return iterFind->second;
594 }
595
596 /************************************************************************/
597 /* STATICS */
598 /************************************************************************/
599 SPtr<Texture> Texture::create(const TEXTURE_DESC& desc, GpuDeviceFlags deviceMask)
600 {
601 return TextureManager::instance().createTexture(desc, deviceMask);
602 }
603
604 SPtr<Texture> Texture::create(const SPtr<PixelData>& pixelData, int usage, bool hwGammaCorrection,
605 GpuDeviceFlags deviceMask)
606 {
607 TEXTURE_DESC desc;
608 desc.type = pixelData->getDepth() > 1 ? TEX_TYPE_3D : TEX_TYPE_2D;
609 desc.width = pixelData->getWidth();
610 desc.height = pixelData->getHeight();
611 desc.depth = pixelData->getDepth();
612 desc.format = pixelData->getFormat();
613 desc.usage = usage;
614 desc.hwGamma = hwGammaCorrection;
615
616 SPtr<Texture> newTex = TextureManager::instance().createTextureInternal(desc, pixelData, deviceMask);
617 newTex->initialize();
618
619 return newTex;
620 }
621 }
622}
623