diff options
Diffstat (limited to 'drivers/gles2/rasterizer_storage_gles2.cpp')
-rw-r--r-- | drivers/gles2/rasterizer_storage_gles2.cpp | 254 |
1 files changed, 196 insertions, 58 deletions
diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 40e7f0c441..a1c5d20a14 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -103,15 +103,28 @@ PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT #define glRenderbufferStorageMultisample glRenderbufferStorageMultisampleEXT #define glFramebufferTexture2DMultisample glFramebufferTexture2DMultisampleEXT +PFNGLTEXIMAGE3DOESPROC glTexImage3DOES; +PFNGLTEXSUBIMAGE3DOESPROC glTexSubImage3DOES; +PFNGLCOMPRESSEDTEXSUBIMAGE3DOESPROC glCompressedTexSubImage3DOES; +#define glTexImage3D glTexImage3DOES +#define glTexSubImage3D glTexSubImage3DOES +#define glCompressedTexSubImage3D glCompressedTexSubImage3DOES + #elif defined(UWP_ENABLED) #include <GLES2/gl2ext.h> #define glRenderbufferStorageMultisample glRenderbufferStorageMultisampleANGLE #define glFramebufferTexture2DMultisample glFramebufferTexture2DMultisampleANGLE #endif +#define GL_TEXTURE_3D 0x806F #define GL_MAX_SAMPLES 0x8D57 #endif //!GLES_OVER_GL +#if !defined(GLES_OVER_GL) +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_TEXTURE_3D 0x806F +#endif + void RasterizerStorageGLES2::bind_quad_array() const { glBindBuffer(GL_ARRAY_BUFFER, resources.quadie); glVertexAttribPointer(VS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 4, 0); @@ -566,10 +579,22 @@ void RasterizerStorageGLES2::texture_allocate(RID p_texture, int p_width, int p_ texture->images.resize(6); } break; case VS::TEXTURE_TYPE_2D_ARRAY: { - texture->images.resize(p_depth_3d); + if (config.texture_array_supported) { + texture->target = GL_TEXTURE_2D_ARRAY; + texture->images.resize(p_depth_3d); + } else { + WARN_PRINT_ONCE("Texture Arrays not supported on this hardware."); + return; + } } break; case VS::TEXTURE_TYPE_3D: { - texture->images.resize(p_depth_3d); + if (config.texture_3d_supported) { + texture->target = GL_TEXTURE_3D; + texture->images.resize(p_depth_3d); + } else { + WARN_PRINT_ONCE("3D textures not supported on this hardware."); + return; + } } break; default: { ERR_PRINT("Unknown texture type!"); @@ -590,7 +615,7 @@ void RasterizerStorageGLES2::texture_allocate(RID p_texture, int p_width, int p_ if (p_flags & VS::TEXTURE_FLAG_USED_FOR_STREAMING) { //not supported - ERR_PRINTS("Streaming texture for non power of 2 or has mipmaps on this hardware: " + texture->path + "'. Mipmaps and repeat disabled."); + ERR_PRINT("Streaming texture for non power of 2 or has mipmaps on this hardware: " + texture->path + "'. Mipmaps and repeat disabled."); texture->flags &= ~(VS::TEXTURE_FLAG_REPEAT | VS::TEXTURE_FLAG_MIPMAPS); } else { texture->alloc_height = po2_height; @@ -614,7 +639,42 @@ void RasterizerStorageGLES2::texture_allocate(RID p_texture, int p_width, int p_ glActiveTexture(GL_TEXTURE0); glBindTexture(texture->target, texture->tex_id); - if (p_flags & VS::TEXTURE_FLAG_USED_FOR_STREAMING) { +#if defined(GLES_OVER_GL) || defined(ANDROID_ENABLED) + if ((p_type == VS::TEXTURE_TYPE_3D && config.texture_3d_supported) || (p_type == VS::TEXTURE_TYPE_2D_ARRAY && config.texture_array_supported)) { + + int width = p_width; + int height = p_height; + int depth = p_depth_3d; + + int mipmaps = 0; + + while (width > 0 || height > 0 || (p_type == VS::TEXTURE_TYPE_3D && depth > 0)) { + width = MAX(1, width); + height = MAX(1, height); + depth = MAX(1, depth); + + glTexImage3D(texture->target, mipmaps, internal_format, width, height, depth, 0, format, type, NULL); + + width /= 2; + height /= 2; + + if (p_type == VS::TEXTURE_TYPE_3D) { + depth /= 2; + } + + mipmaps++; + + if (!(p_flags & VS::TEXTURE_FLAG_MIPMAPS)) + break; + } +#ifdef GLES_OVER_GL + glTexParameteri(texture->target, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(texture->target, GL_TEXTURE_MAX_LEVEL, mipmaps - 1); +#endif + + } else +#endif + if (p_flags & VS::TEXTURE_FLAG_USED_FOR_STREAMING) { //prealloc if video glTexImage2D(texture->target, 0, internal_format, texture->alloc_width, texture->alloc_height, 0, format, type, NULL); } @@ -626,6 +686,9 @@ void RasterizerStorageGLES2::texture_set_data(RID p_texture, const Ref<Image> &p Texture *texture = texture_owner.getornull(p_texture); ERR_FAIL_COND(!texture); + if ((texture->type == VS::TEXTURE_TYPE_2D_ARRAY && !config.texture_array_supported) || (texture->type == VS::TEXTURE_TYPE_3D && !config.texture_3d_supported)) { + return; + } ERR_FAIL_COND(!texture->active); ERR_FAIL_COND(texture->render_target); ERR_FAIL_COND(texture->format != p_image->get_format()); @@ -645,7 +708,7 @@ void RasterizerStorageGLES2::texture_set_data(RID p_texture, const Ref<Image> &p if (texture->resize_to_po2) { if (p_image->is_compressed()) { - ERR_PRINTS("Texture '" + texture->path + "' is required to be a power of 2 because it uses either mipmaps or repeat, so it was decompressed. This will hurt performance and memory usage."); + ERR_PRINT("Texture '" + texture->path + "' is required to be a power of 2 because it uses either mipmaps or repeat, so it was decompressed. This will hurt performance and memory usage."); } if (img == p_image) { @@ -668,7 +731,23 @@ void RasterizerStorageGLES2::texture_set_data(RID p_texture, const Ref<Image> &p } } - GLenum blit_target = (texture->target == GL_TEXTURE_CUBE_MAP) ? _cube_side_enum[p_layer] : GL_TEXTURE_2D; + GLenum blit_target = GL_TEXTURE_2D; + + switch (texture->type) { + case VS::TEXTURE_TYPE_2D: { + blit_target = GL_TEXTURE_2D; + } break; + case VS::TEXTURE_TYPE_CUBEMAP: { + ERR_FAIL_INDEX(p_layer, 6); + blit_target = _cube_side_enum[p_layer]; + } break; + case VS::TEXTURE_TYPE_2D_ARRAY: { + blit_target = GL_TEXTURE_2D_ARRAY; + } break; + case VS::TEXTURE_TYPE_3D: { + blit_target = GL_TEXTURE_3D; + } break; + } texture->data_size = img->get_data().size(); PoolVector<uint8_t>::Read read = img->get_data().read(); @@ -725,23 +804,41 @@ void RasterizerStorageGLES2::texture_set_data(RID p_texture, const Ref<Image> &p int size, ofs; img->get_mipmap_offset_and_size(i, ofs, size); + if (texture->type == VS::TEXTURE_TYPE_2D || texture->type == VS::TEXTURE_TYPE_CUBEMAP) { - if (compressed) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + if (compressed) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - int bw = w; - int bh = h; + int bw = w; + int bh = h; - glCompressedTexImage2D(blit_target, i, internal_format, bw, bh, 0, size, &read[ofs]); - } else { + glCompressedTexImage2D(blit_target, i, internal_format, bw, bh, 0, size, &read[ofs]); + } else { - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - if (texture->flags & VS::TEXTURE_FLAG_USED_FOR_STREAMING) { - glTexSubImage2D(blit_target, i, 0, 0, w, h, format, type, &read[ofs]); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + if (texture->flags & VS::TEXTURE_FLAG_USED_FOR_STREAMING) { + glTexSubImage2D(blit_target, i, 0, 0, w, h, format, type, &read[ofs]); + } else { + glTexImage2D(blit_target, i, internal_format, w, h, 0, format, type, &read[ofs]); + } + } + } +#if defined(GLES_OVER_GL) || defined(ANDROID_ENABLED) + else { + if (texture->compressed) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + + int bw = w; + int bh = h; + + glCompressedTexSubImage3D(blit_target, i, 0, 0, p_layer, bw, bh, 1, internal_format, size, &read[ofs]); } else { - glTexImage2D(blit_target, i, internal_format, w, h, 0, format, type, &read[ofs]); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glTexSubImage3D(blit_target, i, 0, 0, p_layer, w, h, 1, format, type, &read[ofs]); } } +#endif tsize += size; @@ -1201,32 +1298,21 @@ void RasterizerStorageGLES2::sky_set_texture(RID p_sky, RID p_panorama, int p_ra GLenum type = GL_UNSIGNED_BYTE; // Set the initial (empty) mipmaps -#if 1 - //Mobile hardware (PowerVR specially) prefers this approach, the other one kills the game + // Mobile hardware (PowerVR specially) prefers this approach, + // the previous approach with manual lod levels kills the game. for (int i = 0; i < 6; i++) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internal_format, size, size, 0, format, type, NULL); } glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - //no filters for now + + // No filters for now glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#else - while (size >= 1) { - - for (int i = 0; i < 6; i++) { - glTexImage2D(_cube_side_enum[i], lod, internal_format, size, size, 0, format, type, NULL); - } - - lod++; - - size >>= 1; - } -#endif - //framebuffer + // Framebuffer glBindFramebuffer(GL_FRAMEBUFFER, resources.mipmap_blur_fbo); @@ -1498,6 +1584,7 @@ void RasterizerStorageGLES2::_update_shader(Shader *p_shader) const { p_shader->texture_count = gen_code.texture_uniforms.size(); p_shader->texture_hints = gen_code.texture_hints; + p_shader->texture_types = gen_code.texture_types; p_shader->uses_vertex_time = gen_code.uses_vertex_time; p_shader->uses_fragment_time = gen_code.uses_fragment_time; @@ -1645,11 +1732,19 @@ void RasterizerStorageGLES2::shader_get_param_list(RID p_shader, List<PropertyIn case ShaderLanguage::TYPE_SAMPLER2DARRAY: case ShaderLanguage::TYPE_ISAMPLER2DARRAY: - case ShaderLanguage::TYPE_USAMPLER2DARRAY: + case ShaderLanguage::TYPE_USAMPLER2DARRAY: { + + pi.type = Variant::OBJECT; + pi.hint = PROPERTY_HINT_RESOURCE_TYPE; + pi.hint_string = "TextureArray"; + } break; + case ShaderLanguage::TYPE_SAMPLER3D: case ShaderLanguage::TYPE_ISAMPLER3D: case ShaderLanguage::TYPE_USAMPLER3D: { - // Not implemented in GLES2 + pi.type = Variant::OBJECT; + pi.hint = PROPERTY_HINT_RESOURCE_TYPE; + pi.hint_string = "Texture3D"; } break; } @@ -2435,22 +2530,10 @@ void RasterizerStorageGLES2::mesh_add_surface(RID p_mesh, uint32_t p_format, VS: if (surface->blend_shape_data.size()) { ERR_PRINT_ONCE("Blend shapes are not supported in OpenGL ES 2.0"); } - surface->data = array; - surface->index_data = p_index_array; -#else - // Even on non-tools builds, a copy of the surface->data is needed in certain circumstances. - // Rigged meshes using the USE_SKELETON_SOFTWARE path need to read bone data - // from surface->data. - - // if USE_SKELETON_SOFTWARE is active - if (config.use_skeleton_software) { - // if this geometry is used specifically for skinning - if (p_format & (VS::ARRAY_FORMAT_BONES | VS::ARRAY_FORMAT_WEIGHTS)) - surface->data = array; - } - // An alternative is to always make a copy of surface->data. #endif + surface->data = array; + surface->index_data = p_index_array; surface->total_data_size += surface->array_byte_size + surface->index_array_byte_size; for (int i = 0; i < surface->skeleton_bone_used.size(); i++) { @@ -2710,6 +2793,7 @@ void RasterizerStorageGLES2::mesh_set_custom_aabb(RID p_mesh, const AABB &p_aabb ERR_FAIL_COND(!mesh); mesh->custom_aabb = p_aabb; + mesh->instance_change_notify(true, false); } AABB RasterizerStorageGLES2::mesh_get_custom_aabb(RID p_mesh) const { @@ -2874,20 +2958,20 @@ void RasterizerStorageGLES2::multimesh_allocate(RID p_multimesh, int p_instances multimesh->xform_floats = 12; } - if (multimesh->color_format == VS::MULTIMESH_COLOR_NONE) { - multimesh->color_floats = 0; - } else if (multimesh->color_format == VS::MULTIMESH_COLOR_8BIT) { + if (multimesh->color_format == VS::MULTIMESH_COLOR_8BIT) { multimesh->color_floats = 1; } else if (multimesh->color_format == VS::MULTIMESH_COLOR_FLOAT) { multimesh->color_floats = 4; + } else { + multimesh->color_floats = 0; } - if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_NONE) { - multimesh->custom_data_floats = 0; - } else if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_8BIT) { + if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_8BIT) { multimesh->custom_data_floats = 1; } else if (multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_FLOAT) { multimesh->custom_data_floats = 4; + } else { + multimesh->custom_data_floats = 0; } int format_floats = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats; @@ -3063,6 +3147,7 @@ void RasterizerStorageGLES2::multimesh_instance_set_color(RID p_multimesh, int p ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); ERR_FAIL_COND(multimesh->color_format == VS::MULTIMESH_COLOR_NONE); + ERR_FAIL_INDEX(multimesh->color_format, VS::MULTIMESH_COLOR_MAX); int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats; float *dataptr = &multimesh->data.write[stride * p_index + multimesh->xform_floats]; @@ -3095,6 +3180,7 @@ void RasterizerStorageGLES2::multimesh_instance_set_custom_data(RID p_multimesh, ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); ERR_FAIL_COND(multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_NONE); + ERR_FAIL_INDEX(multimesh->custom_data_format, VS::MULTIMESH_CUSTOM_DATA_MAX); int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats; float *dataptr = &multimesh->data.write[stride * p_index + multimesh->xform_floats + multimesh->color_floats]; @@ -3182,6 +3268,7 @@ Color RasterizerStorageGLES2::multimesh_instance_get_color(RID p_multimesh, int ERR_FAIL_COND_V(!multimesh, Color()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Color()); ERR_FAIL_COND_V(multimesh->color_format == VS::MULTIMESH_COLOR_NONE, Color()); + ERR_FAIL_INDEX_V(multimesh->color_format, VS::MULTIMESH_COLOR_MAX, Color()); int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats; float *dataptr = &multimesh->data.write[stride * p_index + multimesh->xform_floats]; @@ -3214,6 +3301,7 @@ Color RasterizerStorageGLES2::multimesh_instance_get_custom_data(RID p_multimesh ERR_FAIL_COND_V(!multimesh, Color()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Color()); ERR_FAIL_COND_V(multimesh->custom_data_format == VS::MULTIMESH_CUSTOM_DATA_NONE, Color()); + ERR_FAIL_INDEX_V(multimesh->custom_data_format, VS::MULTIMESH_CUSTOM_DATA_MAX, Color()); int stride = multimesh->color_floats + multimesh->xform_floats + multimesh->custom_data_floats; float *dataptr = &multimesh->data.write[stride * p_index + multimesh->xform_floats + multimesh->color_floats]; @@ -4722,7 +4810,7 @@ void RasterizerStorageGLES2::_render_target_allocate(RenderTarget *rt) { int max_samples = 0; glGetIntegerv(GL_MAX_SAMPLES, &max_samples); if (msaa > max_samples) { - WARN_PRINTS("MSAA must be <= GL_MAX_SAMPLES, falling-back to GL_MAX_SAMPLES = " + itos(max_samples)); + WARN_PRINT("MSAA must be <= GL_MAX_SAMPLES, falling-back to GL_MAX_SAMPLES = " + itos(max_samples)); msaa = max_samples; } @@ -4914,7 +5002,11 @@ void RasterizerStorageGLES2::_render_target_allocate(RenderTarget *rt) { bool used_depth = false; if (j == 0 && i == 0) { //use always - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->depth, 0); + if (config.support_depth_texture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->depth, 0); + } else { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rt->depth); + } used_depth = true; } @@ -5774,6 +5866,16 @@ int RasterizerStorageGLES2::get_render_info(VS::RenderInfo p_info) { } } +String RasterizerStorageGLES2::get_video_adapter_name() const { + + return (const char *)glGetString(GL_RENDERER); +} + +String RasterizerStorageGLES2::get_video_adapter_vendor() const { + + return (const char *)glGetString(GL_VENDOR); +} + void RasterizerStorageGLES2::initialize() { RasterizerStorageGLES2::system_fbo = 0; @@ -5794,6 +5896,8 @@ void RasterizerStorageGLES2::initialize() { config.depth_type = GL_UNSIGNED_INT; #ifdef GLES_OVER_GL + config.texture_3d_supported = true; + config.texture_array_supported = config.extensions.has("GL_EXT_texture_array"); config.float_texture_supported = true; config.s3tc_supported = true; config.pvrtc_supported = false; @@ -5801,12 +5905,22 @@ void RasterizerStorageGLES2::initialize() { config.support_npot_repeat_mipmap = true; config.depth_buffer_internalformat = GL_DEPTH_COMPONENT24; #else + config.texture_3d_supported = config.extensions.has("GL_OES_texture_3D"); + config.texture_array_supported = false; config.float_texture_supported = config.extensions.has("GL_ARB_texture_float") || config.extensions.has("GL_OES_texture_float"); config.s3tc_supported = config.extensions.has("GL_EXT_texture_compression_s3tc") || config.extensions.has("WEBGL_compressed_texture_s3tc"); config.etc1_supported = config.extensions.has("GL_OES_compressed_ETC1_RGB8_texture") || config.extensions.has("WEBGL_compressed_texture_etc1"); config.pvrtc_supported = config.extensions.has("IMG_texture_compression_pvrtc") || config.extensions.has("WEBGL_compressed_texture_pvrtc"); config.support_npot_repeat_mipmap = config.extensions.has("GL_OES_texture_npot"); +#ifdef JAVASCRIPT_ENABLED + // RenderBuffer internal format must be 16 bits in WebGL, + // but depth_texture should default to 32 always + // if the implementation doesn't support 32, it should just quietly use 16 instead + // https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/ + config.depth_buffer_internalformat = GL_DEPTH_COMPONENT16; + config.depth_type = GL_UNSIGNED_INT; +#else // on mobile check for 24 bit depth support for RenderBufferStorage if (config.extensions.has("GL_OES_depth24")) { config.depth_buffer_internalformat = _DEPTH_COMPONENT24_OES; @@ -5816,6 +5930,7 @@ void RasterizerStorageGLES2::initialize() { config.depth_type = GL_UNSIGNED_SHORT; } #endif +#endif #ifndef GLES_OVER_GL //Manually load extensions for android and ios @@ -5830,6 +5945,9 @@ void RasterizerStorageGLES2::initialize() { void *gles2_lib = dlopen("libGLESv2.so", RTLD_LAZY); glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)dlsym(gles2_lib, "glRenderbufferStorageMultisampleEXT"); glFramebufferTexture2DMultisampleEXT = (PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)dlsym(gles2_lib, "glFramebufferTexture2DMultisampleEXT"); + glTexImage3DOES = (PFNGLTEXIMAGE3DOESPROC)dlsym(gles2_lib, "glTexImage3DOES"); + glTexSubImage3DOES = (PFNGLTEXSUBIMAGE3DOESPROC)dlsym(gles2_lib, "glTexSubImage3DOES"); + glCompressedTexSubImage3DOES = (PFNGLCOMPRESSEDTEXSUBIMAGE3DOESPROC)dlsym(gles2_lib, "glCompressedTexSubImage3DOES"); #endif #endif @@ -6052,6 +6170,26 @@ void RasterizerStorageGLES2::initialize() { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, anisotexdata); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); + +#if defined(GLES_OVER_GL) || defined(ANDROID_ENABLED) + glGenTextures(1, &resources.white_tex_3d); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_3D, resources.white_tex_3d); + glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, 2, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, whitetexdata); + +#ifdef GLES_OVER_GL + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0); +#endif + + glGenTextures(1, &resources.white_tex_array); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_ARRAY, resources.white_tex_array); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, 8, 8, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 1, GL_RGB, GL_UNSIGNED_BYTE, whitetexdata); + glGenerateMipmap(GL_TEXTURE_2D_ARRAY); + glBindTexture(GL_TEXTURE_2D, 0); +#endif } // skeleton buffer |