Coverage Report

Created: 2025-07-11 06:36

/src/ogre/OgreMain/src/OgreTexture.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
-----------------------------------------------------------------------------
3
This source file is part of OGRE
4
    (Object-oriented Graphics Rendering Engine)
5
For the latest info, see http://www.ogre3d.org/
6
7
Copyright (c) 2000-2014 Torus Knot Software Ltd
8
9
Permission is hereby granted, free of charge, to any person obtaining a copy
10
of this software and associated documentation files (the "Software"), to deal
11
in the Software without restriction, including without limitation the rights
12
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
copies of the Software, and to permit persons to whom the Software is
14
furnished to do so, subject to the following conditions:
15
16
The above copyright notice and this permission notice shall be included in
17
all copies or substantial portions of the Software.
18
19
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
THE SOFTWARE.
26
-----------------------------------------------------------------------------
27
*/
28
#include "OgreStableHeaders.h"
29
#include "OgreHardwarePixelBuffer.h"
30
#include "OgreImage.h"
31
#include "OgreTexture.h"
32
33
namespace Ogre {
34
    static const char* CUBEMAP_SUFFIXES[] = {"_rt", "_lf", "_up", "_dn", "_fr", "_bk"};
35
    static const char* CUBEMAP_SUFFIXES_ALT[] = {"_px", "_nx", "_py", "_ny", "_pz", "_nz"};
36
    //--------------------------------------------------------------------------
37
    Texture::Texture(ResourceManager* creator, const String& name, 
38
        ResourceHandle handle, const String& group, bool isManual, 
39
        ManualResourceLoader* loader)
40
0
        : Resource(creator, name, handle, group, isManual, loader),
41
            // init defaults; can be overridden before load()
42
0
            mHeight(512),
43
0
            mWidth(512),
44
0
            mDepth(1),
45
0
            mNumRequestedMipmaps(0),
46
0
            mNumMipmaps(0),
47
0
            mGamma(1.0f),
48
0
            mFSAA(0),
49
0
            mFormat(PF_UNKNOWN),
50
0
            mUsage(TU_DEFAULT),
51
0
            mSrcFormat(PF_UNKNOWN),
52
0
            mSrcWidth(0),
53
0
            mSrcHeight(0), 
54
0
            mSrcDepth(0),
55
0
            mTreatLuminanceAsAlpha(false),
56
0
            mInternalResourcesCreated(false),
57
0
            mMipmapsHardwareGenerated(false),
58
0
            mHwGamma(false),
59
0
            mTextureType(TEX_TYPE_2D),
60
0
            mDesiredIntegerBitDepth(0),
61
0
            mDesiredFloatBitDepth(0),
62
0
            mDesiredFormat(PF_UNKNOWN)
63
0
    {
64
0
        if (createParamDictionary("Texture"))
65
0
        {
66
            // Define the parameters that have to be present to load
67
            // from a generic source; actually there are none, since when
68
            // predeclaring, you use a texture file which includes all the
69
            // information required.
70
0
        }
71
72
        // Set some defaults for default load path
73
0
        if (TextureManager::getSingletonPtr())
74
0
        {
75
0
            TextureManager& tmgr = TextureManager::getSingleton();
76
0
            setNumMipmaps(tmgr.getDefaultNumMipmaps());
77
0
            setDesiredBitDepths(tmgr.getPreferredIntegerBitDepth(), tmgr.getPreferredFloatBitDepth());
78
0
        }
79
80
        
81
0
    }
82
    //--------------------------------------------------------------------------
83
    void Texture::loadRawData( DataStreamPtr& stream, 
84
        ushort uWidth, ushort uHeight, PixelFormat eFormat)
85
0
    {
86
0
        Image img;
87
0
        img.loadRawData(stream, uWidth, uHeight, 1, eFormat);
88
0
        loadImage(img);
89
0
    }
90
    //--------------------------------------------------------------------------    
91
    void Texture::loadImage( const Image &img )
92
0
    {
93
0
        OgreAssert(img.getSize(), "cannot load empty image");
94
0
        LoadingState old = mLoadingState.load();
95
96
        // Scope lock for actual loading
97
0
        try
98
0
        {
99
0
            OGRE_LOCK_AUTO_MUTEX;
100
0
            _loadImages({&img});
101
0
        }
102
0
        catch (...)
103
0
        {
104
            // Reset loading in-progress flag in case failed for some reason
105
0
            mLoadingState.store(old);
106
            // Re-throw
107
0
            throw;
108
0
        }
109
110
        // Notify manager
111
0
        if(getCreator())
112
0
            getCreator()->_notifyResourceLoaded(this);
113
114
        // No deferred loading events since this method is not called in background
115
0
    }
116
    //--------------------------------------------------------------------------
117
    void Texture::setFormat(PixelFormat pf)
118
0
    {
119
0
        mFormat = pf;
120
0
        mDesiredFormat = pf;
121
0
    }
122
    //--------------------------------------------------------------------------
123
    bool Texture::hasAlpha(void) const
124
0
    {
125
0
        return PixelUtil::hasAlpha(mFormat);
126
0
    }
127
    //--------------------------------------------------------------------------
128
    void Texture::setDesiredIntegerBitDepth(ushort bits)
129
0
    {
130
0
        mDesiredIntegerBitDepth = bits;
131
0
    }
132
    //--------------------------------------------------------------------------
133
    ushort Texture::getDesiredIntegerBitDepth(void) const
134
0
    {
135
0
        return mDesiredIntegerBitDepth;
136
0
    }
137
    //--------------------------------------------------------------------------
138
    void Texture::setDesiredFloatBitDepth(ushort bits)
139
0
    {
140
0
        mDesiredFloatBitDepth = bits;
141
0
    }
142
    //--------------------------------------------------------------------------
143
    ushort Texture::getDesiredFloatBitDepth(void) const
144
0
    {
145
0
        return mDesiredFloatBitDepth;
146
0
    }
147
    //--------------------------------------------------------------------------
148
    void Texture::setDesiredBitDepths(ushort integerBits, ushort floatBits)
149
0
    {
150
0
        mDesiredIntegerBitDepth = integerBits;
151
0
        mDesiredFloatBitDepth = floatBits;
152
0
    }
153
    //--------------------------------------------------------------------------
154
    void Texture::setTreatLuminanceAsAlpha(bool asAlpha)
155
0
    {
156
0
        mTreatLuminanceAsAlpha = asAlpha;
157
0
    }
158
    //--------------------------------------------------------------------------
159
    size_t Texture::calculateSize(void) const
160
0
    {
161
0
        return getNumFaces() * PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
162
0
    }
163
    //--------------------------------------------------------------------------
164
    void Texture::_loadImages( const ConstImagePtrList& images )
165
0
    {
166
0
        OgreAssert(!images.empty(), "Cannot load empty vector of images");
167
168
        // Set desired texture size and properties from images[0]
169
0
        mSrcWidth = mWidth = images[0]->getWidth();
170
0
        mSrcHeight = mHeight = images[0]->getHeight();
171
0
        mSrcDepth = mDepth = images[0]->getDepth();
172
0
        mSrcFormat = images[0]->getFormat();
173
174
0
        if(!mLayerNames.empty() && mTextureType != TEX_TYPE_CUBE_MAP)
175
0
            mDepth = uint32(mLayerNames.size());
176
177
0
        if(mTreatLuminanceAsAlpha && mSrcFormat == PF_L8)
178
0
            mDesiredFormat = PF_A8;
179
180
0
        if (mDesiredFormat != PF_UNKNOWN)
181
0
        {
182
            // If have desired format, use it
183
0
            mFormat = mDesiredFormat;
184
0
        }
185
0
        else
186
0
        {
187
            // Get the format according with desired bit depth
188
0
            mFormat = PixelUtil::getFormatForBitDepths(mSrcFormat, mDesiredIntegerBitDepth, mDesiredFloatBitDepth);
189
0
        }
190
191
        // The custom mipmaps in the image clamp the request
192
0
        uint32 imageMips = images[0]->getNumMipmaps();
193
194
0
        if(imageMips > 0)
195
0
        {
196
0
            mNumMipmaps = mNumRequestedMipmaps = std::min(mNumRequestedMipmaps, imageMips);
197
            // Disable flag for auto mip generation
198
0
            mUsage &= ~TU_AUTOMIPMAP;
199
0
        }
200
201
        // Create the texture
202
0
        createInternalResources();
203
        // Check if we're loading one image with multiple faces
204
        // or a vector of images representing the faces
205
0
        uint32 faces;
206
0
        bool multiImage; // Load from multiple images?
207
0
        if(images.size() > 1)
208
0
        {
209
0
            faces = uint32(images.size());
210
0
            multiImage = true;
211
0
        }
212
0
        else
213
0
        {
214
0
            faces = images[0]->getNumFaces();
215
0
            multiImage = false;
216
0
        }
217
        
218
        // Check whether number of faces in images exceeds number of faces
219
        // in this texture. If so, clamp it.
220
0
        if(faces > getNumFaces())
221
0
            faces = getNumFaces();
222
        
223
0
        if (TextureManager::getSingleton().getVerbose()) {
224
            // Say what we're doing
225
0
            Log::Stream str = LogManager::getSingleton().stream();
226
0
            str << "Texture '" << mName << "': Loading " << faces << " faces"
227
0
                << "(" << PixelUtil::getFormatName(images[0]->getFormat()) << ","
228
0
                << images[0]->getWidth() << "x" << images[0]->getHeight() << "x"
229
0
                << images[0]->getDepth() << ")";
230
0
            if (!(mMipmapsHardwareGenerated && mNumMipmaps == 0))
231
0
            {
232
0
                str << " with " << mNumMipmaps;
233
0
                if(mUsage & TU_AUTOMIPMAP)
234
0
                {
235
0
                    if (mMipmapsHardwareGenerated)
236
0
                        str << " hardware";
237
238
0
                    str << " generated mipmaps";
239
0
                }
240
0
                else
241
0
                {
242
0
                    str << " custom mipmaps";
243
0
                }
244
0
                if(multiImage)
245
0
                    str << " from multiple Images.";
246
0
                else
247
0
                    str << " from Image.";
248
0
            }
249
250
            // Print data about first destination surface
251
0
            const auto& buf = getBuffer(0, 0);
252
0
            str << " Internal format is " << PixelUtil::getFormatName(buf->getFormat()) << ","
253
0
                << buf->getWidth() << "x" << buf->getHeight() << "x" << buf->getDepth() << ".";
254
0
        }
255
        
256
        // Main loading loop
257
        // imageMips == 0 if the image has no custom mipmaps, otherwise contains the number of custom mips
258
0
        for(uint32 mip = 0; mip <= std::min(mNumMipmaps, imageMips); ++mip)
259
0
        {
260
0
            for(uint32 i = 0; i < std::max(faces, uint32(images.size())); ++i)
261
0
            {
262
0
                PixelBox src;
263
0
                size_t face = (mDepth == 1) ? i : 0; // depth = 1, then cubemap face else 3d/ array layer
264
265
0
                auto buffer = getBuffer(face, mip);
266
0
                Box dst(0, 0, 0, buffer->getWidth(), buffer->getHeight(), buffer->getDepth());
267
268
0
                if(multiImage)
269
0
                {
270
                    // Load from multiple images
271
0
                    src = images[i]->getPixelBox(0, mip);
272
                    // set dst layer
273
0
                    if(mDepth > 1)
274
0
                    {
275
0
                        dst.front = i;
276
0
                        dst.back = i + 1;
277
0
                    }
278
0
                }
279
0
                else
280
0
                {
281
                    // Load from faces of images[0]
282
0
                    src = images[0]->getPixelBox(i, mip);
283
0
                }
284
285
0
                if(mGamma != 1.0f) {
286
                    // Apply gamma correction
287
                    // Do not overwrite original image but do gamma correction in temporary buffer
288
0
                    Image tmp(src.format, src.getWidth(), getHeight(), src.getDepth());
289
0
                    PixelBox corrected = tmp.getPixelBox();
290
0
                    PixelUtil::bulkPixelConversion(src, corrected);
291
292
0
                    Image::applyGamma(corrected.data, mGamma, tmp.getSize(), tmp.getBPP());
293
294
                    // Destination: entire texture. blitFromMemory does the scaling to
295
                    // a power of two for us when needed
296
0
                    buffer->blitFromMemory(corrected, dst);
297
0
                }
298
0
                else 
299
0
                {
300
                    // Destination: entire texture. blitFromMemory does the scaling to
301
                    // a power of two for us when needed
302
0
                    buffer->blitFromMemory(src, dst);
303
0
                }
304
                
305
0
            }
306
0
        }
307
        // Update size (the final size, not including temp space)
308
0
        mSize = getNumFaces() * PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
309
310
0
    }
311
    //-----------------------------------------------------------------------------
312
0
    uint32 Texture::getMaxMipmaps() const {
313
        // see ARB_texture_non_power_of_two
314
0
        return Bitwise::mostSignificantBitSet(std::max(mWidth, std::max(mHeight, mDepth)));
315
0
    }
316
    void Texture::createInternalResources(void)
317
0
    {
318
0
        if (!mInternalResourcesCreated)
319
0
        {
320
            // Check requested number of mipmaps
321
0
            mNumMipmaps = std::min(mNumMipmaps, getMaxMipmaps());
322
323
0
            createInternalResourcesImpl();
324
0
            mInternalResourcesCreated = true;
325
326
            // this is also public API, so update state accordingly
327
0
            if(!isLoading())
328
0
            {
329
0
                if(mIsManual && mLoader)
330
0
                    mLoader->loadResource(this);
331
332
0
                mLoadingState.store(LOADSTATE_LOADED);
333
0
                _fireLoadingComplete();
334
0
            }
335
0
        }
336
0
    }
337
    //-----------------------------------------------------------------------------
338
    void Texture::freeInternalResources(void)
339
0
    {
340
0
        if (mInternalResourcesCreated)
341
0
        {
342
0
            mSurfaceList.clear();
343
0
            freeInternalResourcesImpl();
344
0
            mInternalResourcesCreated = false;
345
346
            // this is also public API, so update state accordingly
347
0
            if(mLoadingState.load() != LOADSTATE_UNLOADING)
348
0
            {
349
0
                mLoadingState.store(LOADSTATE_UNLOADED);
350
0
                _fireUnloadingComplete();
351
0
            }
352
0
        }
353
0
    }
354
355
    void Texture::createSurfaceList(void)
356
0
    {
357
0
        mSurfaceList.clear();
358
359
0
        uint32 depth = mDepth;
360
0
        for (uint8 face = 0; face < getNumFaces(); face++)
361
0
        {
362
0
            uint32 width = mWidth;
363
0
            uint32 height = mHeight;
364
365
0
            for (uint32 mip = 0; mip <= getNumMipmaps(); mip++)
366
0
            {
367
0
                auto buf = createSurface(face, mip, width, height, depth);
368
0
                mSurfaceList.push_back(buf);
369
370
0
                if (width > 1)
371
0
                    width = width / 2;
372
0
                if (height > 1)
373
0
                    height = height / 2;
374
0
                if (depth > 1 && mTextureType != TEX_TYPE_2D_ARRAY)
375
0
                    depth = depth / 2;
376
0
            }
377
0
        }
378
0
    }
379
380
    //-----------------------------------------------------------------------------
381
    void Texture::unloadImpl(void)
382
0
    {
383
0
        freeInternalResources();
384
0
    }
385
    //-----------------------------------------------------------------------------   
386
    void Texture::copyToTexture( TexturePtr& target )
387
0
    {
388
0
        OgreAssert(target->getNumFaces() == getNumFaces(), "Texture types must match");
389
0
        size_t numMips = std::min(getNumMipmaps(), target->getNumMipmaps());
390
0
        if((mUsage & TU_AUTOMIPMAP) || (target->getUsage()&TU_AUTOMIPMAP))
391
0
            numMips = 0;
392
0
        for(unsigned int face=0; face<getNumFaces(); face++)
393
0
        {
394
0
            for(unsigned int mip=0; mip<=numMips; mip++)
395
0
            {
396
0
                target->getBuffer(face, mip)->blit(getBuffer(face, mip));
397
0
            }
398
0
        }
399
0
    }
400
401
    RenderTarget* Texture::getRenderTarget(size_t slice, size_t mipmap)
402
0
    {
403
0
        if(mTextureType == TEX_TYPE_CUBE_MAP)
404
0
            return getBuffer(slice, mipmap)->getRenderTarget();
405
406
0
        return getBuffer(0, mipmap)->getRenderTarget(slice);
407
0
    }
408
409
    const HardwarePixelBufferSharedPtr& Texture::getBuffer(size_t face, size_t mipmap)
410
0
    {
411
0
        OgreAssert(face < getNumFaces(), "out of range");
412
0
        OgreAssert(mipmap <= mNumMipmaps, "out of range");
413
414
0
        size_t idx = face * (mNumMipmaps + 1) + mipmap;
415
0
        assert(idx < mSurfaceList.size());
416
0
        return mSurfaceList[idx];
417
0
    }
418
419
    //---------------------------------------------------------------------
420
    void Texture::convertToImage(Image& destImage, bool includeMipMaps)
421
0
    {
422
0
        uint32 numMips = includeMipMaps? getNumMipmaps() : 0;
423
0
        destImage.create(getFormat(), getWidth(), getHeight(), getDepth(), getNumFaces(), numMips);
424
425
0
        for (uint32 face = 0; face < getNumFaces(); ++face)
426
0
        {
427
0
            for (uint32 mip = 0; mip <= numMips; ++mip)
428
0
            {
429
0
                getBuffer(face, mip)->blitToMemory(destImage.getPixelBox(face, mip));
430
0
            }
431
0
        }
432
0
    }
433
434
    //--------------------------------------------------------------------------
435
    void Texture::getCustomAttribute(const String&, void*)
436
0
    {
437
0
    }
438
439
    void Texture::readImage(LoadedImages& imgs, const String& name, const String& ext, bool haveNPOT)
440
0
    {
441
0
        DataStreamPtr dstream = ResourceGroupManager::getSingleton().openResource(name, mGroup, this);
442
443
0
        imgs.push_back(Image());
444
0
        Image& img = imgs.back();
445
0
        img.load(dstream, ext);
446
447
0
        if( haveNPOT )
448
0
            return;
449
450
        // Scale to nearest power of 2
451
0
        uint32 w = Bitwise::firstPO2From(img.getWidth());
452
0
        uint32 h = Bitwise::firstPO2From(img.getHeight());
453
0
        if((img.getWidth() != w) || (img.getHeight() != h))
454
0
            img.resize(w, h);
455
0
    }
456
457
    void Texture::prepareImpl(void)
458
0
    {
459
0
        if (mUsage & TU_RENDERTARGET)
460
0
            return;
461
462
0
        const RenderSystemCapabilities* renderCaps =
463
0
            Root::getSingleton().getRenderSystem()->getCapabilities();
464
465
0
        bool haveNPOT = renderCaps->hasCapability(RSC_NON_POWER_OF_2_TEXTURES) ||
466
0
                        (renderCaps->getNonPOW2TexturesLimited() && mNumMipmaps == 0);
467
468
0
        String baseName, ext;
469
0
        StringUtil::splitBaseFilename(mName, baseName, ext);
470
471
0
        LoadedImages loadedImages;
472
473
0
        try
474
0
        {
475
0
            if(mLayerNames.empty())
476
0
            {
477
0
                readImage(loadedImages, mName, ext, haveNPOT);
478
479
                // If this is a volumetric texture set the texture type flag accordingly.
480
                // If this is a cube map, set the texture type flag accordingly.
481
0
                if (loadedImages[0].hasFlag(IF_CUBEMAP))
482
0
                    mTextureType = TEX_TYPE_CUBE_MAP;
483
                // If this is a volumetric texture set the texture type flag accordingly.
484
0
                if (loadedImages[0].getDepth() > 1 && mTextureType != TEX_TYPE_2D_ARRAY)
485
0
                    mTextureType = TEX_TYPE_3D;
486
0
            }
487
0
        }
488
0
        catch(const FileNotFoundException&)
489
0
        {
490
0
            if(mTextureType == TEX_TYPE_CUBE_MAP)
491
0
            {
492
0
                mLayerNames.resize(6);
493
0
                for (size_t i = 0; i < 6; i++)
494
0
                    mLayerNames[i] = StringUtil::format("%s%s.%s", baseName.c_str(), CUBEMAP_SUFFIXES[i], ext.c_str());
495
496
0
                if(!ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(mLayerNames[0]))
497
0
                {
498
                    // assume alternative naming convention
499
0
                    for (size_t i = 0; i < 6; i++)
500
0
                        mLayerNames[i] =
501
0
                            StringUtil::format("%s%s.%s", baseName.c_str(), CUBEMAP_SUFFIXES_ALT[i], ext.c_str());
502
0
                }
503
0
            }
504
0
            else if (mTextureType == TEX_TYPE_2D_ARRAY)
505
0
            { // ignore
506
0
            }
507
0
            else
508
0
                throw; // rethrow
509
0
        }
510
511
        // read sub-images
512
0
        for(const String& name : mLayerNames)
513
0
        {
514
0
            StringUtil::splitBaseFilename(name, baseName, ext);
515
0
            readImage(loadedImages, name, ext, haveNPOT);
516
0
        }
517
518
        // If compressed and 0 custom mipmap, disable auto mip generation and
519
        // disable software mipmap creation.
520
        // Not supported by GLES.
521
0
        if (PixelUtil::isCompressed(loadedImages[0].getFormat()) &&
522
0
            !renderCaps->hasCapability(RSC_AUTOMIPMAP_COMPRESSED) && loadedImages[0].getNumMipmaps() == 0)
523
0
        {
524
0
            mNumMipmaps = mNumRequestedMipmaps = 0;
525
            // Disable flag for auto mip generation
526
0
            mUsage &= ~TU_AUTOMIPMAP;
527
0
        }
528
529
        // avoid copying Image data
530
0
        std::swap(mLoadedImages, loadedImages);
531
0
    }
532
533
    void Texture::unprepareImpl()
534
0
    {
535
0
        mLoadedImages.clear();
536
0
    }
537
538
    void Texture::loadImpl()
539
0
    {
540
0
        if (mUsage & TU_RENDERTARGET)
541
0
        {
542
0
            createInternalResources();
543
0
            return;
544
0
        }
545
546
0
        LoadedImages loadedImages;
547
        // Now the only copy is on the stack and will be cleaned in case of
548
        // exceptions being thrown from _loadImages
549
0
        std::swap(loadedImages, mLoadedImages);
550
551
        // Call internal _loadImages, not loadImage since that's external and
552
        // will determine load status etc again
553
0
        ConstImagePtrList imagePtrs;
554
555
0
        for (auto& img : loadedImages)
556
0
        {
557
0
            imagePtrs.push_back(&img);
558
0
        }
559
560
0
        _loadImages(imagePtrs);
561
0
    }
562
}