1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rightsR
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../../Precompiled.h"
24 
25 #include "../../Core/Context.h"
26 #include "../../Core/Profiler.h"
27 #include "../../Graphics/Graphics.h"
28 #include "../../Graphics/GraphicsEvents.h"
29 #include "../../Graphics/GraphicsImpl.h"
30 #include "../../Graphics/Renderer.h"
31 #include "../../Graphics/Texture2D.h"
32 #include "../../IO/FileSystem.h"
33 #include "../../IO/Log.h"
34 #include "../../Resource/ResourceCache.h"
35 #include "../../Resource/XMLFile.h"
36 
37 #include "../../DebugNew.h"
38 
39 namespace Urho3D
40 {
41 
OnDeviceLost()42 void Texture2D::OnDeviceLost()
43 {
44     GPUObject::OnDeviceLost();
45 
46     if (renderSurface_)
47         renderSurface_->OnDeviceLost();
48 }
49 
OnDeviceReset()50 void Texture2D::OnDeviceReset()
51 {
52     if (!object_.name_ || dataPending_)
53     {
54         // If has a resource file, reload through the resource cache. Otherwise just recreate.
55         ResourceCache* cache = GetSubsystem<ResourceCache>();
56         if (cache->Exists(GetName()))
57             dataLost_ = !cache->ReloadResource(this);
58 
59         if (!object_.name_)
60         {
61             Create();
62             dataLost_ = true;
63         }
64     }
65 
66     dataPending_ = false;
67 }
68 
Release()69 void Texture2D::Release()
70 {
71     if (object_.name_)
72     {
73         if (!graphics_)
74             return;
75 
76         if (!graphics_->IsDeviceLost())
77         {
78             for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
79             {
80                 if (graphics_->GetTexture(i) == this)
81                     graphics_->SetTexture(i, 0);
82             }
83 
84             glDeleteTextures(1, &object_.name_);
85         }
86 
87         if (renderSurface_)
88             renderSurface_->Release();
89 
90         object_.name_ = 0;
91     }
92     else
93     {
94         if (renderSurface_)
95             renderSurface_->Release();
96     }
97 
98     resolveDirty_ = false;
99     levelsDirty_ = false;
100 }
101 
SetData(unsigned level,int x,int y,int width,int height,const void * data)102 bool Texture2D::SetData(unsigned level, int x, int y, int width, int height, const void* data)
103 {
104     URHO3D_PROFILE(SetTextureData);
105 
106     if (!object_.name_ || !graphics_)
107     {
108         URHO3D_LOGERROR("No texture created, can not set data");
109         return false;
110     }
111 
112     if (!data)
113     {
114         URHO3D_LOGERROR("Null source for setting data");
115         return false;
116     }
117 
118     if (level >= levels_)
119     {
120         URHO3D_LOGERROR("Illegal mip level for setting data");
121         return false;
122     }
123 
124     if (graphics_->IsDeviceLost())
125     {
126         URHO3D_LOGWARNING("Texture data assignment while device is lost");
127         dataPending_ = true;
128         return true;
129     }
130 
131     if (IsCompressed())
132     {
133         x &= ~3;
134         y &= ~3;
135     }
136 
137     int levelWidth = GetLevelWidth(level);
138     int levelHeight = GetLevelHeight(level);
139     if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
140     {
141         URHO3D_LOGERROR("Illegal dimensions for setting data");
142         return false;
143     }
144 
145     graphics_->SetTextureForUpdate(this);
146 
147     bool wholeLevel = x == 0 && y == 0 && width == levelWidth && height == levelHeight;
148     unsigned format = GetSRGB() ? GetSRGBFormat(format_) : format_;
149 
150     if (!IsCompressed())
151     {
152         if (wholeLevel)
153             glTexImage2D(target_, level, format, width, height, 0, GetExternalFormat(format_), GetDataType(format_), data);
154         else
155             glTexSubImage2D(target_, level, x, y, width, height, GetExternalFormat(format_), GetDataType(format_), data);
156     }
157     else
158     {
159         if (wholeLevel)
160             glCompressedTexImage2D(target_, level, format, width, height, 0, GetDataSize(width, height), data);
161         else
162             glCompressedTexSubImage2D(target_, level, x, y, width, height, format, GetDataSize(width, height), data);
163     }
164 
165     graphics_->SetTexture(0, 0);
166     return true;
167 }
168 
SetData(Image * image,bool useAlpha)169 bool Texture2D::SetData(Image* image, bool useAlpha)
170 {
171     if (!image)
172     {
173         URHO3D_LOGERROR("Null image, can not set data");
174         return false;
175     }
176 
177     // Use a shared ptr for managing the temporary mip images created during this function
178     SharedPtr<Image> mipImage;
179     unsigned memoryUse = sizeof(Texture2D);
180     int quality = QUALITY_HIGH;
181     Renderer* renderer = GetSubsystem<Renderer>();
182     if (renderer)
183         quality = renderer->GetTextureQuality();
184 
185     if (!image->IsCompressed())
186     {
187         // Convert unsuitable formats to RGBA
188         unsigned components = image->GetComponents();
189         if (Graphics::GetGL3Support() && ((components == 1 && !useAlpha) || components == 2))
190         {
191             mipImage = image->ConvertToRGBA(); image = mipImage;
192             if (!image)
193                 return false;
194             components = image->GetComponents();
195         }
196 
197         unsigned char* levelData = image->GetData();
198         int levelWidth = image->GetWidth();
199         int levelHeight = image->GetHeight();
200         unsigned format = 0;
201 
202         // Discard unnecessary mip levels
203         for (unsigned i = 0; i < mipsToSkip_[quality]; ++i)
204         {
205             mipImage = image->GetNextLevel(); image = mipImage;
206             levelData = image->GetData();
207             levelWidth = image->GetWidth();
208             levelHeight = image->GetHeight();
209         }
210 
211         switch (components)
212         {
213         case 1:
214             format = useAlpha ? Graphics::GetAlphaFormat() : Graphics::GetLuminanceFormat();
215             break;
216 
217         case 2:
218             format = Graphics::GetLuminanceAlphaFormat();
219             break;
220 
221         case 3:
222             format = Graphics::GetRGBFormat();
223             break;
224 
225         case 4:
226             format = Graphics::GetRGBAFormat();
227             break;
228 
229         default:
230             assert(false);  // Should not reach here
231             break;
232         }
233 
234         // If image was previously compressed, reset number of requested levels to avoid error if level count is too high for new size
235         if (IsCompressed() && requestedLevels_ > 1)
236             requestedLevels_ = 0;
237         SetSize(levelWidth, levelHeight, format);
238         if (!object_.name_)
239             return false;
240 
241         for (unsigned i = 0; i < levels_; ++i)
242         {
243             SetData(i, 0, 0, levelWidth, levelHeight, levelData);
244             memoryUse += levelWidth * levelHeight * components;
245 
246             if (i < levels_ - 1)
247             {
248                 mipImage = image->GetNextLevel(); image = mipImage;
249                 levelData = image->GetData();
250                 levelWidth = image->GetWidth();
251                 levelHeight = image->GetHeight();
252             }
253         }
254     }
255     else
256     {
257         int width = image->GetWidth();
258         int height = image->GetHeight();
259         unsigned levels = image->GetNumCompressedLevels();
260         unsigned format = graphics_->GetFormat(image->GetCompressedFormat());
261         bool needDecompress = false;
262 
263         if (!format)
264         {
265             format = Graphics::GetRGBAFormat();
266             needDecompress = true;
267         }
268 
269         unsigned mipsToSkip = mipsToSkip_[quality];
270         if (mipsToSkip >= levels)
271             mipsToSkip = levels - 1;
272         while (mipsToSkip && (width / (1 << mipsToSkip) < 4 || height / (1 << mipsToSkip) < 4))
273             --mipsToSkip;
274         width /= (1 << mipsToSkip);
275         height /= (1 << mipsToSkip);
276 
277         SetNumLevels(Max((levels - mipsToSkip), 1U));
278         SetSize(width, height, format);
279 
280         for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
281         {
282             CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
283             if (!needDecompress)
284             {
285                 SetData(i, 0, 0, level.width_, level.height_, level.data_);
286                 memoryUse += level.rows_ * level.rowSize_;
287             }
288             else
289             {
290                 unsigned char* rgbaData = new unsigned char[level.width_ * level.height_ * 4];
291                 level.Decompress(rgbaData);
292                 SetData(i, 0, 0, level.width_, level.height_, rgbaData);
293                 memoryUse += level.width_ * level.height_ * 4;
294                 delete[] rgbaData;
295             }
296         }
297     }
298 
299     SetMemoryUse(memoryUse);
300     return true;
301 }
302 
GetData(unsigned level,void * dest) const303 bool Texture2D::GetData(unsigned level, void* dest) const
304 {
305     if (!object_.name_ || !graphics_)
306     {
307         URHO3D_LOGERROR("No texture created, can not get data");
308         return false;
309     }
310 
311 #ifndef GL_ES_VERSION_2_0
312     if (!dest)
313     {
314         URHO3D_LOGERROR("Null destination for getting data");
315         return false;
316     }
317 
318     if (level >= levels_)
319     {
320         URHO3D_LOGERROR("Illegal mip level for getting data");
321         return false;
322     }
323 
324     if (graphics_->IsDeviceLost())
325     {
326         URHO3D_LOGWARNING("Getting texture data while device is lost");
327         return false;
328     }
329 
330     if (multiSample_ > 1 && !autoResolve_)
331     {
332         URHO3D_LOGERROR("Can not get data from multisampled texture without autoresolve");
333         return false;
334     }
335 
336     if (resolveDirty_)
337         graphics_->ResolveToTexture(const_cast<Texture2D*>(this));
338 
339     graphics_->SetTextureForUpdate(const_cast<Texture2D*>(this));
340 
341     if (!IsCompressed())
342         glGetTexImage(target_, level, GetExternalFormat(format_), GetDataType(format_), dest);
343     else
344         glGetCompressedTexImage(target_, level, dest);
345 
346     graphics_->SetTexture(0, 0);
347     return true;
348 #else
349     // Special case on GLES: if the texture is a rendertarget, can make it current and use glReadPixels()
350     if (usage_ == TEXTURE_RENDERTARGET)
351     {
352         graphics_->SetRenderTarget(0, const_cast<Texture2D*>(this));
353         // Ensure the FBO is current; this viewport is actually never rendered to
354         graphics_->SetViewport(IntRect(0, 0, width_, height_));
355         glReadPixels(0, 0, width_, height_, GetExternalFormat(format_), GetDataType(format_), dest);
356         return true;
357     }
358 
359     URHO3D_LOGERROR("Getting texture data not supported");
360     return false;
361 #endif
362 }
363 
Create()364 bool Texture2D::Create()
365 {
366     Release();
367 
368     if (!graphics_ || !width_ || !height_)
369         return false;
370 
371     if (graphics_->IsDeviceLost())
372     {
373         URHO3D_LOGWARNING("Texture creation while device is lost");
374         return true;
375     }
376 
377 #ifdef GL_ES_VERSION_2_0
378     if (multiSample_ > 1)
379     {
380         URHO3D_LOGWARNING("Multisampled texture is not supported on OpenGL ES");
381         multiSample_ = 1;
382         autoResolve_ = false;
383     }
384 #endif
385 
386     unsigned format = GetSRGB() ? GetSRGBFormat(format_) : format_;
387     unsigned externalFormat = GetExternalFormat(format_);
388     unsigned dataType = GetDataType(format_);
389 
390     // Create a renderbuffer instead of a texture if depth texture is not properly supported, or if this will be a packed
391     // depth stencil texture
392 #ifndef GL_ES_VERSION_2_0
393     if (format == Graphics::GetDepthStencilFormat())
394 #else
395     if (format == GL_DEPTH_COMPONENT16 || format == GL_DEPTH_COMPONENT24_OES || format == GL_DEPTH24_STENCIL8_OES ||
396         (format == GL_DEPTH_COMPONENT && !graphics_->GetShadowMapFormat()))
397 #endif
398     {
399         if (renderSurface_)
400         {
401             renderSurface_->CreateRenderBuffer(width_, height_, format, multiSample_);
402             return true;
403         }
404         else
405             return false;
406     }
407     else
408     {
409         if (multiSample_ > 1)
410         {
411             if (autoResolve_)
412             {
413                 // Multisample with autoresolve: create a renderbuffer for rendering, but also a texture
414                 renderSurface_->CreateRenderBuffer(width_, height_, format, multiSample_);
415             }
416             else
417             {
418                 // Multisample without autoresolve: create a texture only
419 #ifndef GL_ES_VERSION_2_0
420                 if (!Graphics::GetGL3Support() && !GLEW_ARB_texture_multisample)
421                 {
422                     URHO3D_LOGERROR("Multisampled texture extension not available");
423                     return false;
424                 }
425 
426                 target_ = GL_TEXTURE_2D_MULTISAMPLE;
427                 if (renderSurface_)
428                     renderSurface_->target_ = GL_TEXTURE_2D_MULTISAMPLE;
429 #endif
430             }
431         }
432     }
433 
434     glGenTextures(1, &object_.name_);
435 
436     // Ensure that our texture is bound to OpenGL texture unit 0
437     graphics_->SetTextureForUpdate(this);
438 
439     // If not compressed, create the initial level 0 texture with null data
440     bool success = true;
441 
442     if (!IsCompressed())
443     {
444         glGetError();
445 #ifndef GL_ES_VERSION_2_0
446         if (multiSample_ > 1 && !autoResolve_)
447             glTexImage2DMultisample(target_, multiSample_, format, width_, height_, GL_TRUE);
448         else
449 #endif
450         glTexImage2D(target_, 0, format, width_, height_, 0, externalFormat, dataType, 0);
451         if (glGetError())
452         {
453             URHO3D_LOGERROR("Failed to create texture");
454             success = false;
455         }
456     }
457 
458     // Set mipmapping
459     if (usage_ == TEXTURE_DEPTHSTENCIL)
460         requestedLevels_ = 1;
461     else if (usage_ == TEXTURE_RENDERTARGET)
462     {
463 #if defined(__EMSCRIPTEN__) || defined(IOS) || defined(TVOS)
464         // glGenerateMipmap appears to not be working on WebGL or iOS/tvOS, disable rendertarget mipmaps for now
465         requestedLevels_ = 1;
466 #else
467         if (requestedLevels_ != 1)
468         {
469             // Generate levels for the first time now
470             RegenerateLevels();
471             // Determine max. levels automatically
472             requestedLevels_ = 0;
473         }
474 #endif
475     }
476 
477     levels_ = CheckMaxLevels(width_, height_, requestedLevels_);
478 #ifndef GL_ES_VERSION_2_0
479     glTexParameteri(target_, GL_TEXTURE_BASE_LEVEL, 0);
480     glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, levels_ - 1);
481 #endif
482 
483     // Set initial parameters, then unbind the texture
484     UpdateParameters();
485     graphics_->SetTexture(0, 0);
486 
487     return success;
488 }
489 
490 }
491