xref: /reactos/dll/directx/wine/wined3d/resource.c (revision 40462c92)
1 /*
2  * Copyright 2002-2004 Jason Edmeades
3  * Copyright 2003-2004 Raphael Junqueira
4  * Copyright 2004 Christian Costa
5  * Copyright 2005 Oliver Stieber
6  * Copyright 2009-2010 Henri Verbeet for CodeWeavers
7  * Copyright 2006-2008, 2013 Stefan Dösinger for CodeWeavers
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
24 #include "config.h"
25 #include "wine/port.h"
26 #include "wined3d_private.h"
27 
28 WINE_DEFAULT_DEBUG_CHANNEL(d3d);
29 WINE_DECLARE_DEBUG_CHANNEL(d3d_perf);
30 
31 static void resource_check_usage(DWORD usage)
32 {
33     static DWORD handled = WINED3DUSAGE_RENDERTARGET
34             | WINED3DUSAGE_DEPTHSTENCIL
35             | WINED3DUSAGE_WRITEONLY
36             | WINED3DUSAGE_DYNAMIC
37             | WINED3DUSAGE_STATICDECL
38             | WINED3DUSAGE_OVERLAY
39             | WINED3DUSAGE_SCRATCH
40             | WINED3DUSAGE_PRIVATE
41             | WINED3DUSAGE_LEGACY_CUBEMAP
42             | WINED3DUSAGE_TEXTURE;
43 
44     /* WINED3DUSAGE_WRITEONLY is supposed to result in write-combined mappings
45      * being returned. OpenGL doesn't give us explicit control over that, but
46      * the hints and access flags we set for typical access patterns on
47      * dynamic resources should in theory have the same effect on the OpenGL
48      * driver. */
49 
50     if (usage & ~handled)
51     {
52         FIXME("Unhandled usage flags %#x.\n", usage & ~handled);
53         handled |= usage;
54     }
55     if ((usage & (WINED3DUSAGE_DYNAMIC | WINED3DUSAGE_WRITEONLY)) == WINED3DUSAGE_DYNAMIC)
56         WARN_(d3d_perf)("WINED3DUSAGE_DYNAMIC used without WINED3DUSAGE_WRITEONLY.\n");
57 }
58 
59 HRESULT resource_init(struct wined3d_resource *resource, struct wined3d_device *device,
60         enum wined3d_resource_type type, const struct wined3d_format *format,
61         enum wined3d_multisample_type multisample_type, unsigned int multisample_quality,
62         unsigned int usage, unsigned int access, unsigned int width, unsigned int height, unsigned int depth,
63         unsigned int size, void *parent, const struct wined3d_parent_ops *parent_ops,
64         const struct wined3d_resource_ops *resource_ops)
65 {
66     enum wined3d_gl_resource_type base_type = WINED3D_GL_RES_TYPE_COUNT;
67     enum wined3d_gl_resource_type gl_type = WINED3D_GL_RES_TYPE_COUNT;
68     const struct wined3d_gl_info *gl_info = &device->adapter->gl_info;
69     BOOL tex_2d_ok = FALSE;
70     unsigned int i;
71 
72     static const struct
73     {
74         enum wined3d_resource_type type;
75         DWORD cube_usage;
76         enum wined3d_gl_resource_type gl_type;
77     }
78     resource_types[] =
79     {
80         {WINED3D_RTYPE_BUFFER,      0,                              WINED3D_GL_RES_TYPE_BUFFER},
81         {WINED3D_RTYPE_TEXTURE_1D,  0,                              WINED3D_GL_RES_TYPE_TEX_1D},
82         {WINED3D_RTYPE_TEXTURE_2D,  0,                              WINED3D_GL_RES_TYPE_TEX_2D},
83         {WINED3D_RTYPE_TEXTURE_2D,  0,                              WINED3D_GL_RES_TYPE_TEX_RECT},
84         {WINED3D_RTYPE_TEXTURE_2D,  0,                              WINED3D_GL_RES_TYPE_RB},
85         {WINED3D_RTYPE_TEXTURE_2D,  WINED3DUSAGE_LEGACY_CUBEMAP,    WINED3D_GL_RES_TYPE_TEX_CUBE},
86         {WINED3D_RTYPE_TEXTURE_3D,  0,                              WINED3D_GL_RES_TYPE_TEX_3D},
87     };
88 
89     resource_check_usage(usage);
90 
91     if (usage & WINED3DUSAGE_SCRATCH && access & WINED3D_RESOURCE_ACCESS_GPU)
92     {
93         ERR("Trying to create a scratch resource with access flags %s.\n",
94                 wined3d_debug_resource_access(access));
95         return WINED3DERR_INVALIDCALL;
96     }
97 
98     for (i = 0; i < ARRAY_SIZE(resource_types); ++i)
99     {
100         if (resource_types[i].type != type
101                 || resource_types[i].cube_usage != (usage & WINED3DUSAGE_LEGACY_CUBEMAP))
102             continue;
103 
104         gl_type = resource_types[i].gl_type;
105         if (base_type == WINED3D_GL_RES_TYPE_COUNT)
106             base_type = gl_type;
107 
108         if ((usage & WINED3DUSAGE_RENDERTARGET) && !(format->flags[gl_type] & WINED3DFMT_FLAG_RENDERTARGET))
109         {
110             WARN("Format %s cannot be used for render targets.\n", debug_d3dformat(format->id));
111             continue;
112         }
113         if ((usage & WINED3DUSAGE_DEPTHSTENCIL)
114                 && !(format->flags[gl_type] & (WINED3DFMT_FLAG_DEPTH | WINED3DFMT_FLAG_STENCIL)))
115         {
116             WARN("Format %s cannot be used for depth/stencil buffers.\n", debug_d3dformat(format->id));
117             continue;
118         }
119         if (wined3d_settings.offscreen_rendering_mode == ORM_FBO
120                 && usage & (WINED3DUSAGE_RENDERTARGET | WINED3DUSAGE_DEPTHSTENCIL)
121                 && !(format->flags[gl_type] & WINED3DFMT_FLAG_FBO_ATTACHABLE))
122         {
123             WARN("Render target or depth stencil is not FBO attachable.\n");
124             continue;
125         }
126         if ((usage & WINED3DUSAGE_TEXTURE) && !(format->flags[gl_type] & WINED3DFMT_FLAG_TEXTURE))
127         {
128             WARN("Format %s cannot be used for texturing.\n", debug_d3dformat(format->id));
129             continue;
130         }
131         if (((width & (width - 1)) || (height & (height - 1)))
132                 && !gl_info->supported[ARB_TEXTURE_NON_POWER_OF_TWO]
133                 && !gl_info->supported[WINED3D_GL_NORMALIZED_TEXRECT]
134                 && gl_type == WINED3D_GL_RES_TYPE_TEX_2D)
135         {
136             TRACE("Skipping 2D texture type to try texture rectangle.\n");
137             tex_2d_ok = TRUE;
138             continue;
139         }
140         break;
141     }
142 
143     if (base_type != WINED3D_GL_RES_TYPE_COUNT && i == ARRAY_SIZE(resource_types))
144     {
145         if (tex_2d_ok)
146         {
147             /* Non power of 2 texture and rectangle textures or renderbuffers do not work.
148              * Use 2D textures, the texture code will pad to a power of 2 size. */
149             gl_type = WINED3D_GL_RES_TYPE_TEX_2D;
150         }
151         else if (usage & WINED3DUSAGE_SCRATCH)
152         {
153             /* Needed for proper format information. */
154             gl_type = base_type;
155         }
156         else
157         {
158             WARN("Did not find a suitable GL resource type for resource type %s.\n",
159                     debug_d3dresourcetype(type));
160             return WINED3DERR_INVALIDCALL;
161         }
162     }
163 
164     if (base_type != WINED3D_GL_RES_TYPE_COUNT
165             && (format->flags[base_type] & (WINED3DFMT_FLAG_BLOCKS | WINED3DFMT_FLAG_BLOCKS_NO_VERIFY))
166             == WINED3DFMT_FLAG_BLOCKS)
167     {
168         UINT width_mask = format->block_width - 1;
169         UINT height_mask = format->block_height - 1;
170         if (width & width_mask || height & height_mask)
171             return WINED3DERR_INVALIDCALL;
172     }
173 
174     resource->ref = 1;
175     resource->device = device;
176     resource->type = type;
177     resource->gl_type = gl_type;
178     resource->format = format;
179     if (gl_type < WINED3D_GL_RES_TYPE_COUNT)
180         resource->format_flags = format->flags[gl_type];
181     resource->multisample_type = multisample_type;
182     resource->multisample_quality = multisample_quality;
183     resource->usage = usage;
184     resource->access = access;
185     resource->width = width;
186     resource->height = height;
187     resource->depth = depth;
188     resource->size = size;
189     resource->priority = 0;
190     resource->parent = parent;
191     resource->parent_ops = parent_ops;
192     resource->resource_ops = resource_ops;
193     resource->map_binding = WINED3D_LOCATION_SYSMEM;
194 
195     if (size)
196     {
197         if (!wined3d_resource_allocate_sysmem(resource))
198         {
199             ERR("Failed to allocate system memory.\n");
200             return E_OUTOFMEMORY;
201         }
202     }
203     else
204     {
205         resource->heap_memory = NULL;
206     }
207 
208     if (!(usage & WINED3DUSAGE_PRIVATE))
209     {
210         /* Check that we have enough video ram left */
211         if (!(access & WINED3D_RESOURCE_ACCESS_CPU) && device->wined3d->flags & WINED3D_VIDMEM_ACCOUNTING)
212         {
213             if (size > wined3d_device_get_available_texture_mem(device))
214             {
215                 ERR("Out of adapter memory\n");
216                 wined3d_resource_free_sysmem(resource);
217                 return WINED3DERR_OUTOFVIDEOMEMORY;
218             }
219             adapter_adjust_memory(device->adapter, size);
220         }
221 
222         device_resource_add(device, resource);
223     }
224 
225     return WINED3D_OK;
226 }
227 
228 static void wined3d_resource_destroy_object(void *object)
229 {
230     struct wined3d_resource *resource = object;
231 
232     wined3d_resource_free_sysmem(resource);
233     context_resource_released(resource->device, resource, resource->type);
234     wined3d_resource_release(resource);
235 }
236 
237 void resource_cleanup(struct wined3d_resource *resource)
238 {
239     const struct wined3d *d3d = resource->device->wined3d;
240 
241     TRACE("Cleaning up resource %p.\n", resource);
242 
243     if (!(resource->usage & WINED3DUSAGE_PRIVATE))
244     {
245         if (!(resource->access & WINED3D_RESOURCE_ACCESS_CPU) && d3d->flags & WINED3D_VIDMEM_ACCOUNTING)
246         {
247             TRACE("Decrementing device memory pool by %u.\n", resource->size);
248             adapter_adjust_memory(resource->device->adapter, (INT64)0 - resource->size);
249         }
250 
251         device_resource_released(resource->device, resource);
252     }
253     wined3d_resource_acquire(resource);
254     wined3d_cs_destroy_object(resource->device->cs, wined3d_resource_destroy_object, resource);
255 }
256 
257 void resource_unload(struct wined3d_resource *resource)
258 {
259     if (resource->map_count)
260         ERR("Resource %p is being unloaded while mapped.\n", resource);
261 }
262 
263 DWORD CDECL wined3d_resource_set_priority(struct wined3d_resource *resource, DWORD priority)
264 {
265     DWORD prev;
266 
267     if (!wined3d_resource_access_is_managed(resource->access))
268     {
269         WARN("Called on non-managed resource %p, ignoring.\n", resource);
270         return 0;
271     }
272 
273     prev = resource->priority;
274     resource->priority = priority;
275     TRACE("resource %p, new priority %u, returning old priority %u.\n", resource, priority, prev);
276     return prev;
277 }
278 
279 DWORD CDECL wined3d_resource_get_priority(const struct wined3d_resource *resource)
280 {
281     TRACE("resource %p, returning %u.\n", resource, resource->priority);
282     return resource->priority;
283 }
284 
285 void * CDECL wined3d_resource_get_parent(const struct wined3d_resource *resource)
286 {
287     return resource->parent;
288 }
289 
290 void CDECL wined3d_resource_set_parent(struct wined3d_resource *resource, void *parent)
291 {
292     resource->parent = parent;
293 }
294 
295 void CDECL wined3d_resource_get_desc(const struct wined3d_resource *resource, struct wined3d_resource_desc *desc)
296 {
297     desc->resource_type = resource->type;
298     desc->format = resource->format->id;
299     desc->multisample_type = resource->multisample_type;
300     desc->multisample_quality = resource->multisample_quality;
301     desc->usage = resource->usage;
302     desc->access = resource->access;
303     desc->width = resource->width;
304     desc->height = resource->height;
305     desc->depth = resource->depth;
306     desc->size = resource->size;
307 }
308 
309 static DWORD wined3d_resource_sanitise_map_flags(const struct wined3d_resource *resource, DWORD flags)
310 {
311     /* Not all flags make sense together, but Windows never returns an error.
312      * Catch the cases that could cause issues. */
313     if (flags & WINED3D_MAP_READ)
314     {
315         if (flags & WINED3D_MAP_DISCARD)
316         {
317             WARN("WINED3D_MAP_READ combined with WINED3D_MAP_DISCARD, ignoring flags.\n");
318             return flags & (WINED3D_MAP_READ | WINED3D_MAP_WRITE);
319         }
320         if (flags & WINED3D_MAP_NOOVERWRITE)
321         {
322             WARN("WINED3D_MAP_READ combined with WINED3D_MAP_NOOVERWRITE, ignoring flags.\n");
323             return flags & (WINED3D_MAP_READ | WINED3D_MAP_WRITE);
324         }
325     }
326     else if (flags & (WINED3D_MAP_DISCARD | WINED3D_MAP_NOOVERWRITE))
327     {
328         if (!(resource->usage & WINED3DUSAGE_DYNAMIC))
329         {
330             WARN("DISCARD or NOOVERWRITE map on non-dynamic buffer, ignoring.\n");
331             return flags & (WINED3D_MAP_READ | WINED3D_MAP_WRITE);
332         }
333         if ((flags & (WINED3D_MAP_DISCARD | WINED3D_MAP_NOOVERWRITE))
334                 == (WINED3D_MAP_DISCARD | WINED3D_MAP_NOOVERWRITE))
335         {
336             WARN("WINED3D_MAP_NOOVERWRITE used with WINED3D_MAP_DISCARD, ignoring WINED3D_MAP_DISCARD.\n");
337             flags &= ~WINED3D_MAP_DISCARD;
338         }
339     }
340 
341     return flags;
342 }
343 
344 HRESULT CDECL wined3d_resource_map(struct wined3d_resource *resource, unsigned int sub_resource_idx,
345         struct wined3d_map_desc *map_desc, const struct wined3d_box *box, DWORD flags)
346 {
347     TRACE("resource %p, sub_resource_idx %u, map_desc %p, box %s, flags %#x.\n",
348             resource, sub_resource_idx, map_desc, debug_box(box), flags);
349 
350     if (!(flags & (WINED3D_MAP_READ | WINED3D_MAP_WRITE)))
351     {
352         WARN("No read/write flags specified.\n");
353         return E_INVALIDARG;
354     }
355 
356     if ((flags & WINED3D_MAP_READ) && !(resource->access & WINED3D_RESOURCE_ACCESS_MAP_R))
357     {
358         WARN("Resource does not have MAP_R access.\n");
359         return E_INVALIDARG;
360     }
361 
362     if ((flags & WINED3D_MAP_WRITE) && !(resource->access & WINED3D_RESOURCE_ACCESS_MAP_W))
363     {
364         WARN("Resource does not have MAP_W access.\n");
365         return E_INVALIDARG;
366     }
367 
368     flags = wined3d_resource_sanitise_map_flags(resource, flags);
369     wined3d_resource_wait_idle(resource);
370 
371     return wined3d_cs_map(resource->device->cs, resource, sub_resource_idx, map_desc, box, flags);
372 }
373 
374 HRESULT CDECL wined3d_resource_map_info(struct wined3d_resource *resource, unsigned int sub_resource_idx,
375         struct wined3d_map_info *info, DWORD flags)
376 {
377     TRACE("resource %p, sub_resource_idx %u.\n", resource, sub_resource_idx);
378 
379     return resource->resource_ops->resource_map_info(resource, sub_resource_idx, info, flags);
380 }
381 
382 HRESULT CDECL wined3d_resource_unmap(struct wined3d_resource *resource, unsigned int sub_resource_idx)
383 {
384     TRACE("resource %p, sub_resource_idx %u.\n", resource, sub_resource_idx);
385 
386     return wined3d_cs_unmap(resource->device->cs, resource, sub_resource_idx);
387 }
388 
389 UINT CDECL wined3d_resource_update_info(struct wined3d_resource *resource, unsigned int sub_resource_idx,
390         const struct wined3d_box *box, unsigned int row_pitch, unsigned int depth_pitch)
391 {
392     unsigned int width, height, depth;
393     struct wined3d_box b;
394     UINT data_size;
395 
396     TRACE("resource %p, sub_resource_idx %u, box %s, row_pitch %u, depth_pitch %u.\n",
397             resource, sub_resource_idx, debug_box(box), row_pitch, depth_pitch);
398 
399     if (resource->type == WINED3D_RTYPE_BUFFER)
400     {
401         if (sub_resource_idx > 0)
402         {
403             WARN("Invalid sub_resource_idx %u.\n", sub_resource_idx);
404             return 0;
405         }
406 
407         width = resource->size;
408         height = 1;
409         depth = 1;
410     }
411     else if (resource->type == WINED3D_RTYPE_TEXTURE_1D ||
412             resource->type == WINED3D_RTYPE_TEXTURE_2D || resource->type == WINED3D_RTYPE_TEXTURE_3D)
413     {
414         struct wined3d_texture *texture = texture_from_resource(resource);
415         unsigned int level;
416 
417         if (sub_resource_idx >= texture->level_count * texture->layer_count)
418         {
419             WARN("Invalid sub_resource_idx %u.\n", sub_resource_idx);
420             return 0;
421         }
422 
423         level = sub_resource_idx % texture->level_count;
424         width = wined3d_texture_get_level_width(texture, level);
425         height = wined3d_texture_get_level_height(texture, level);
426         depth = wined3d_texture_get_level_depth(texture, level);
427     }
428     else
429     {
430         FIXME("Not implemented for %s resources.\n", debug_d3dresourcetype(resource->type));
431         return 0;
432     }
433 
434     if (!box)
435     {
436         wined3d_box_set(&b, 0, 0, width, height, 0, depth);
437         box = &b;
438     }
439     else if (box->left >= box->right || box->right > width
440             || box->top >= box->bottom || box->bottom > height
441             || box->front >= box->back || box->back > depth)
442     {
443         WARN("Invalid box %s specified.\n", debug_box(box));
444         return 0;
445     }
446 
447     if (resource->format_flags & WINED3DFMT_FLAG_BLOCKS)
448     {
449         if (resource->type != WINED3D_RTYPE_TEXTURE_2D)
450         {
451             FIXME("Calculation of block formats not implemented for %s resources.\n", debug_d3dresourcetype(resource->type));
452             return 0;
453         }
454 
455         height  = (box->bottom - box->top  + resource->format->block_height - 1) / resource->format->block_height;
456         width   = (box->right  - box->left + resource->format->block_width  - 1) / resource->format->block_width;
457         return (height - 1) * row_pitch + width * resource->format->block_byte_count;
458     }
459 
460     data_size = 0;
461     switch (resource->type)
462     {
463         case WINED3D_RTYPE_TEXTURE_3D:
464             data_size += (box->back - box->front - 1) * depth_pitch;
465             /* fall-through */
466         case WINED3D_RTYPE_TEXTURE_2D:
467             data_size += (box->bottom - box->top - 1) * row_pitch;
468             /* fall-through */
469         case WINED3D_RTYPE_TEXTURE_1D:
470             data_size += (box->right - box->left) * resource->format->byte_count;
471             break;
472         case WINED3D_RTYPE_BUFFER:
473             data_size = box->right - box->left;
474             break;
475         case WINED3D_RTYPE_NONE:
476             break;
477     }
478 
479     return data_size;
480 }
481 
482 void CDECL wined3d_resource_preload(struct wined3d_resource *resource)
483 {
484     wined3d_cs_emit_preload_resource(resource->device->cs, resource);
485 }
486 
487 BOOL wined3d_resource_allocate_sysmem(struct wined3d_resource *resource)
488 {
489     void **p;
490     SIZE_T align = RESOURCE_ALIGNMENT - 1 + sizeof(*p);
491     void *mem;
492 
493     if (!(mem = heap_alloc_zero(resource->size + align)))
494         return FALSE;
495 
496     p = (void **)(((ULONG_PTR)mem + align) & ~(RESOURCE_ALIGNMENT - 1)) - 1;
497     *p = mem;
498 
499     resource->heap_memory = ++p;
500 
501     return TRUE;
502 }
503 
504 void wined3d_resource_free_sysmem(struct wined3d_resource *resource)
505 {
506     void **p = resource->heap_memory;
507 
508     if (!p)
509         return;
510 
511     heap_free(*(--p));
512     resource->heap_memory = NULL;
513 }
514 
515 GLbitfield wined3d_resource_gl_map_flags(DWORD d3d_flags)
516 {
517     GLbitfield ret = 0;
518 
519     if (d3d_flags & WINED3D_MAP_WRITE)
520         ret |= GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT;
521     if (d3d_flags & WINED3D_MAP_READ)
522         ret |= GL_MAP_READ_BIT;
523 
524     if (d3d_flags & WINED3D_MAP_DISCARD)
525         ret |= GL_MAP_INVALIDATE_BUFFER_BIT;
526     if (d3d_flags & WINED3D_MAP_NOOVERWRITE)
527         ret |= GL_MAP_UNSYNCHRONIZED_BIT;
528 
529     return ret;
530 }
531 
532 GLenum wined3d_resource_gl_legacy_map_flags(DWORD d3d_flags)
533 {
534     switch (d3d_flags & (WINED3D_MAP_READ | WINED3D_MAP_WRITE))
535     {
536         case WINED3D_MAP_READ:
537             return GL_READ_ONLY_ARB;
538 
539         case WINED3D_MAP_WRITE:
540             return GL_WRITE_ONLY_ARB;
541 
542         default:
543             return GL_READ_WRITE_ARB;
544     }
545 }
546 
547 BOOL wined3d_resource_is_offscreen(struct wined3d_resource *resource)
548 {
549     struct wined3d_swapchain *swapchain;
550 
551     /* Only 2D texture resources can be onscreen. */
552     if (resource->type != WINED3D_RTYPE_TEXTURE_2D)
553         return TRUE;
554 
555     /* Not on a swapchain - must be offscreen */
556     if (!(swapchain = texture_from_resource(resource)->swapchain))
557         return TRUE;
558 
559     /* The front buffer is always onscreen */
560     if (resource == &swapchain->front_buffer->resource)
561         return FALSE;
562 
563     /* If the swapchain is rendered to an FBO, the backbuffer is
564      * offscreen, otherwise onscreen */
565     return swapchain->render_to_fbo;
566 }
567 
568 void wined3d_resource_update_draw_binding(struct wined3d_resource *resource)
569 {
570     if (!wined3d_resource_is_offscreen(resource) || wined3d_settings.offscreen_rendering_mode != ORM_FBO)
571     {
572         resource->draw_binding = WINED3D_LOCATION_DRAWABLE;
573     }
574     else if (resource->multisample_type)
575     {
576         const struct wined3d_gl_info *gl_info = &resource->device->adapter->gl_info;
577         if (gl_info->supported[ARB_TEXTURE_MULTISAMPLE])
578             resource->draw_binding = WINED3D_LOCATION_TEXTURE_RGB;
579         else
580             resource->draw_binding = WINED3D_LOCATION_RB_MULTISAMPLE;
581     }
582     else if (resource->gl_type == WINED3D_GL_RES_TYPE_RB)
583     {
584         resource->draw_binding = WINED3D_LOCATION_RB_RESOLVED;
585     }
586     else
587     {
588         resource->draw_binding = WINED3D_LOCATION_TEXTURE_RGB;
589     }
590 }
591