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