Răsfoiți Sursa

Add Open GL ES 3 support (#2536)

* Add support for GLES3 rendering API and it features:
- Instancing
- Uniform buffer object
- Multiple render targets
- Texture2DArray
- Texture3D
- Support for textures: ETC2, sRGB, floating point, depth, 1 & 2 channel (R & R/G).
Worked render pathes - Deferred, DeferredHWDepth, Prepass, PrepassHWDepth.
For build set option URHO3D_GLES3=1 - for CMake -DURHO3D_GLES3=1, for Gradle /PURHO3D_GLES3=1.

Fixed broken shadowmaps on GLES3.
Activated using uniform constant buffers on GLES3.
Added info about GPU and GL version in debug hud in OpenGL.

* Fix cosmetic stuff.

* fix spaces

* Set URHO3D_GLES3 option default False, and force clear it on non android or webgl builds.
Otherwise it set GL_ES in desktop shaders.

* Remove setting URHO3D_GLES3 to false in IOS and TVOS build config.

* Fix Android build Urho3DPlayer with GLES3

* Fix precision for GL ES 3.

* Remove using constant uniform buffers in GL ES3.

* Fix emscripten build errors.

* Prepeare for merge

* Fix some compile errors

* Fix webgl builds. Add GLES3 sample.
orefkov 3 ani în urmă
părinte
comite
508c281a44
36 a modificat fișierele cu 6016 adăugiri și 5701 ștergeri
  1. 36 0
      Source/Samples/06_SkeletalAnimation/SkeletalAnimation.cpp
  2. 4 0
      Source/Samples/06_SkeletalAnimation/SkeletalAnimation.h
  3. 5 0
      Source/Urho3D/Engine/DebugHud.cpp
  4. 5 4
      Source/Urho3D/Graphics/Batch.cpp
  5. 10 1
      Source/Urho3D/Graphics/Graphics.h
  6. 5 5
      Source/Urho3D/Graphics/Material.cpp
  7. 2 2
      Source/Urho3D/Graphics/Renderer.cpp
  8. 8 7
      Source/Urho3D/Graphics/View.cpp
  9. 374 369
      Source/Urho3D/GraphicsAPI/GraphicsDefs.h
  10. 3485 3407
      Source/Urho3D/GraphicsAPI/OpenGL/OGLGraphics.cpp
  11. 150 128
      Source/Urho3D/GraphicsAPI/OpenGL/OGLGraphicsImpl.h
  12. 103 102
      Source/Urho3D/GraphicsAPI/OpenGL/OGLRenderSurface.cpp
  13. 368 368
      Source/Urho3D/GraphicsAPI/OpenGL/OGLShaderProgram.cpp
  14. 11 0
      Source/Urho3D/GraphicsAPI/OpenGL/OGLShaderVariation.cpp
  15. 388 330
      Source/Urho3D/GraphicsAPI/OpenGL/OGLTexture.cpp
  16. 480 478
      Source/Urho3D/GraphicsAPI/OpenGL/OGLTexture2D.cpp
  17. 1 1
      Source/Urho3D/GraphicsAPI/OpenGL/OGLTexture2DArray.cpp
  18. 3 3
      Source/Urho3D/GraphicsAPI/OpenGL/OGLTexture3D.cpp
  19. 459 459
      Source/Urho3D/GraphicsAPI/OpenGL/OGLTextureCube.cpp
  20. 1 1
      Source/Urho3D/GraphicsAPI/ShaderPrecache.cpp
  21. 1 1
      Source/Urho3D/GraphicsAPI/Texture2DArray.cpp
  22. 1 1
      Source/Urho3D/GraphicsAPI/Texture3D.cpp
  23. 18 4
      Source/Urho3D/Resource/Image.cpp
  24. 1 1
      bin/CoreData/RenderPaths/DeferredHWDepth.xml
  25. 1 1
      bin/CoreData/Shaders/GLSL/Basic.glsl
  26. 7 7
      bin/CoreData/Shaders/GLSL/Lighting.glsl
  27. 3 1
      bin/CoreData/Shaders/GLSL/LitSolid.glsl
  28. 8 2
      bin/CoreData/Shaders/GLSL/PostProcess.glsl
  29. 7 7
      bin/CoreData/Shaders/GLSL/Samplers.glsl
  30. 1 1
      bin/CoreData/Shaders/GLSL/Text.glsl
  31. 7 5
      bin/CoreData/Shaders/GLSL/Transform.glsl
  32. 10 3
      bin/CoreData/Shaders/GLSL/Uniforms.glsl
  33. 38 1
      bin/Data/Scripts/06_SkeletalAnimation.as
  34. 13 1
      cmake/Modules/UrhoCommon.cmake
  35. 1 0
      script/.build-options
  36. 1 0
      script/.env-file

+ 36 - 0
Source/Samples/06_SkeletalAnimation/SkeletalAnimation.cpp

@@ -14,6 +14,7 @@
 #include <Urho3D/Graphics/Octree.h>
 #include <Urho3D/Graphics/Renderer.h>
 #include <Urho3D/Graphics/Zone.h>
+#include <Urho3D/Graphics/RenderPath.h>
 #include <Urho3D/Input/Input.h>
 #include <Urho3D/Resource/ResourceCache.h>
 #include <Urho3D/Scene/Scene.h>
@@ -130,6 +131,14 @@ void SkeletalAnimation::CreateScene()
         // Create our custom Mover component that will move & animate the model during each frame's update
         auto* mover = modelNode->CreateComponent<Mover>();
         mover->SetParameters(MODEL_MOVE_SPEED, MODEL_ROTATE_SPEED, bounds);
+#ifdef URHO3D_GLES3
+        Node* nLight = modelNode->CreateChild("Light", LOCAL);
+        nLight->SetPosition(Vector3(1.0f, 2.0f, 1.0f));
+        nLight->LookAt(Vector3::ZERO, Vector3::UP, TransformSpace::Parent);
+        Light* light = nLight->CreateComponent<Light>();
+        light->SetLightType(LIGHT_SPOT);
+        light->SetColor(Color(0.5f + Random(0.5f), 0.5f + Random(0.5f), 0.5f + Random(0.5f)));
+#endif
     }
 
     // Create the camera. Limit far clip distance to match the fog
@@ -139,8 +148,29 @@ void SkeletalAnimation::CreateScene()
 
     // Set an initial position for the camera scene node above the plane
     cameraNode_->SetPosition(Vector3(0.0f, 5.0f, 0.0f));
+#ifdef URHO3D_GLES3
+    CreateLights();
+#endif
 }
 
+#ifdef URHO3D_GLES3
+void SkeletalAnimation::CreateLights() {
+    for (unsigned i = 0; i < 40; i++) {
+        Node* nLight = scene_->CreateChild("Light", LOCAL);
+        Vector3 pos(Random(40.0f) - 20.0f, 1.0f + Random(1.0f), Random(40.0f) - 20.0f);
+        nLight->SetPosition(pos);
+        pos.y_ = 0;
+        pos.x_ += Random(2.0f) - 1.0f;
+        pos.z_ += Random(2.0f) - 1.0f;
+        nLight->LookAt(pos);
+
+        Light* light = nLight->CreateComponent<Light>();
+        light->SetLightType(LIGHT_SPOT);
+        light->SetColor(Color(0.5f + Random(0.5f), 0.5f + Random(0.5f), 0.5f + Random(0.5f)));
+    }
+}
+#endif
+
 void SkeletalAnimation::CreateInstructions()
 {
     auto* cache = GetSubsystem<ResourceCache>();
@@ -168,6 +198,12 @@ void SkeletalAnimation::SetupViewport()
 
     // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
     SharedPtr<Viewport> viewport(new Viewport(context_, scene_, cameraNode_->GetComponent<Camera>()));
+#ifdef URHO3D_GLES3
+        SharedPtr<RenderPath> rp(new RenderPath);
+        auto* cache = GetSubsystem<ResourceCache>();
+        rp->Load(cache->GetResource<XMLFile>("RenderPaths/Deferred.xml"));
+        viewport->SetRenderPath(rp);
+#endif
     renderer->SetViewport(0, viewport);
 }
 

+ 4 - 0
Source/Samples/06_SkeletalAnimation/SkeletalAnimation.h

@@ -51,6 +51,10 @@ private:
     void CreateScene();
     /// Construct an instruction text to the UI.
     void CreateInstructions();
+#ifdef URHO3D_GLES3
+    /// Create additional lights fo scene
+    void CreateLights();
+#endif
     /// Set up a viewport for displaying the scene.
     void SetupViewport();
     /// Subscribe to application-wide logic update and post-render update events.

+ 5 - 0
Source/Urho3D/Engine/DebugHud.cpp

@@ -155,6 +155,11 @@ void DebugHud::Update()
             renderer->GetMaxOccluderTriangles() > 0 ? "On" : "Off",
             renderer->GetDynamicInstancing() ? "On" : "Off",
             graphics->GetApiName().CString());
+    #ifdef URHO3D_OPENGL
+        mode.AppendWithFormat(" Renderer:%s Version:%s", graphics->GetRendererName().CString(),
+            graphics->GetVersionString().CString());
+    #endif
+
 
         modeText_->SetText(mode);
     }

+ 5 - 4
Source/Urho3D/Graphics/Batch.cpp

@@ -6,6 +6,7 @@
 #include "../Graphics/Camera.h"
 #include "../Graphics/Geometry.h"
 #include "../Graphics/Graphics.h"
+#include "../GraphicsAPI/GraphicsDefs.h"
 #include "../Graphics/Material.h"
 #include "../Graphics/Renderer.h"
 #include "../Graphics/Technique.h"
@@ -514,7 +515,7 @@ void Batch::Prepare(View* view, Camera* camera, bool setModelTransform, bool all
                     }
 
                     normalOffsetScale *= light->GetShadowBias().normalOffset_;
-#ifdef GL_ES_VERSION_2_0
+#ifdef MOBILE_GRAPHICS
                     normalOffsetScale *= renderer->GetMobileNormalOffsetMul();
 #endif
                     graphics->SetShaderParameter(VSP_NORMALOFFSETSCALE, normalOffsetScale);
@@ -575,11 +576,11 @@ void Batch::Prepare(View* view, Camera* camera, bool setModelTransform, bool all
     }
 
     // Set zone texture if necessary
-#ifndef GL_ES_VERSION_2_0
+#ifndef URHO3D_GLES2
     if (zone_ && graphics->HasTextureUnit(TU_ZONE))
         graphics->SetTexture(TU_ZONE, zone_->GetZoneTexture());
 #else
-    // On OpenGL ES set the zone texture to the environment unit instead
+    // On OpenGL ES2 set the zone texture to the environment unit instead
     if (zone_ && zone_->GetZoneTexture() && graphics->HasTextureUnit(TU_ENVIRONMENT))
         graphics->SetTexture(TU_ENVIRONMENT, zone_->GetZoneTexture());
 #endif
@@ -775,7 +776,7 @@ void BatchQueue::SortFrontToBack2Pass(Vector<Batch*>& batches)
 {
     // Mobile devices likely use a tiled deferred approach, with which front-to-back sorting is irrelevant. The 2-pass
     // method is also time consuming, so just sort with state having priority
-#ifdef GL_ES_VERSION_2_0
+#ifdef MOBILE_GRAPHICS
     Sort(batches.Begin(), batches.End(), CompareBatchesState);
 #else
     // For desktop, first sort by distance and remap shader/material/geometry IDs in the sort key

+ 10 - 1
Source/Urho3D/Graphics/Graphics.h

@@ -658,6 +658,10 @@ public:
 
     /// Bind texture unit 0 for update. Called by Texture. Used only on OpenGL.
     void SetTextureForUpdate_OGL(Texture* texture);
+    /// Get Renderer name. Used on OpenGL
+    const String& GetRendererName() const { return rendererName_; }
+    /// Get Version string. Used on OpenGL
+    const String& GetVersionString() const { return versionString_; }
 #endif // def URHO3D_OPENGL
 
     /// Maximize the window.
@@ -1183,7 +1187,12 @@ private:
     String orientations_;
     /// Graphics API name.
     String apiName_;
-
+#ifdef URHO3D_OPENGL
+    /// Renderer name (usually GPU name)
+    String rendererName_;
+    /// Version of GL drivers
+    String versionString_;
+#endif
     /// OpenGL3 support flag.
     inline static bool gl3Support;
     /// Used graphics API.

+ 5 - 5
Source/Urho3D/Graphics/Material.cpp

@@ -38,7 +38,7 @@ static const char* textureUnitNames[] =
     "specular",
     "emissive",
     "environment",
-#ifdef DESKTOP_GRAPHICS
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
     "volume",
     "custom1",
     "custom2",
@@ -276,7 +276,7 @@ bool Material::BeginLoadXML(Deserializer& source)
                 // Detect cube maps and arrays by file extension: they are defined by an XML file
                 if (GetExtension(name) == ".xml")
                 {
-#ifdef DESKTOP_GRAPHICS
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
                     StringHash type = ParseTextureTypeXml(cache, name);
                     if (!type && textureElem.HasAttribute("unit"))
                     {
@@ -335,7 +335,7 @@ bool Material::BeginLoadJSON(Deserializer& source)
                 // Detect cube maps and arrays by file extension: they are defined by an XML file
                 if (GetExtension(name) == ".xml")
                 {
-#ifdef DESKTOP_GRAPHICS
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
                     StringHash type = ParseTextureTypeXml(cache, name);
                     if (!type && !unitString.Empty())
                     {
@@ -427,7 +427,7 @@ bool Material::Load(const XMLElement& source)
             // Detect cube maps and arrays by file extension: they are defined by an XML file
             if (GetExtension(name) == ".xml")
             {
-#ifdef DESKTOP_GRAPHICS
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
                 StringHash type = ParseTextureTypeXml(cache, name);
                 if (!type && unit == TU_VOLUMEMAP)
                     type = Texture3D::GetTypeStatic();
@@ -583,7 +583,7 @@ bool Material::Load(const JSONValue& source)
             // Detect cube maps and arrays by file extension: they are defined by an XML file
             if (GetExtension(textureName) == ".xml")
             {
-#ifdef DESKTOP_GRAPHICS
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
                 StringHash type = ParseTextureTypeXml(cache, textureName);
                 if (!type && unit == TU_VOLUMEMAP)
                     type = Texture3D::GetTypeStatic();

+ 2 - 2
Source/Urho3D/Graphics/Renderer.cpp

@@ -943,7 +943,7 @@ Texture2D* Renderer::GetShadowMap(Light* light, Camera* camera, i32 viewWidth, i
         }
         else
         {
-#ifndef GL_ES_VERSION_2_0
+#ifndef URHO3D_GLES2
             // OpenGL (desktop) and D3D11: shadow compare mode needs to be specifically enabled for the shadow map
             newShadowMap->SetFilterMode(FILTER_BILINEAR);
             newShadowMap->SetShadowCompare(shadowMapUsage == TEXTURE_DEPTHSTENCIL);
@@ -1808,7 +1808,7 @@ void Renderer::CreateGeometries()
     pointLightGeometry_->SetIndexBuffer(plib);
     pointLightGeometry_->SetDrawRange(TRIANGLE_LIST, 0, plib->GetIndexCount());
 
-#if !defined(GL_ES_VERSION_2_0)
+#if !defined(URHO3D_GLES2)
     if (graphics_->GetShadowMapFormat())
     {
         faceSelectCubeMap_ = new TextureCube(context_);

+ 8 - 7
Source/Urho3D/Graphics/View.cpp

@@ -10,6 +10,7 @@
 #include "../Graphics/Geometry.h"
 #include "../Graphics/Graphics.h"
 #include "../Graphics/GraphicsEvents.h"
+#include "../GraphicsAPI/GraphicsDefs.h"
 #include "../Graphics/Material.h"
 #include "../Graphics/OcclusionBuffer.h"
 #include "../Graphics/Octree.h"
@@ -356,7 +357,7 @@ bool View::Define(RenderSurface* renderTarget, Viewport* viewport)
 
     if (Graphics::GetGAPI() == GAPI_OPENGL)
     {
-#ifdef GL_ES_VERSION_2_0
+#if defined(URHO3D_GLES2)
         // On OpenGL ES we assume a stencil is not available or would not give a good performance, and disable light stencil
         // optimizations in any case
         noStencil_ = true;
@@ -565,7 +566,7 @@ void View::Render()
         camera_->SetAspectRatioInternal((float)(viewSize_.x_) / (float)(viewSize_.y_));
 
     // Bind the face selection and indirection cube maps for point light shadows
-#ifndef GL_ES_VERSION_2_0
+#ifndef URHO3D_GLES2
     if (renderer_->GetDrawShadows())
     {
         graphics_->SetTexture(TU_FACESELECT, renderer_->GetFaceSelectCubeMap());
@@ -1787,7 +1788,7 @@ bool View::SetTextures(RenderPathCommand& command)
             continue;
         }
 
-#ifdef DESKTOP_GRAPHICS
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
         Texture* texture = FindNamedTexture(command.textureNames_[i], false, i == TU_VOLUMEMAP);
 #else
         Texture* texture = FindNamedTexture(command.textureNames_[i], false, false);
@@ -2004,7 +2005,7 @@ void View::AllocateScreenBuffers()
 
         // If OpenGL ES, use substitute target to avoid resolve from the backbuffer, which may be slow. However if multisampling
         // is specified, there is no choice
-#ifdef GL_ES_VERSION_2_0
+#ifdef URHO3D_GLES2
         if (!renderTarget_ && graphics_->GetMultiSample() < 2)
             needSubstitute = true;
 #endif
@@ -2263,7 +2264,7 @@ void View::ProcessLight(LightQueryResult& query, i32 threadIndex)
     if (isShadowed && light->GetShadowDistance() > 0.0f && light->GetDistance() > light->GetShadowDistance())
         isShadowed = false;
     // OpenGL ES can not support point light shadows
-#ifdef GL_ES_VERSION_2_0
+#if defined(URHO3D_GLES2)
     if (isShadowed && type == LIGHT_POINT)
         isShadowed = false;
 #endif
@@ -3095,7 +3096,7 @@ void View::RenderShadowMap(const LightBatchQueue& queue)
 
         // Perform further modification of depth bias on OpenGL ES, as shadow calculations' precision is limited
         float addition = 0.0f;
-#ifdef GL_ES_VERSION_2_0
+#ifdef MOBILE_GRAPHICS
         multiplier *= renderer_->GetMobileShadowBiasMul();
         addition = renderer_->GetMobileShadowBiasAdd();
 #endif
@@ -3189,7 +3190,7 @@ Texture* View::FindNamedTexture(const String& name, bool isRenderTarget, bool is
         if (GetExtension(name) == ".xml")
         {
             // Assume 3D textures are only bound to the volume map unit, otherwise it's a cube texture
-#ifdef DESKTOP_GRAPHICS
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
             StringHash type = ParseTextureTypeXml(cache, name);
             if (!type && isVolumeMap)
                 type = Texture3D::GetTypeStatic();

+ 374 - 369
Source/Urho3D/GraphicsAPI/GraphicsDefs.h

@@ -1,27 +1,27 @@
 // Copyright (c) 2008-2022 the Urho3D project
 // License: MIT
-
-/// \file
-
-#pragma once
-
-#include "../Container/FlagSet.h"
-#include "../Container/HashBase.h"
-#include "../Math/StringHash.h"
+
+/// \file
+
+#pragma once
+
+#include "../Container/FlagSet.h"
+#include "../Container/HashBase.h"
+#include "../Math/StringHash.h"
 #include "../Math/Vector3.h"
-
-namespace Urho3D
-{
-
-class Vector3;
-
+
+namespace Urho3D
+{
+
+class Vector3;
+
 // Graphics capability support level. Web platform (Emscripten) also uses OpenGL ES, but is considered a desktop platform capability-wise
-#if defined(IOS) || defined(TVOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
-#define MOBILE_GRAPHICS
-#else
-#define DESKTOP_GRAPHICS
-#endif
-
+#if defined(IOS) || defined(TVOS) || defined(__ANDROID__) || defined(__arm__) || defined(__aarch64__)
+#define MOBILE_GRAPHICS
+#else
+#define DESKTOP_GRAPHICS
+#endif
+
 enum GAPI
 {
     GAPI_NONE = 0,
@@ -29,343 +29,348 @@ enum GAPI
     GAPI_D3D11
 };
 
-/// Primitive type.
-enum PrimitiveType
-{
-    TRIANGLE_LIST = 0,
-    LINE_LIST,
-    POINT_LIST,
-    TRIANGLE_STRIP,
-    LINE_STRIP,
-    TRIANGLE_FAN
-};
-
-/// %Geometry type for vertex shader geometry variations.
-enum GeometryType
-{
-    GEOM_STATIC = 0,
-    GEOM_SKINNED = 1,
-    GEOM_INSTANCED = 2,
-    GEOM_BILLBOARD = 3,
-    GEOM_DIRBILLBOARD = 4,
-    GEOM_TRAIL_FACE_CAMERA = 5,
-    GEOM_TRAIL_BONE = 6,
-    MAX_GEOMETRYTYPES = 7,
-    // This is not a real geometry type for VS, but used to mark objects that do not desire to be instanced
-    GEOM_STATIC_NOINSTANCING = 7,
-};
-
-/// Blending mode.
-enum BlendMode
-{
-    BLEND_REPLACE = 0,
-    BLEND_ADD,
-    BLEND_MULTIPLY,
-    BLEND_ALPHA,
-    BLEND_ADDALPHA,
-    BLEND_PREMULALPHA,
-    BLEND_INVDESTALPHA,
-    BLEND_SUBTRACT,
-    BLEND_SUBTRACTALPHA,
-    MAX_BLENDMODES
-};
-
-/// Depth or stencil compare mode.
-enum CompareMode
-{
-    CMP_ALWAYS = 0,
-    CMP_EQUAL,
-    CMP_NOTEQUAL,
-    CMP_LESS,
-    CMP_LESSEQUAL,
-    CMP_GREATER,
-    CMP_GREATEREQUAL,
-    MAX_COMPAREMODES
-};
-
-/// Culling mode.
-enum CullMode
-{
-    CULL_NONE = 0,
-    CULL_CCW,
-    CULL_CW,
-    MAX_CULLMODES
-};
-
-/// Fill mode.
-enum FillMode
-{
-    FILL_SOLID = 0,
-    FILL_WIREFRAME,
-    FILL_POINT
-};
-
-/// Stencil operation.
-enum StencilOp
-{
-    OP_KEEP = 0,
-    OP_ZERO,
-    OP_REF,
-    OP_INCR,
-    OP_DECR
-};
-
-/// Vertex/index buffer lock state.
-enum LockState
-{
-    LOCK_NONE = 0,
-    LOCK_HARDWARE,
-    LOCK_SHADOW,
-    LOCK_SCRATCH
-};
-
-/// Hardcoded legacy vertex elements.
-enum LegacyVertexElement
-{
-    ELEMENT_POSITION = 0,
-    ELEMENT_NORMAL,
-    ELEMENT_COLOR,
-    ELEMENT_TEXCOORD1,
-    ELEMENT_TEXCOORD2,
-    ELEMENT_CUBETEXCOORD1,
-    ELEMENT_CUBETEXCOORD2,
-    ELEMENT_TANGENT,
-    ELEMENT_BLENDWEIGHTS,
-    ELEMENT_BLENDINDICES,
-    ELEMENT_INSTANCEMATRIX1,
-    ELEMENT_INSTANCEMATRIX2,
-    ELEMENT_INSTANCEMATRIX3,
-    // Custom 32-bit integer object index. Due to API limitations, not supported on D3D9
-    ELEMENT_OBJECTINDEX,
-    MAX_LEGACY_VERTEX_ELEMENTS
-};
-
-/// Arbitrary vertex declaration element datatypes.
-enum VertexElementType
-{
-    TYPE_INT = 0,
-    TYPE_FLOAT,
-    TYPE_VECTOR2,
-    TYPE_VECTOR3,
-    TYPE_VECTOR4,
-    TYPE_UBYTE4,
-    TYPE_UBYTE4_NORM,
-    MAX_VERTEX_ELEMENT_TYPES
-};
-
-/// Arbitrary vertex declaration element semantics.
-enum VertexElementSemantic
-{
-    SEM_POSITION = 0,
-    SEM_NORMAL,
-    SEM_BINORMAL,
-    SEM_TANGENT,
-    SEM_TEXCOORD,
-    SEM_COLOR,
-    SEM_BLENDWEIGHTS,
-    SEM_BLENDINDICES,
-    SEM_OBJECTINDEX,
-    MAX_VERTEX_ELEMENT_SEMANTICS
-};
-
-/// Vertex element description for arbitrary vertex declarations.
-struct URHO3D_API VertexElement
-{
-    /// Default-construct.
-    VertexElement() noexcept :
-        type_(TYPE_VECTOR3),
-        semantic_(SEM_POSITION),
-        index_(0),
-        perInstance_(false),
-        offset_(0)
-    {
-    }
-
-    /// Construct with type, semantic, index and whether is per-instance data.
+#if defined(DESKTOP_GRAPHICS) || defined(URHO3D_GLES3)
+#define DESKTOP_GRAPHICS_OR_GLES3
+#endif
+
+
+/// Primitive type.
+enum PrimitiveType
+{
+    TRIANGLE_LIST = 0,
+    LINE_LIST,
+    POINT_LIST,
+    TRIANGLE_STRIP,
+    LINE_STRIP,
+    TRIANGLE_FAN
+};
+
+/// %Geometry type for vertex shader geometry variations.
+enum GeometryType
+{
+    GEOM_STATIC = 0,
+    GEOM_SKINNED = 1,
+    GEOM_INSTANCED = 2,
+    GEOM_BILLBOARD = 3,
+    GEOM_DIRBILLBOARD = 4,
+    GEOM_TRAIL_FACE_CAMERA = 5,
+    GEOM_TRAIL_BONE = 6,
+    MAX_GEOMETRYTYPES = 7,
+    // This is not a real geometry type for VS, but used to mark objects that do not desire to be instanced
+    GEOM_STATIC_NOINSTANCING = 7,
+};
+
+/// Blending mode.
+enum BlendMode
+{
+    BLEND_REPLACE = 0,
+    BLEND_ADD,
+    BLEND_MULTIPLY,
+    BLEND_ALPHA,
+    BLEND_ADDALPHA,
+    BLEND_PREMULALPHA,
+    BLEND_INVDESTALPHA,
+    BLEND_SUBTRACT,
+    BLEND_SUBTRACTALPHA,
+    MAX_BLENDMODES
+};
+
+/// Depth or stencil compare mode.
+enum CompareMode
+{
+    CMP_ALWAYS = 0,
+    CMP_EQUAL,
+    CMP_NOTEQUAL,
+    CMP_LESS,
+    CMP_LESSEQUAL,
+    CMP_GREATER,
+    CMP_GREATEREQUAL,
+    MAX_COMPAREMODES
+};
+
+/// Culling mode.
+enum CullMode
+{
+    CULL_NONE = 0,
+    CULL_CCW,
+    CULL_CW,
+    MAX_CULLMODES
+};
+
+/// Fill mode.
+enum FillMode
+{
+    FILL_SOLID = 0,
+    FILL_WIREFRAME,
+    FILL_POINT
+};
+
+/// Stencil operation.
+enum StencilOp
+{
+    OP_KEEP = 0,
+    OP_ZERO,
+    OP_REF,
+    OP_INCR,
+    OP_DECR
+};
+
+/// Vertex/index buffer lock state.
+enum LockState
+{
+    LOCK_NONE = 0,
+    LOCK_HARDWARE,
+    LOCK_SHADOW,
+    LOCK_SCRATCH
+};
+
+/// Hardcoded legacy vertex elements.
+enum LegacyVertexElement
+{
+    ELEMENT_POSITION = 0,
+    ELEMENT_NORMAL,
+    ELEMENT_COLOR,
+    ELEMENT_TEXCOORD1,
+    ELEMENT_TEXCOORD2,
+    ELEMENT_CUBETEXCOORD1,
+    ELEMENT_CUBETEXCOORD2,
+    ELEMENT_TANGENT,
+    ELEMENT_BLENDWEIGHTS,
+    ELEMENT_BLENDINDICES,
+    ELEMENT_INSTANCEMATRIX1,
+    ELEMENT_INSTANCEMATRIX2,
+    ELEMENT_INSTANCEMATRIX3,
+    // Custom 32-bit integer object index. Due to API limitations, not supported on D3D9
+    ELEMENT_OBJECTINDEX,
+    MAX_LEGACY_VERTEX_ELEMENTS
+};
+
+/// Arbitrary vertex declaration element datatypes.
+enum VertexElementType
+{
+    TYPE_INT = 0,
+    TYPE_FLOAT,
+    TYPE_VECTOR2,
+    TYPE_VECTOR3,
+    TYPE_VECTOR4,
+    TYPE_UBYTE4,
+    TYPE_UBYTE4_NORM,
+    MAX_VERTEX_ELEMENT_TYPES
+};
+
+/// Arbitrary vertex declaration element semantics.
+enum VertexElementSemantic
+{
+    SEM_POSITION = 0,
+    SEM_NORMAL,
+    SEM_BINORMAL,
+    SEM_TANGENT,
+    SEM_TEXCOORD,
+    SEM_COLOR,
+    SEM_BLENDWEIGHTS,
+    SEM_BLENDINDICES,
+    SEM_OBJECTINDEX,
+    MAX_VERTEX_ELEMENT_SEMANTICS
+};
+
+/// Vertex element description for arbitrary vertex declarations.
+struct URHO3D_API VertexElement
+{
+    /// Default-construct.
+    VertexElement() noexcept :
+        type_(TYPE_VECTOR3),
+        semantic_(SEM_POSITION),
+        index_(0),
+        perInstance_(false),
+        offset_(0)
+    {
+    }
+
+    /// Construct with type, semantic, index and whether is per-instance data.
     VertexElement(VertexElementType type, VertexElementSemantic semantic, i8 index = 0, bool perInstance = false) noexcept :
-        type_(type),
-        semantic_(semantic),
-        index_(index),
-        perInstance_(perInstance),
-        offset_(0)
-    {
-    }
-
-    /// Test for equality with another vertex element. Offset is intentionally not compared, as it's relevant only when an element exists within a vertex buffer.
+        type_(type),
+        semantic_(semantic),
+        index_(index),
+        perInstance_(perInstance),
+        offset_(0)
+    {
+    }
+
+    /// Test for equality with another vertex element. Offset is intentionally not compared, as it's relevant only when an element exists within a vertex buffer.
     bool operator ==(const VertexElement& rhs) const
     {
         return type_ == rhs.type_ && semantic_ == rhs.semantic_ && index_ == rhs.index_ && perInstance_ == rhs.perInstance_;
     }
-
-    /// Test for inequality with another vertex element.
-    bool operator !=(const VertexElement& rhs) const { return !(*this == rhs); }
-
-    /// Data type of element.
-    VertexElementType type_;
-    /// Semantic of element.
-    VertexElementSemantic semantic_;
-    /// Semantic index of element, for example multi-texcoords.
+
+    /// Test for inequality with another vertex element.
+    bool operator !=(const VertexElement& rhs) const { return !(*this == rhs); }
+
+    /// Data type of element.
+    VertexElementType type_;
+    /// Semantic of element.
+    VertexElementSemantic semantic_;
+    /// Semantic index of element, for example multi-texcoords.
     i8 index_;
-    /// Per-instance flag.
-    bool perInstance_;
-    /// Offset of element from vertex start. Filled by VertexBuffer once the vertex declaration is built.
+    /// Per-instance flag.
+    bool perInstance_;
+    /// Offset of element from vertex start. Filled by VertexBuffer once the vertex declaration is built.
     i32 offset_;
-};
-
-/// Sizes of vertex element types.
+};
+
+/// Sizes of vertex element types.
 extern URHO3D_API const i32 ELEMENT_TYPESIZES[];
-
-/// Vertex element definitions for the legacy elements.
-extern URHO3D_API const VertexElement LEGACY_VERTEXELEMENTS[];
-
-/// Texture filtering mode.
-enum TextureFilterMode
-{
-    FILTER_NEAREST = 0,
-    FILTER_BILINEAR,
-    FILTER_TRILINEAR,
-    FILTER_ANISOTROPIC,
-    FILTER_NEAREST_ANISOTROPIC,
-    FILTER_DEFAULT,
-    MAX_FILTERMODES
-};
-
-/// Texture addressing mode.
-enum TextureAddressMode
-{
-    ADDRESS_WRAP = 0,
-    ADDRESS_MIRROR,
-    ADDRESS_CLAMP,
-    ADDRESS_BORDER,
-    MAX_ADDRESSMODES
-};
-
-/// Texture coordinates.
-enum TextureCoordinate
-{
-    COORD_U = 0,
-    COORD_V,
-    COORD_W,
-    MAX_COORDS
-};
-
-/// Texture usage types.
-enum TextureUsage
-{
-    TEXTURE_STATIC = 0,
-    TEXTURE_DYNAMIC,
-    TEXTURE_RENDERTARGET,
-    TEXTURE_DEPTHSTENCIL
-};
-
-/// Cube map faces.
-enum CubeMapFace
-{
-    FACE_POSITIVE_X = 0,
-    FACE_NEGATIVE_X,
-    FACE_POSITIVE_Y,
-    FACE_NEGATIVE_Y,
-    FACE_POSITIVE_Z,
-    FACE_NEGATIVE_Z,
-    MAX_CUBEMAP_FACES
-};
-
-/// Cubemap single image layout modes.
-enum CubeMapLayout
-{
-    CML_HORIZONTAL = 0,
-    CML_HORIZONTALNVIDIA,
-    CML_HORIZONTALCROSS,
-    CML_VERTICALCROSS,
-    CML_BLENDER
-};
-
-/// Update mode for render surface viewports.
-enum RenderSurfaceUpdateMode
-{
-    SURFACE_MANUALUPDATE = 0,
-    SURFACE_UPDATEVISIBLE,
-    SURFACE_UPDATEALWAYS
-};
-
-/// Shader types.
-enum ShaderType
-{
-    VS = 0,
-    PS,
-};
-
-/// Shader parameter groups for determining need to update. On APIs that support constant buffers, these correspond to different constant buffers.
-enum ShaderParameterGroup
-{
-    SP_FRAME = 0,
-    SP_CAMERA,
-    SP_ZONE,
-    SP_LIGHT,
-    SP_MATERIAL,
-    SP_OBJECT,
-    SP_CUSTOM,
-    MAX_SHADER_PARAMETER_GROUPS
-};
-
-/// Texture units.
+
+/// Vertex element definitions for the legacy elements.
+extern URHO3D_API const VertexElement LEGACY_VERTEXELEMENTS[];
+
+/// Texture filtering mode.
+enum TextureFilterMode
+{
+    FILTER_NEAREST = 0,
+    FILTER_BILINEAR,
+    FILTER_TRILINEAR,
+    FILTER_ANISOTROPIC,
+    FILTER_NEAREST_ANISOTROPIC,
+    FILTER_DEFAULT,
+    MAX_FILTERMODES
+};
+
+/// Texture addressing mode.
+enum TextureAddressMode
+{
+    ADDRESS_WRAP = 0,
+    ADDRESS_MIRROR,
+    ADDRESS_CLAMP,
+    ADDRESS_BORDER,
+    MAX_ADDRESSMODES
+};
+
+/// Texture coordinates.
+enum TextureCoordinate
+{
+    COORD_U = 0,
+    COORD_V,
+    COORD_W,
+    MAX_COORDS
+};
+
+/// Texture usage types.
+enum TextureUsage
+{
+    TEXTURE_STATIC = 0,
+    TEXTURE_DYNAMIC,
+    TEXTURE_RENDERTARGET,
+    TEXTURE_DEPTHSTENCIL
+};
+
+/// Cube map faces.
+enum CubeMapFace
+{
+    FACE_POSITIVE_X = 0,
+    FACE_NEGATIVE_X,
+    FACE_POSITIVE_Y,
+    FACE_NEGATIVE_Y,
+    FACE_POSITIVE_Z,
+    FACE_NEGATIVE_Z,
+    MAX_CUBEMAP_FACES
+};
+
+/// Cubemap single image layout modes.
+enum CubeMapLayout
+{
+    CML_HORIZONTAL = 0,
+    CML_HORIZONTALNVIDIA,
+    CML_HORIZONTALCROSS,
+    CML_VERTICALCROSS,
+    CML_BLENDER
+};
+
+/// Update mode for render surface viewports.
+enum RenderSurfaceUpdateMode
+{
+    SURFACE_MANUALUPDATE = 0,
+    SURFACE_UPDATEVISIBLE,
+    SURFACE_UPDATEALWAYS
+};
+
+/// Shader types.
+enum ShaderType
+{
+    VS = 0,
+    PS,
+};
+
+/// Shader parameter groups for determining need to update. On APIs that support constant buffers, these correspond to different constant buffers.
+enum ShaderParameterGroup
+{
+    SP_FRAME = 0,
+    SP_CAMERA,
+    SP_ZONE,
+    SP_LIGHT,
+    SP_MATERIAL,
+    SP_OBJECT,
+    SP_CUSTOM,
+    MAX_SHADER_PARAMETER_GROUPS
+};
+
+/// Texture units.
 /// @manualbind
-enum TextureUnit
-{
-    TU_DIFFUSE = 0,
-    TU_ALBEDOBUFFER = 0,
-    TU_NORMAL = 1,
-    TU_NORMALBUFFER = 1,
-    TU_SPECULAR = 2,
-    TU_EMISSIVE = 3,
-    TU_ENVIRONMENT = 4,
-#ifdef DESKTOP_GRAPHICS
-    TU_VOLUMEMAP = 5,
-    TU_CUSTOM1 = 6,
-    TU_CUSTOM2 = 7,
-    TU_LIGHTRAMP = 8,
-    TU_LIGHTSHAPE = 9,
-    TU_SHADOWMAP = 10,
-    TU_FACESELECT = 11,
-    TU_INDIRECTION = 12,
-    TU_DEPTHBUFFER = 13,
-    TU_LIGHTBUFFER = 14,
-    TU_ZONE = 15,
-    MAX_MATERIAL_TEXTURE_UNITS = 8,
-    MAX_TEXTURE_UNITS = 16
-#else
-    TU_LIGHTRAMP = 5,
-    TU_LIGHTSHAPE = 6,
-    TU_SHADOWMAP = 7,
-    MAX_MATERIAL_TEXTURE_UNITS = 5,
-    MAX_TEXTURE_UNITS = 8
-#endif
-};
-
-/// Billboard camera facing modes.
-enum FaceCameraMode
-{
-    FC_NONE = 0,
-    FC_ROTATE_XYZ,
-    FC_ROTATE_Y,
-    FC_LOOKAT_XYZ,
-    FC_LOOKAT_Y,
-    FC_LOOKAT_MIXED,
-    FC_DIRECTION,
-};
-
-/// Shadow type.
-enum ShadowQuality
-{
-    SHADOWQUALITY_SIMPLE_16BIT = 0,
-    SHADOWQUALITY_SIMPLE_24BIT,
-    SHADOWQUALITY_PCF_16BIT,
-    SHADOWQUALITY_PCF_24BIT,
-    SHADOWQUALITY_VSM,
-    SHADOWQUALITY_BLUR_VSM
-};
-
-// Inbuilt shader parameters.
+enum TextureUnit
+{
+    TU_DIFFUSE = 0,
+    TU_ALBEDOBUFFER = 0,
+    TU_NORMAL = 1,
+    TU_NORMALBUFFER = 1,
+    TU_SPECULAR = 2,
+    TU_EMISSIVE = 3,
+    TU_ENVIRONMENT = 4,
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
+    TU_VOLUMEMAP = 5,
+    TU_CUSTOM1 = 6,
+    TU_CUSTOM2 = 7,
+    TU_LIGHTRAMP = 8,
+    TU_LIGHTSHAPE = 9,
+    TU_SHADOWMAP = 10,
+    TU_FACESELECT = 11,
+    TU_INDIRECTION = 12,
+    TU_DEPTHBUFFER = 13,
+    TU_LIGHTBUFFER = 14,
+    TU_ZONE = 15,
+    MAX_MATERIAL_TEXTURE_UNITS = 8,
+    MAX_TEXTURE_UNITS = 16
+#else
+    TU_LIGHTRAMP = 5,
+    TU_LIGHTSHAPE = 6,
+    TU_SHADOWMAP = 7,
+    MAX_MATERIAL_TEXTURE_UNITS = 5,
+    MAX_TEXTURE_UNITS = 8
+#endif
+};
+
+/// Billboard camera facing modes.
+enum FaceCameraMode
+{
+    FC_NONE = 0,
+    FC_ROTATE_XYZ,
+    FC_ROTATE_Y,
+    FC_LOOKAT_XYZ,
+    FC_LOOKAT_Y,
+    FC_LOOKAT_MIXED,
+    FC_DIRECTION,
+};
+
+/// Shadow type.
+enum ShadowQuality
+{
+    SHADOWQUALITY_SIMPLE_16BIT = 0,
+    SHADOWQUALITY_SIMPLE_24BIT,
+    SHADOWQUALITY_PCF_16BIT,
+    SHADOWQUALITY_PCF_24BIT,
+    SHADOWQUALITY_VSM,
+    SHADOWQUALITY_BLUR_VSM
+};
+
+// Inbuilt shader parameters.
 inline const StringHash VSP_AMBIENTENDCOLOR{"AmbientEndColor"};
 inline const StringHash VSP_AMBIENTSTARTCOLOR{"AmbientStartColor"};
 inline const StringHash VSP_BILLBOARDROT{"BillboardRot"};
@@ -404,7 +409,7 @@ inline const StringHash PSP_SHADOWSPLITS{"ShadowSplits"};
 inline const StringHash PSP_VSMSHADOWPARAMS{"VSMShadowParams"};
 inline const StringHash PSP_ZONEMAX{"ZoneMax"};
 inline const StringHash PSP_ZONEMIN{"ZoneMin"};
-
+
 inline const StringHash VSP_CAMERAPOS{"CameraPos"};
 inline const StringHash PSP_CAMERAPOS{"CameraPosPS"};
 
@@ -432,28 +437,28 @@ inline const StringHash PSP_NEARCLIP{"NearClipPS"};
 inline const StringHash VSP_NORMALOFFSETSCALE{"NormalOffsetScale"};
 inline const StringHash PSP_NORMALOFFSETSCALE{"NormalOffsetScalePS"};
 
-// Scale calculation from bounding box diagonal.
+// Scale calculation from bounding box diagonal.
 inline const Vector3 DOT_SCALE{1 / 3.0f, 1 / 3.0f, 1 / 3.0f};
-
+
 enum MaterialQuality : u32
-{
-    QUALITY_LOW = 0,
-    QUALITY_MEDIUM = 1,
-    QUALITY_HIGH = 2,
-    QUALITY_MAX = 15,
-};
-
+{
+    QUALITY_LOW = 0,
+    QUALITY_MEDIUM = 1,
+    QUALITY_HIGH = 2,
+    QUALITY_MAX = 15,
+};
+
 enum ClearTarget : u32
-{
-    CLEAR_COLOR = 0x1,
-    CLEAR_DEPTH = 0x2,
-    CLEAR_STENCIL = 0x4,
-};
-URHO3D_FLAGSET(ClearTarget, ClearTargetFlags);
-
+{
+    CLEAR_COLOR = 0x1,
+    CLEAR_DEPTH = 0x2,
+    CLEAR_STENCIL = 0x4,
+};
+URHO3D_FLAGSET(ClearTarget, ClearTargetFlags);
+
 /// Legacy vertex element bitmasks.
 enum class VertexElements : u32
-{
+{
     None            = 0,
     Position        = 1 << 0,
     Normal          = 1 << 1,
@@ -469,13 +474,13 @@ enum class VertexElements : u32
     InstanceMatrix2 = 1 << 11,
     InstanceMatrix3 = 1 << 12,
     ObjectIndex     = 1 << 13
-};
+};
 URHO3D_FLAGS(VertexElements);
-
+
 inline constexpr i32 MAX_RENDERTARGETS = 4;
 inline constexpr i32 MAX_VERTEX_STREAMS = 4;
 inline constexpr i32 MAX_CONSTANT_REGISTERS = 256;
-
+
 inline constexpr i32 BITS_PER_COMPONENT = 8;
 
 } // namespace Urho3D

+ 3485 - 3407
Source/Urho3D/GraphicsAPI/OpenGL/OGLGraphics.cpp

@@ -1,3419 +1,3497 @@
-// Copyright (c) 2008-2022 the Urho3D project
-// License: MIT
-
-#include "../../Precompiled.h"
-
-#include "../../Core/Context.h"
-#include "../../Core/Mutex.h"
-#include "../../Core/ProcessUtils.h"
-#include "../../Core/Profiler.h"
-#include "../../Graphics/Graphics.h"
-#include "../../Graphics/GraphicsEvents.h"
-#include "../../GraphicsAPI/ConstantBuffer.h"
-#include "../../GraphicsAPI/IndexBuffer.h"
-#include "../../GraphicsAPI/OpenGL/OGLGraphicsImpl.h"
-#include "../../GraphicsAPI/OpenGL/OGLShaderProgram.h"
-#include "../../GraphicsAPI/RenderSurface.h"
-#include "../../GraphicsAPI/Shader.h"
-#include "../../GraphicsAPI/ShaderPrecache.h"
-#include "../../GraphicsAPI/ShaderVariation.h"
-#include "../../GraphicsAPI/Texture2D.h"
-#include "../../GraphicsAPI/TextureCube.h"
-#include "../../GraphicsAPI/VertexBuffer.h"
-#include "../../IO/File.h"
-#include "../../IO/Log.h"
-#include "../../Resource/ResourceCache.h"
-
-#include <SDL/SDL.h>
-
-#include "../../DebugNew.h"
-
-#ifdef GL_ES_VERSION_2_0
-#define GL_DEPTH_COMPONENT24 GL_DEPTH_COMPONENT24_OES
-#define glClearDepth glClearDepthf
-#endif
-
-#ifdef __EMSCRIPTEN__
-#include "../../Input/Input.h"
-#include "../../UI/Cursor.h"
-#include "../../UI/UI.h"
-#include <emscripten/emscripten.h>
-#include <emscripten/bind.h>
-
-// Emscripten provides even all GL extension functions via static linking. However there is
-// no GLES2-specific extension header at the moment to include instanced rendering declarations,
-// so declare them manually from GLES3 gl2ext.h. Emscripten will provide these when linking final output.
-extern "C"
-{
-    GL_APICALL void GL_APIENTRY glDrawArraysInstancedANGLE (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
-    GL_APICALL void GL_APIENTRY glDrawElementsInstancedANGLE (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount);
-    GL_APICALL void GL_APIENTRY glVertexAttribDivisorANGLE (GLuint index, GLuint divisor);
-}
-
-// Helper functions to support emscripten canvas resolution change
-static const Urho3D::Context *appContext;
-
-static void JSCanvasSize(int width, int height, bool fullscreen, float scale)
-{
-    URHO3D_LOGINFOF("JSCanvasSize: width=%d height=%d fullscreen=%d ui scale=%f", width, height, fullscreen, scale);
-
-    using namespace Urho3D;
-
-    if (appContext)
-    {
-        bool uiCursorVisible = false;
-        bool systemCursorVisible = false;
-        MouseMode mouseMode{};
-
-        // Detect current system pointer state
-        Input* input = appContext->GetSubsystem<Input>();
-        if (input)
-        {
-            systemCursorVisible = input->IsMouseVisible();
-            mouseMode = input->GetMouseMode();
-        }
-
-        UI* ui = appContext->GetSubsystem<UI>();
-        if (ui)
-        {
-            ui->SetScale(scale);
-
-            // Detect current UI pointer state
-            Cursor* cursor = ui->GetCursor();
-            if (cursor)
-                uiCursorVisible = cursor->IsVisible();
-        }
-
-        // Apply new resolution
-        appContext->GetSubsystem<Graphics>()->SetMode(width, height);
-
-        // Reset the pointer state as it was before resolution change
-        if (input)
-        {
-            if (uiCursorVisible)
-                input->SetMouseVisible(false);
-            else
-                input->SetMouseVisible(systemCursorVisible);
-
-            input->SetMouseMode(mouseMode);
-        }
-
-        if (ui)
-        {
-            Cursor* cursor = ui->GetCursor();
-            if (cursor)
-            {
-                cursor->SetVisible(uiCursorVisible);
-
-                IntVector2 pos = input->GetMousePosition();
-                pos = ui->ConvertSystemToUI(pos);
-
-                cursor->SetPosition(pos);
-            }
-        }
-    }
-}
-
-using namespace emscripten;
-EMSCRIPTEN_BINDINGS(Module) {
-    function("JSCanvasSize", &JSCanvasSize);
-}
-#endif
-
-namespace Urho3D
-{
-
-static const GLenum glCmpFunc[] =
-{
-    GL_ALWAYS,
-    GL_EQUAL,
-    GL_NOTEQUAL,
-    GL_LESS,
-    GL_LEQUAL,
-    GL_GREATER,
-    GL_GEQUAL
-};
-
-static const GLenum glSrcBlend[] =
-{
-    GL_ONE,
-    GL_ONE,
-    GL_DST_COLOR,
-    GL_SRC_ALPHA,
-    GL_SRC_ALPHA,
-    GL_ONE,
-    GL_ONE_MINUS_DST_ALPHA,
-    GL_ONE,
-    GL_SRC_ALPHA
-};
-
-static const GLenum glDestBlend[] =
-{
-    GL_ZERO,
-    GL_ONE,
-    GL_ZERO,
-    GL_ONE_MINUS_SRC_ALPHA,
-    GL_ONE,
-    GL_ONE_MINUS_SRC_ALPHA,
-    GL_DST_ALPHA,
-    GL_ONE,
-    GL_ONE
-};
-
-static const GLenum glBlendOp[] =
-{
-    GL_FUNC_ADD,
-    GL_FUNC_ADD,
-    GL_FUNC_ADD,
-    GL_FUNC_ADD,
-    GL_FUNC_ADD,
-    GL_FUNC_ADD,
-    GL_FUNC_ADD,
-    GL_FUNC_REVERSE_SUBTRACT,
-    GL_FUNC_REVERSE_SUBTRACT
-};
-
-#ifndef GL_ES_VERSION_2_0
-static const GLenum glFillMode[] =
-{
-    GL_FILL,
-    GL_LINE,
-    GL_POINT
-};
-
-static const GLenum glStencilOps[] =
-{
-    GL_KEEP,
-    GL_ZERO,
-    GL_REPLACE,
-    GL_INCR_WRAP,
-    GL_DECR_WRAP
-};
-#endif
-
-static const GLenum glElementTypes[] =
-{
-    GL_INT,
-    GL_FLOAT,
-    GL_FLOAT,
-    GL_FLOAT,
-    GL_FLOAT,
-    GL_UNSIGNED_BYTE,
-    GL_UNSIGNED_BYTE
-};
-
-static const GLint glElementComponents[] =
-{
-    1,
-    1,
-    2,
-    3,
-    4,
-    4,
-    4
-};
-
-#ifdef GL_ES_VERSION_2_0
-static unsigned glesDepthStencilFormat = GL_DEPTH_COMPONENT16;
-static unsigned glesReadableDepthFormat = GL_DEPTH_COMPONENT;
-#endif
-
-static String extensions;
-
-bool CheckExtension(const String& name)
-{
-    if (extensions.Empty())
-        extensions = (const char*)glGetString(GL_EXTENSIONS);
-    return extensions.Contains(name);
-}
-
-static void GetGLPrimitiveType(unsigned elementCount, PrimitiveType type, unsigned& primitiveCount, GLenum& glPrimitiveType)
-{
-    switch (type)
-    {
-    case TRIANGLE_LIST:
-        primitiveCount = elementCount / 3;
-        glPrimitiveType = GL_TRIANGLES;
-        break;
-
-    case LINE_LIST:
-        primitiveCount = elementCount / 2;
-        glPrimitiveType = GL_LINES;
-        break;
-
-    case POINT_LIST:
-        primitiveCount = elementCount;
-        glPrimitiveType = GL_POINTS;
-        break;
-
-    case TRIANGLE_STRIP:
-        primitiveCount = elementCount - 2;
-        glPrimitiveType = GL_TRIANGLE_STRIP;
-        break;
-
-    case LINE_STRIP:
-        primitiveCount = elementCount - 1;
-        glPrimitiveType = GL_LINE_STRIP;
-        break;
-
-    case TRIANGLE_FAN:
-        primitiveCount = elementCount - 2;
-        glPrimitiveType = GL_TRIANGLE_FAN;
-        break;
-    }
-}
-
-void Graphics::Constructor_OGL()
-{
-    impl_ = new GraphicsImpl_OGL();
-    position_ = IntVector2(SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED);
-    shadowMapFormat_ = GL_DEPTH_COMPONENT16;
-    hiresShadowMapFormat_ = GL_DEPTH_COMPONENT24;
-    shaderPath_ = "Shaders/GLSL/";
-    shaderExtension_ = ".glsl";
-    orientations_ = "LandscapeLeft LandscapeRight";
-#ifndef GL_ES_VERSION_2_0
-    apiName_ = "GL2";
-#else
-    apiName_ = "GLES2";
-#endif
-
-    Graphics::gl3Support = false;
-
-    SetTextureUnitMappings_OGL();
-    ResetCachedState_OGL();
-
-    context_->RequireSDL(SDL_INIT_VIDEO);
-
-    // Register Graphics library object factories
-    RegisterGraphicsLibrary(context_);
-
-#ifdef __EMSCRIPTEN__
-    appContext = context_;
-#endif
-}
-
-void Graphics::Destructor_OGL()
-{
-    Close_OGL();
-
-    delete static_cast<GraphicsImpl_OGL*>(impl_);
-    impl_ = nullptr;
-
-    context_->ReleaseSDL();
-}
-
-bool Graphics::SetScreenMode_OGL(int width, int height, const ScreenModeParams& params, bool maximize)
-{
-    URHO3D_PROFILE(SetScreenMode_OGL);
-
-    // Ensure that parameters are properly filled
-    ScreenModeParams newParams = params;
-    AdjustScreenMode(width, height, newParams, maximize);
-
-    if (IsInitialized_OGL() && width == width_ && height == height_ && screenParams_ == newParams)
-        return true;
-
-    // If only vsync changes, do not destroy/recreate the context
-    if (IsInitialized_OGL() && width == width_ && height == height_
-        && screenParams_.EqualsExceptVSync(newParams) && screenParams_.vsync_ != newParams.vsync_)
-    {
-        SDL_GL_SetSwapInterval(newParams.vsync_ ? 1 : 0);
-        screenParams_.vsync_ = newParams.vsync_;
-        return true;
-    }
-
-    // Track if the window was repositioned and don't update window position in this case
-    bool reposition = false;
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    // With an external window, only the size can change after initial setup, so do not recreate context
-    if (!externalWindow_ || !impl->context_)
-    {
-        // Close the existing window and OpenGL context, mark GPU objects as lost
-        Release_OGL(false, true);
-
-        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
-
-#ifndef GL_ES_VERSION_2_0
-        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
-        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
-        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
-
-        if (externalWindow_)
-            SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
-        else
-            SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
-
-        SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
-
-        if (!forceGL2_)
-        {
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
-        }
-        else
-        {
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
-        }
-#else
-        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
-        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-#endif
-
-        SDL_Rect display_rect;
-        SDL_GetDisplayBounds(newParams.monitor_, &display_rect);
-        reposition = newParams.fullscreen_ || (newParams.borderless_ && width >= display_rect.w && height >= display_rect.h);
-
-        const int x = reposition ? display_rect.x : position_.x_;
-        const int y = reposition ? display_rect.y : position_.y_;
-
-        unsigned flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
-        if (newParams.fullscreen_)
-            flags |= SDL_WINDOW_FULLSCREEN;
-        if (newParams.borderless_)
-            flags |= SDL_WINDOW_BORDERLESS;
-        if (newParams.resizable_)
-            flags |= SDL_WINDOW_RESIZABLE;
-
-#ifndef __EMSCRIPTEN__
-        if (newParams.highDPI_)
-            flags |= SDL_WINDOW_ALLOW_HIGHDPI;
-#endif
-
-        SDL_SetHint(SDL_HINT_ORIENTATIONS, orientations_.CString());
-
-        // Try 24-bit depth first, fallback to 16-bit
-        for (const int depthSize : { 24, 16 })
-        {
-            SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthSize);
-
-            // Try requested multisample level first, fallback to lower levels and no multisample
-            for (int multiSample = newParams.multiSample_; multiSample > 0; multiSample /= 2)
-            {
-                if (multiSample > 1)
-                {
-                    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
-                    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multiSample);
-                }
-                else
-                {
-                    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
-                    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
-                }
-
-                if (!externalWindow_)
-                    window_ = SDL_CreateWindow(windowTitle_.CString(), x, y, width, height, flags);
-                else
-                {
-    #ifndef __EMSCRIPTEN__
-                    if (!window_)
-                        window_ = SDL_CreateWindowFrom(externalWindow_, SDL_WINDOW_OPENGL);
-                    newParams.fullscreen_ = false;
-    #endif
-                }
-
-                if (window_)
-                {
-                    // TODO: We probably want to keep depthSize as well
-                    newParams.multiSample_ = multiSample;
-                    break;
-                }
-            }
-
-            if (window_)
-                break;
-        }
-
-        if (!window_)
-        {
-            URHO3D_LOGERRORF("Could not create window, root cause: '%s'", SDL_GetError());
-            return false;
-        }
-
-        // Reposition the window on the specified monitor
-        if (reposition)
-            SDL_SetWindowPosition(window_, display_rect.x, display_rect.y);
-
-        CreateWindowIcon();
-
-        if (maximize)
-        {
-            Maximize();
-            SDL_GL_GetDrawableSize(window_, &width, &height);
-        }
-
-        // Create/restore context and GPU objects and set initial renderstate
-        Restore_OGL();
-
-        // Specific error message is already logged by Restore_OGL() when context creation or OpenGL extensions check fails
-        if (!impl->context_)
-            return false;
-    }
-
-    // Set vsync
-    SDL_GL_SetSwapInterval(newParams.vsync_ ? 1 : 0);
-
-    // Store the system FBO on iOS/tvOS now
-#if defined(IOS) || defined(TVOS)
-    glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl->systemFBO_);
-#endif
-
-    screenParams_ = newParams;
-
-    SDL_GL_GetDrawableSize(window_, &width_, &height_);
-    if (!reposition)
-        SDL_GetWindowPosition(window_, &position_.x_, &position_.y_);
-
-    int logicalWidth, logicalHeight;
-    SDL_GetWindowSize(window_, &logicalWidth, &logicalHeight);
-    screenParams_.highDPI_ = (width_ != logicalWidth) || (height_ != logicalHeight);
-
-    // Reset rendertargets and viewport for the new screen mode
-    ResetRenderTargets_OGL();
-
-    // Clear the initial window contents to black
-    Clear_OGL(CLEAR_COLOR);
-    SDL_GL_SwapWindow(window_);
-
-    CheckFeatureSupport_OGL();
-
-#ifdef URHO3D_LOGGING
-    URHO3D_LOGINFOF("Adapter used %s %s", (const char *) glGetString(GL_VENDOR), (const char *) glGetString(GL_RENDERER));
-#endif
-
-    OnScreenModeChanged();
-    return true;
-}
-
-void Graphics::SetSRGB_OGL(bool enable)
-{
-    enable &= sRGBWriteSupport_;
-
-    if (enable != sRGB_)
-    {
-        sRGB_ = enable;
-        GetImpl_OGL()->fboDirty_ = true;
-    }
-}
-
-void Graphics::SetDither_OGL(bool enable)
-{
-    if (enable)
-        glEnable(GL_DITHER);
-    else
-        glDisable(GL_DITHER);
-}
-
-void Graphics::SetFlushGPU_OGL(bool enable)
-{
-    // Currently unimplemented on OpenGL
-}
-
-void Graphics::SetForceGL2_OGL(bool enable)
-{
-    if (IsInitialized_OGL())
-    {
-        URHO3D_LOGERROR("OpenGL 2 can only be forced before setting the initial screen mode");
-        return;
-    }
-
-    forceGL2_ = enable;
-}
-
-void Graphics::Close_OGL()
-{
-    if (!IsInitialized_OGL())
-        return;
-
-    // Actually close the window
-    Release_OGL(true, true);
-}
-
-bool Graphics::TakeScreenShot_OGL(Image& destImage)
-{
-    URHO3D_PROFILE(TakeScreenShot_OGL);
-
-    if (!IsInitialized_OGL())
-        return false;
-
-    if (IsDeviceLost_OGL())
-    {
-        URHO3D_LOGERROR("Can not take screenshot while device is lost");
-        return false;
-    }
-
-    ResetRenderTargets_OGL();
-
-#ifndef GL_ES_VERSION_2_0
-    destImage.SetSize(width_, height_, 3);
-    glReadPixels(0, 0, width_, height_, GL_RGB, GL_UNSIGNED_BYTE, destImage.GetData());
-#else
-    // Use RGBA format on OpenGL ES, as otherwise (at least on Android) the produced image is all black
-    destImage.SetSize(width_, height_, 4);
-    glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, destImage.GetData());
-#endif
-
-    // On OpenGL we need to flip the image vertically after reading
-    destImage.FlipVertical();
-
-    return true;
-}
-
-bool Graphics::BeginFrame_OGL()
-{
-    if (!IsInitialized_OGL() || IsDeviceLost_OGL())
-        return false;
-
-    // If using an external window, check it for size changes, and reset screen mode if necessary
-    if (externalWindow_)
-    {
-        int width, height;
-
-        SDL_GL_GetDrawableSize(window_, &width, &height);
-        if (width != width_ || height != height_)
-            SetMode(width, height);
-    }
-
-    // Re-enable depth test and depth func in case a third party program has modified it
-    glEnable(GL_DEPTH_TEST);
-    glDepthFunc(glCmpFunc[depthTestMode_]);
-
-    // Set default rendertarget and depth buffer
-    ResetRenderTargets_OGL();
-
-    // Cleanup textures from previous frame
-    for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
-        SetTexture_OGL(i, nullptr);
-
-    // Enable color and depth write
-    SetColorWrite_OGL(true);
-    SetDepthWrite_OGL(true);
-
-    numPrimitives_ = 0;
-    numBatches_ = 0;
-
-    SendEvent(E_BEGINRENDERING);
-
-    return true;
-}
-
-void Graphics::EndFrame_OGL()
-{
-    if (!IsInitialized_OGL())
-        return;
-
-    URHO3D_PROFILE(Present);
-
-    SendEvent(E_ENDRENDERING);
-
-    SDL_GL_SwapWindow(window_);
-
-    // Clean up too large scratch buffers
-    CleanupScratchBuffers();
-}
-
-void Graphics::Clear_OGL(ClearTargetFlags flags, const Color& color, float depth, u32 stencil)
-{
-    PrepareDraw_OGL();
-
-#ifdef GL_ES_VERSION_2_0
-    flags &= ~CLEAR_STENCIL;
-#endif
-
-    bool oldColorWrite = colorWrite_;
-    bool oldDepthWrite = depthWrite_;
-
-    if (flags & CLEAR_COLOR && !oldColorWrite)
-        SetColorWrite_OGL(true);
-    if (flags & CLEAR_DEPTH && !oldDepthWrite)
-        SetDepthWrite_OGL(true);
-    if (flags & CLEAR_STENCIL && stencilWriteMask_ != M_U32_MASK_ALL_BITS)
-        glStencilMask(M_U32_MASK_ALL_BITS);
-
-    GLbitfield glFlags = 0;
-    if (flags & CLEAR_COLOR)
-    {
-        glFlags |= GL_COLOR_BUFFER_BIT;
-        glClearColor(color.r_, color.g_, color.b_, color.a_);
-    }
-    if (flags & CLEAR_DEPTH)
-    {
-        glFlags |= GL_DEPTH_BUFFER_BIT;
-        glClearDepth(depth);
-    }
-    if (flags & CLEAR_STENCIL)
-    {
-        glFlags |= GL_STENCIL_BUFFER_BIT;
-        glClearStencil((GLint)stencil);
-    }
-
-    // If viewport is less than full screen, set a scissor to limit the clear
-    /// \todo Any user-set scissor test will be lost
-    IntVector2 viewSize = GetRenderTargetDimensions_OGL();
-    if (viewport_.left_ != 0 || viewport_.top_ != 0 || viewport_.right_ != viewSize.x_ || viewport_.bottom_ != viewSize.y_)
-        SetScissorTest_OGL(true, IntRect(0, 0, viewport_.Width(), viewport_.Height()));
-    else
-        SetScissorTest_OGL(false);
-
-    glClear(glFlags);
-
-    SetScissorTest_OGL(false);
-    SetColorWrite_OGL(oldColorWrite);
-    SetDepthWrite_OGL(oldDepthWrite);
-    if (flags & CLEAR_STENCIL && stencilWriteMask_ != M_U32_MASK_ALL_BITS)
-        glStencilMask(stencilWriteMask_);
-}
-
-bool Graphics::ResolveToTexture_OGL(Texture2D* destination, const IntRect& viewport)
-{
-    if (!destination || !destination->GetRenderSurface())
-        return false;
-
-    URHO3D_PROFILE(ResolveToTexture_OGL);
-
-    IntRect vpCopy = viewport;
-    if (vpCopy.right_ <= vpCopy.left_)
-        vpCopy.right_ = vpCopy.left_ + 1;
-    if (vpCopy.bottom_ <= vpCopy.top_)
-        vpCopy.bottom_ = vpCopy.top_ + 1;
-    vpCopy.left_ = Clamp(vpCopy.left_, 0, width_);
-    vpCopy.top_ = Clamp(vpCopy.top_, 0, height_);
-    vpCopy.right_ = Clamp(vpCopy.right_, 0, width_);
-    vpCopy.bottom_ = Clamp(vpCopy.bottom_, 0, height_);
-
-    // Make sure the FBO is not in use
-    ResetRenderTargets_OGL();
-
-    // Use Direct3D convention with the vertical coordinates ie. 0 is top
-    SetTextureForUpdate_OGL(destination);
-    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, vpCopy.left_, height_ - vpCopy.bottom_, vpCopy.Width(), vpCopy.Height());
-    SetTexture_OGL(0, nullptr);
-
-    return true;
-}
-
-bool Graphics::ResolveToTexture_OGL(Texture2D* texture)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (!texture)
-        return false;
-    RenderSurface* surface = texture->GetRenderSurface();
-    if (!surface || !surface->GetRenderBuffer())
-        return false;
-
-    URHO3D_PROFILE(ResolveToTexture_OGL);
-
-    texture->SetResolveDirty(false);
-    surface->SetResolveDirty(false);
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    // Use separate FBOs for resolve to not disturb the currently set rendertarget(s)
-    if (!impl->resolveSrcFBO_)
-        impl->resolveSrcFBO_ = CreateFramebuffer_OGL();
-    if (!impl->resolveDestFBO_)
-        impl->resolveDestFBO_ = CreateFramebuffer_OGL();
-
-    if (!gl3Support)
-    {
-        glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, impl->resolveSrcFBO_);
-        glFramebufferRenderbufferEXT(GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT,
-            surface->GetRenderBuffer());
-        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, impl->resolveDestFBO_);
-        glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texture->GetGPUObjectName(),
-            0);
-        glBlitFramebufferEXT(0, 0, texture->GetWidth(), texture->GetHeight(), 0, 0, texture->GetWidth(), texture->GetHeight(),
-            GL_COLOR_BUFFER_BIT, GL_NEAREST);
-        glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
-        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
-    }
-    else
-    {
-        glBindFramebuffer(GL_READ_FRAMEBUFFER, impl->resolveSrcFBO_);
-        glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, surface->GetRenderBuffer());
-        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, impl->resolveDestFBO_);
-        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->GetGPUObjectName(), 0);
-        glBlitFramebuffer(0, 0, texture->GetWidth(), texture->GetHeight(), 0, 0, texture->GetWidth(), texture->GetHeight(),
-            GL_COLOR_BUFFER_BIT, GL_NEAREST);
-        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
-        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
-    }
-
-    // Restore previously bound FBO
-    BindFramebuffer_OGL(impl->boundFBO_);
-    return true;
-#else
-    // Not supported on GLES
-    return false;
-#endif
-}
-
-bool Graphics::ResolveToTexture_OGL(TextureCube* texture)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (!texture)
-        return false;
-
-    URHO3D_PROFILE(ResolveToTexture_OGL);
-
-    texture->SetResolveDirty(false);
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    // Use separate FBOs for resolve to not disturb the currently set rendertarget(s)
-    if (!impl->resolveSrcFBO_)
-        impl->resolveSrcFBO_ = CreateFramebuffer_OGL();
-    if (!impl->resolveDestFBO_)
-        impl->resolveDestFBO_ = CreateFramebuffer_OGL();
-
-    if (!gl3Support)
-    {
-        for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
-        {
-            // Resolve only the surface(s) that were actually rendered to
-            RenderSurface* surface = texture->GetRenderSurface((CubeMapFace)i);
-            if (!surface->IsResolveDirty())
-                continue;
-
-            surface->SetResolveDirty(false);
-            glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, impl->resolveSrcFBO_);
-            glFramebufferRenderbufferEXT(GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT,
-                surface->GetRenderBuffer());
-            glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, impl->resolveDestFBO_);
-            glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
-                texture->GetGPUObjectName(), 0);
-            glBlitFramebufferEXT(0, 0, texture->GetWidth(), texture->GetHeight(), 0, 0, texture->GetWidth(), texture->GetHeight(),
-                GL_COLOR_BUFFER_BIT, GL_NEAREST);
-        }
-
-        glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
-        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
-    }
-    else
-    {
-        for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
-        {
-            RenderSurface* surface = texture->GetRenderSurface((CubeMapFace)i);
-            if (!surface->IsResolveDirty())
-                continue;
-
-            surface->SetResolveDirty(false);
-            glBindFramebuffer(GL_READ_FRAMEBUFFER, impl->resolveSrcFBO_);
-            glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, surface->GetRenderBuffer());
-            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, impl->resolveDestFBO_);
-            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
-                texture->GetGPUObjectName(), 0);
-            glBlitFramebuffer(0, 0, texture->GetWidth(), texture->GetHeight(), 0, 0, texture->GetWidth(), texture->GetHeight(),
-                GL_COLOR_BUFFER_BIT, GL_NEAREST);
-        }
-
-        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
-        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
-    }
-
-    // Restore previously bound FBO
-    BindFramebuffer_OGL(impl->boundFBO_);
-    return true;
-#else
-    // Not supported on GLES
-    return false;
-#endif
-}
-
-void Graphics::Draw_OGL(PrimitiveType type, unsigned vertexStart, unsigned vertexCount)
-{
-    if (!vertexCount)
-        return;
-
-    PrepareDraw_OGL();
-
-    unsigned primitiveCount;
-    GLenum glPrimitiveType;
-
-    GetGLPrimitiveType(vertexCount, type, primitiveCount, glPrimitiveType);
-    glDrawArrays(glPrimitiveType, vertexStart, vertexCount);
-
-    numPrimitives_ += primitiveCount;
-    ++numBatches_;
-}
-
-void Graphics::Draw_OGL(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned minVertex, unsigned vertexCount)
-{
-    if (!indexCount || !indexBuffer_ || !indexBuffer_->GetGPUObjectName())
-        return;
-
-    PrepareDraw_OGL();
-
-    unsigned indexSize = indexBuffer_->GetIndexSize();
-    unsigned primitiveCount;
-    GLenum glPrimitiveType;
-
-    GetGLPrimitiveType(indexCount, type, primitiveCount, glPrimitiveType);
-    GLenum indexType = indexSize == sizeof(unsigned short) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
-    intptr_t offset = (intptr_t)indexStart * indexSize;
-    glDrawElements(glPrimitiveType, indexCount, indexType, (const void*)offset);
-
-    numPrimitives_ += primitiveCount;
-    ++numBatches_;
-}
-
-void Graphics::Draw_OGL(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned baseVertexIndex, unsigned minVertex, unsigned vertexCount)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support || !indexCount || !indexBuffer_ || !indexBuffer_->GetGPUObjectName())
-        return;
-
-    PrepareDraw_OGL();
-
-    unsigned indexSize = indexBuffer_->GetIndexSize();
-    unsigned primitiveCount;
-    GLenum glPrimitiveType;
-
-    GetGLPrimitiveType(indexCount, type, primitiveCount, glPrimitiveType);
-    GLenum indexType = indexSize == sizeof(unsigned short) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
-    intptr_t offset = (intptr_t)indexStart * indexSize;
-    glDrawElementsBaseVertex(glPrimitiveType, indexCount, indexType, (const void*)offset, baseVertexIndex);
-
-    numPrimitives_ += primitiveCount;
-    ++numBatches_;
-#endif
-}
-
-void Graphics::DrawInstanced_OGL(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned minVertex, unsigned vertexCount,
-    unsigned instanceCount)
-{
-#if !defined(GL_ES_VERSION_2_0) || defined(__EMSCRIPTEN__)
-    if (!indexCount || !indexBuffer_ || !indexBuffer_->GetGPUObjectName() || !instancingSupport_)
-        return;
-
-    PrepareDraw_OGL();
-
-    unsigned indexSize = indexBuffer_->GetIndexSize();
-    unsigned primitiveCount;
-    GLenum glPrimitiveType;
-
-    GetGLPrimitiveType(indexCount, type, primitiveCount, glPrimitiveType);
-    GLenum indexType = indexSize == sizeof(unsigned short) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
-    intptr_t offset = (intptr_t)indexStart * indexSize;
-#ifdef __EMSCRIPTEN__
-    glDrawElementsInstancedANGLE(glPrimitiveType, indexCount, indexType, (const void*)offset, instanceCount);
-#else
-    if (gl3Support)
-    {
-        glDrawElementsInstanced(glPrimitiveType, indexCount, indexType, (const void*)offset, instanceCount);
-    }
-    else
-    {
-        glDrawElementsInstancedARB(glPrimitiveType, indexCount, indexType, (const void*)offset, instanceCount);
-    }
-#endif
-
-    numPrimitives_ += instanceCount * primitiveCount;
-    ++numBatches_;
-#endif
-}
-
-void Graphics::DrawInstanced_OGL(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned baseVertexIndex, unsigned minVertex,
-        unsigned vertexCount, unsigned instanceCount)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support || !indexCount || !indexBuffer_ || !indexBuffer_->GetGPUObjectName() || !instancingSupport_)
-        return;
-
-    PrepareDraw_OGL();
-
-    unsigned indexSize = indexBuffer_->GetIndexSize();
-    unsigned primitiveCount;
-    GLenum glPrimitiveType;
-
-    GetGLPrimitiveType(indexCount, type, primitiveCount, glPrimitiveType);
-    GLenum indexType = indexSize == sizeof(unsigned short) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
-    intptr_t offset = (intptr_t)indexStart * indexSize;
-    glDrawElementsInstancedBaseVertex(glPrimitiveType, indexCount, indexType, (const void*)offset, instanceCount, baseVertexIndex);
-
-    numPrimitives_ += instanceCount * primitiveCount;
-    ++numBatches_;
-#endif
-}
-
-void Graphics::SetVertexBuffer_OGL(VertexBuffer* buffer)
-{
-    // Note: this is not multi-instance safe
-    static Vector<VertexBuffer*> vertexBuffers(1);
-    vertexBuffers[0] = buffer;
-    SetVertexBuffers_OGL(vertexBuffers);
-}
-
-bool Graphics::SetVertexBuffers_OGL(const Vector<VertexBuffer*>& buffers, unsigned instanceOffset)
-{
-    if (buffers.Size() > MAX_VERTEX_STREAMS)
-    {
-        URHO3D_LOGERROR("Too many vertex buffers");
-        return false;
-    }
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (instanceOffset != impl->lastInstanceOffset_)
-    {
-        impl->lastInstanceOffset_ = instanceOffset;
-        impl->vertexBuffersDirty_ = true;
-    }
-
-    for (i32 i = 0; i < MAX_VERTEX_STREAMS; ++i)
-    {
-        VertexBuffer* buffer = nullptr;
-        if (i < buffers.Size())
-            buffer = buffers[i];
-        if (buffer != vertexBuffers_[i])
-        {
-            vertexBuffers_[i] = buffer;
-            impl->vertexBuffersDirty_ = true;
-        }
-    }
-
-    return true;
-}
-
-bool Graphics::SetVertexBuffers_OGL(const Vector<SharedPtr<VertexBuffer>>& buffers, unsigned instanceOffset)
-{
-    return SetVertexBuffers_OGL(reinterpret_cast<const Vector<VertexBuffer*>&>(buffers), instanceOffset);
-}
-
-void Graphics::SetIndexBuffer_OGL(IndexBuffer* buffer)
-{
-    if (indexBuffer_ == buffer)
-        return;
-
-    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer ? buffer->GetGPUObjectName() : 0);
-    indexBuffer_ = buffer;
-}
-
-void Graphics::SetShaders_OGL(ShaderVariation* vs, ShaderVariation* ps)
-{
-    if (vs == vertexShader_ && ps == pixelShader_)
-        return;
-
-    // Compile the shaders now if not yet compiled. If already attempted, do not retry
-    if (vs && !vs->GetGPUObjectName())
-    {
-        if (vs->GetCompilerOutput().Empty())
-        {
-            URHO3D_PROFILE(CompileVertexShader);
-
-            bool success = vs->Create();
-            if (success)
-                URHO3D_LOGDEBUG("Compiled vertex shader " + vs->GetFullName());
-            else
-            {
-                URHO3D_LOGERROR("Failed to compile vertex shader " + vs->GetFullName() + ":\n" + vs->GetCompilerOutput());
-                vs = nullptr;
-            }
-        }
-        else
-            vs = nullptr;
-    }
-
-    if (ps && !ps->GetGPUObjectName())
-    {
-        if (ps->GetCompilerOutput().Empty())
-        {
-            URHO3D_PROFILE(CompilePixelShader);
-
-            bool success = ps->Create();
-            if (success)
-                URHO3D_LOGDEBUG("Compiled pixel shader " + ps->GetFullName());
-            else
-            {
-                URHO3D_LOGERROR("Failed to compile pixel shader " + ps->GetFullName() + ":\n" + ps->GetCompilerOutput());
-                ps = nullptr;
-            }
-        }
-        else
-            ps = nullptr;
-    }
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (!vs || !ps)
-    {
-        glUseProgram(0);
-        vertexShader_ = nullptr;
-        pixelShader_ = nullptr;
-        impl->shaderProgram_ = nullptr;
-    }
-    else
-    {
-        vertexShader_ = vs;
-        pixelShader_ = ps;
-
-        Pair<ShaderVariation*, ShaderVariation*> combination(vs, ps);
-        ShaderProgramMap_OGL::Iterator i = impl->shaderPrograms_.Find(combination);
-
-        if (i != impl->shaderPrograms_.End())
-        {
-            // Use the existing linked program
-            if (i->second_->GetGPUObjectName())
-            {
-                glUseProgram(i->second_->GetGPUObjectName());
-                impl->shaderProgram_ = i->second_;
-            }
-            else
-            {
-                glUseProgram(0);
-                impl->shaderProgram_ = nullptr;
-            }
-        }
-        else
-        {
-            // Link a new combination
-            URHO3D_PROFILE(LinkShaders);
-
-            SharedPtr<ShaderProgram_OGL> newProgram(new ShaderProgram_OGL(this, vs, ps));
-            if (newProgram->Link())
-            {
-                URHO3D_LOGDEBUG("Linked vertex shader " + vs->GetFullName() + " and pixel shader " + ps->GetFullName());
-                // Note: Link() calls glUseProgram() to set the texture sampler uniforms,
-                // so it is not necessary to call it again
-                impl->shaderProgram_ = newProgram;
-            }
-            else
-            {
-                URHO3D_LOGERROR("Failed to link vertex shader " + vs->GetFullName() + " and pixel shader " + ps->GetFullName() + ":\n" +
-                         newProgram->GetLinkerOutput());
-                glUseProgram(0);
-                impl->shaderProgram_ = nullptr;
-            }
-
-            impl->shaderPrograms_[combination] = newProgram;
-        }
-    }
-
-    // Update the clip plane uniform on GL3, and set constant buffers
-#ifndef GL_ES_VERSION_2_0
-    if (gl3Support && impl->shaderProgram_)
-    {
-        const SharedPtr<ConstantBuffer>* constantBuffers = impl->shaderProgram_->GetConstantBuffers();
-        for (unsigned i = 0; i < MAX_SHADER_PARAMETER_GROUPS * 2; ++i)
-        {
-            ConstantBuffer* buffer = constantBuffers[i].Get();
-            if (buffer != impl->constantBuffers_[i])
-            {
-                unsigned object = buffer ? buffer->GetGPUObjectName() : 0;
-                glBindBufferBase(GL_UNIFORM_BUFFER, i, object);
-                // Calling glBindBufferBase also affects the generic buffer binding point
-                impl->boundUBO_ = object;
-                impl->constantBuffers_[i] = buffer;
-                ShaderProgram_OGL::ClearGlobalParameterSource((ShaderParameterGroup)(i % MAX_SHADER_PARAMETER_GROUPS));
-            }
-        }
-
-        SetShaderParameter_OGL(VSP_CLIPPLANE, useClipPlane_ ? clipPlane_ : Vector4(0.0f, 0.0f, 0.0f, 1.0f));
-    }
-#endif
-
-    // Store shader combination if shader dumping in progress
-    if (shaderPrecache_)
-        shaderPrecache_->StoreShaders(vertexShader_, pixelShader_);
-
-    if (impl->shaderProgram_)
-    {
-        impl->usedVertexAttributes_ = impl->shaderProgram_->GetUsedVertexAttributes();
-        impl->vertexAttributes_ = &impl->shaderProgram_->GetVertexAttributes();
-    }
-    else
-    {
-        impl->usedVertexAttributes_ = 0;
-        impl->vertexAttributes_ = nullptr;
-    }
-
-    impl->vertexBuffersDirty_ = true;
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, const float* data, unsigned count)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, (unsigned)(count * sizeof(float)), data);
-                return;
-            }
-
-            switch (info->glType_)
-            {
-            case GL_FLOAT:
-                glUniform1fv(info->location_, count, data);
-                break;
-
-            case GL_FLOAT_VEC2:
-                glUniform2fv(info->location_, count / 2, data);
-                break;
-
-            case GL_FLOAT_VEC3:
-                glUniform3fv(info->location_, count / 3, data);
-                break;
-
-            case GL_FLOAT_VEC4:
-                glUniform4fv(info->location_, count / 4, data);
-                break;
-
-            case GL_FLOAT_MAT3:
-                glUniformMatrix3fv(info->location_, count / 9, GL_FALSE, data);
-                break;
-
-            case GL_FLOAT_MAT4:
-                glUniformMatrix4fv(info->location_, count / 16, GL_FALSE, data);
-                break;
-
-            default: break;
-            }
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, float value)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, sizeof(float), &value);
-                return;
-            }
-
-            glUniform1fv(info->location_, 1, &value);
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, int value)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, sizeof(int), &value);
-                return;
-            }
-
-            glUniform1i(info->location_, value);
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, bool value)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    // \todo Not tested
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, sizeof(bool), &value);
-                return;
-            }
-
-            glUniform1i(info->location_, (int)value);
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, const Color& color)
-{
-    SetShaderParameter_OGL(param, color.Data(), 4);
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, const Vector2& vector)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, sizeof(Vector2), &vector);
-                return;
-            }
-
-            // Check the uniform type to avoid mismatch
-            switch (info->glType_)
-            {
-            case GL_FLOAT:
-                glUniform1fv(info->location_, 1, vector.Data());
-                break;
-
-            case GL_FLOAT_VEC2:
-                glUniform2fv(info->location_, 1, vector.Data());
-                break;
-
-            default: break;
-            }
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, const Matrix3& matrix)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetVector3ArrayParameter(info->offset_, 3, &matrix);
-                return;
-            }
-
-            glUniformMatrix3fv(info->location_, 1, GL_FALSE, matrix.Data());
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, const Vector3& vector)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, sizeof(Vector3), &vector);
-                return;
-            }
-
-            // Check the uniform type to avoid mismatch
-            switch (info->glType_)
-            {
-            case GL_FLOAT:
-                glUniform1fv(info->location_, 1, vector.Data());
-                break;
-
-            case GL_FLOAT_VEC2:
-                glUniform2fv(info->location_, 1, vector.Data());
-                break;
-
-            case GL_FLOAT_VEC3:
-                glUniform3fv(info->location_, 1, vector.Data());
-                break;
-
-            default: break;
-            }
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, const Matrix4& matrix)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, sizeof(Matrix4), &matrix);
-                return;
-            }
-
-            glUniformMatrix4fv(info->location_, 1, GL_FALSE, matrix.Data());
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, const Vector4& vector)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, sizeof(Vector4), &vector);
-                return;
-            }
-
-            // Check the uniform type to avoid mismatch
-            switch (info->glType_)
-            {
-            case GL_FLOAT:
-                glUniform1fv(info->location_, 1, vector.Data());
-                break;
-
-            case GL_FLOAT_VEC2:
-                glUniform2fv(info->location_, 1, vector.Data());
-                break;
-
-            case GL_FLOAT_VEC3:
-                glUniform3fv(info->location_, 1, vector.Data());
-                break;
-
-            case GL_FLOAT_VEC4:
-                glUniform4fv(info->location_, 1, vector.Data());
-                break;
-
-            default: break;
-            }
-        }
-    }
-}
-
-void Graphics::SetShaderParameter_OGL(StringHash param, const Matrix3x4& matrix)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->shaderProgram_)
-    {
-        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
-        if (info)
-        {
-            // Expand to a full Matrix4
-            static Matrix4 fullMatrix;
-            fullMatrix.m00_ = matrix.m00_;
-            fullMatrix.m01_ = matrix.m01_;
-            fullMatrix.m02_ = matrix.m02_;
-            fullMatrix.m03_ = matrix.m03_;
-            fullMatrix.m10_ = matrix.m10_;
-            fullMatrix.m11_ = matrix.m11_;
-            fullMatrix.m12_ = matrix.m12_;
-            fullMatrix.m13_ = matrix.m13_;
-            fullMatrix.m20_ = matrix.m20_;
-            fullMatrix.m21_ = matrix.m21_;
-            fullMatrix.m22_ = matrix.m22_;
-            fullMatrix.m23_ = matrix.m23_;
-
-            if (info->bufferPtr_)
-            {
-                ConstantBuffer* buffer = info->bufferPtr_;
-                if (!buffer->IsDirty())
-                    impl->dirtyConstantBuffers_.Push(buffer);
-                buffer->SetParameter(info->offset_, sizeof(Matrix4), &fullMatrix);
-                return;
-            }
-
-            glUniformMatrix4fv(info->location_, 1, GL_FALSE, fullMatrix.Data());
-        }
-    }
-}
-
-bool Graphics::NeedParameterUpdate_OGL(ShaderParameterGroup group, const void* source)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-    return impl->shaderProgram_ ? impl->shaderProgram_->NeedParameterUpdate(group, source) : false;
-}
-
-bool Graphics::HasShaderParameter_OGL(StringHash param)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-    return impl->shaderProgram_ && impl->shaderProgram_->HasParameter(param);
-}
-
-bool Graphics::HasTextureUnit_OGL(TextureUnit unit)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-    return impl->shaderProgram_ && impl->shaderProgram_->HasTextureUnit(unit);
-}
-
-void Graphics::ClearParameterSource_OGL(ShaderParameterGroup group)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-    if (impl->shaderProgram_)
-        impl->shaderProgram_->ClearParameterSource(group);
-}
-
-void Graphics::ClearParameterSources_OGL()
-{
-    ShaderProgram_OGL::ClearParameterSources();
-}
-
-void Graphics::ClearTransformSources_OGL()
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-    if (impl->shaderProgram_)
-    {
-        impl->shaderProgram_->ClearParameterSource(SP_CAMERA);
-        impl->shaderProgram_->ClearParameterSource(SP_OBJECT);
-    }
-}
-
-void Graphics::SetTexture_OGL(unsigned index, Texture* texture)
-{
-    if (index >= MAX_TEXTURE_UNITS)
-        return;
-
-    // Check if texture is currently bound as a rendertarget. In that case, use its backup texture, or blank if not defined
-    if (texture)
-    {
-        if (renderTargets_[0] && renderTargets_[0]->GetParentTexture() == texture)
-            texture = texture->GetBackupTexture();
-        else
-        {
-            // Resolve multisampled texture now as necessary
-            if (texture->GetMultiSample() > 1 && texture->GetAutoResolve() && texture->IsResolveDirty())
-            {
-                if (texture->GetType() == Texture2D::GetTypeStatic())
-                    ResolveToTexture_OGL(static_cast<Texture2D*>(texture));
-                if (texture->GetType() == TextureCube::GetTypeStatic())
-                    ResolveToTexture_OGL(static_cast<TextureCube*>(texture));
-            }
-        }
-    }
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (textures_[index] != texture)
-    {
-        if (impl->activeTexture_ != index)
-        {
-            glActiveTexture(GL_TEXTURE0 + index);
-            impl->activeTexture_ = index;
-        }
-
-        if (texture)
-        {
-            unsigned glType = texture->GetTarget();
-            // Unbind old texture type if necessary
-            if (impl->textureTypes_[index] && impl->textureTypes_[index] != glType)
-                glBindTexture(impl->textureTypes_[index], 0);
-            glBindTexture(glType, texture->GetGPUObjectName());
-            impl->textureTypes_[index] = glType;
-
-            if (texture->GetParametersDirty())
-                texture->UpdateParameters();
-            if (texture->GetLevelsDirty())
-                texture->RegenerateLevels();
-        }
-        else if (impl->textureTypes_[index])
-        {
-            glBindTexture(impl->textureTypes_[index], 0);
-            impl->textureTypes_[index] = 0;
-        }
-
-        textures_[index] = texture;
-    }
-    else
-    {
-        if (texture && (texture->GetParametersDirty() || texture->GetLevelsDirty()))
-        {
-            if (impl->activeTexture_ != index)
-            {
-                glActiveTexture(GL_TEXTURE0 + index);
-                impl->activeTexture_ = index;
-            }
-
-            glBindTexture(texture->GetTarget(), texture->GetGPUObjectName());
-            if (texture->GetParametersDirty())
-                texture->UpdateParameters();
-            if (texture->GetLevelsDirty())
-                texture->RegenerateLevels();
-        }
-    }
-}
-
-void Graphics::SetTextureForUpdate_OGL(Texture* texture)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->activeTexture_ != 0)
-    {
-        glActiveTexture(GL_TEXTURE0);
-        impl->activeTexture_ = 0;
-    }
-
-    unsigned glType = texture->GetTarget();
-    // Unbind old texture type if necessary
-    if (impl->textureTypes_[0] && impl->textureTypes_[0] != glType)
-        glBindTexture(impl->textureTypes_[0], 0);
-    glBindTexture(glType, texture->GetGPUObjectName());
-    impl->textureTypes_[0] = glType;
-    textures_[0] = texture;
-}
-
-void Graphics::SetDefaultTextureFilterMode_OGL(TextureFilterMode mode)
-{
-    if (mode != defaultTextureFilterMode_)
-    {
-        defaultTextureFilterMode_ = mode;
-        SetTextureParametersDirty_OGL();
-    }
-}
-
-void Graphics::SetDefaultTextureAnisotropy_OGL(unsigned level)
-{
-    level = Max(level, 1U);
-
-    if (level != defaultTextureAnisotropy_)
-    {
-        defaultTextureAnisotropy_ = level;
-        SetTextureParametersDirty_OGL();
-    }
-}
-
-void Graphics::SetTextureParametersDirty_OGL()
-{
-    MutexLock lock(gpuObjectMutex_);
-
-    for (Vector<GPUObject*>::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i)
-    {
-        auto* texture = dynamic_cast<Texture*>(*i);
-        if (texture)
-            texture->SetParametersDirty();
-    }
-}
-
-void Graphics::ResetRenderTargets_OGL()
-{
-    for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i)
-        SetRenderTarget_OGL(i, (RenderSurface*)nullptr);
-    SetDepthStencil_OGL((RenderSurface*)nullptr);
-    SetViewport_OGL(IntRect(0, 0, width_, height_));
-}
-
-void Graphics::ResetRenderTarget_OGL(unsigned index)
-{
-    SetRenderTarget_OGL(index, (RenderSurface*)nullptr);
-}
-
-void Graphics::ResetDepthStencil_OGL()
-{
-    SetDepthStencil_OGL((RenderSurface*)nullptr);
-}
-
-void Graphics::SetRenderTarget_OGL(unsigned index, RenderSurface* renderTarget)
-{
-    if (index >= MAX_RENDERTARGETS)
-        return;
-
-    if (renderTarget != renderTargets_[index])
-    {
-        renderTargets_[index] = renderTarget;
-
-        // If the rendertarget is also bound as a texture, replace with backup texture or null
-        if (renderTarget)
-        {
-            Texture* parentTexture = renderTarget->GetParentTexture();
-
-            for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
-            {
-                if (textures_[i] == parentTexture)
-                    SetTexture_OGL(i, textures_[i]->GetBackupTexture());
-            }
-
-            // If multisampled, mark the texture & surface needing resolve
-            if (parentTexture->GetMultiSample() > 1 && parentTexture->GetAutoResolve())
-            {
-                parentTexture->SetResolveDirty(true);
-                renderTarget->SetResolveDirty(true);
-            }
-
-            // If mipmapped, mark the levels needing regeneration
-            if (parentTexture->GetLevels() > 1)
-                parentTexture->SetLevelsDirty();
-        }
-
-        GetImpl_OGL()->fboDirty_ = true;
-    }
-}
-
-void Graphics::SetRenderTarget_OGL(unsigned index, Texture2D* texture)
-{
-    RenderSurface* renderTarget = nullptr;
-    if (texture)
-        renderTarget = texture->GetRenderSurface();
-
-    SetRenderTarget_OGL(index, renderTarget);
-}
-
-void Graphics::SetDepthStencil_OGL(RenderSurface* depthStencil)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    // If we are using a rendertarget texture, it is required in OpenGL to also have an own depth-stencil
-    // Create a new depth-stencil texture as necessary to be able to provide similar behaviour as Direct3D9
-    // Only do this for non-multisampled rendertargets; when using multisampled target a similarly multisampled
-    // depth-stencil should also be provided (backbuffer depth isn't compatible)
-    if (renderTargets_[0] && renderTargets_[0]->GetMultiSample() == 1 && !depthStencil)
-    {
-        int width = renderTargets_[0]->GetWidth();
-        int height = renderTargets_[0]->GetHeight();
-
-        // Direct3D9 default depth-stencil can not be used when rendertarget is larger than the window.
-        // Check size similarly
-        if (width <= width_ && height <= height_)
-        {
-            unsigned searchKey = (width << 16u) | height;
-            HashMap<unsigned, SharedPtr<Texture2D>>::Iterator i = impl->depthTextures_.Find(searchKey);
-            if (i != impl->depthTextures_.End())
-                depthStencil = i->second_->GetRenderSurface();
-            else
-            {
-                SharedPtr<Texture2D> newDepthTexture(new Texture2D(context_));
-                newDepthTexture->SetSize(width, height, GetDepthStencilFormat_OGL(), TEXTURE_DEPTHSTENCIL);
-                impl->depthTextures_[searchKey] = newDepthTexture;
-                depthStencil = newDepthTexture->GetRenderSurface();
-            }
-        }
-    }
-
-    if (depthStencil != depthStencil_)
-    {
-        depthStencil_ = depthStencil;
-        impl->fboDirty_ = true;
-    }
-}
-
-void Graphics::SetDepthStencil_OGL(Texture2D* texture)
-{
-    RenderSurface* depthStencil = nullptr;
-    if (texture)
-        depthStencil = texture->GetRenderSurface();
-
-    SetDepthStencil_OGL(depthStencil);
-}
-
-void Graphics::SetViewport_OGL(const IntRect& rect)
-{
-    PrepareDraw_OGL();
-
-    IntVector2 rtSize = GetRenderTargetDimensions_OGL();
-
-    IntRect rectCopy = rect;
-
-    if (rectCopy.right_ <= rectCopy.left_)
-        rectCopy.right_ = rectCopy.left_ + 1;
-    if (rectCopy.bottom_ <= rectCopy.top_)
-        rectCopy.bottom_ = rectCopy.top_ + 1;
-    rectCopy.left_ = Clamp(rectCopy.left_, 0, rtSize.x_);
-    rectCopy.top_ = Clamp(rectCopy.top_, 0, rtSize.y_);
-    rectCopy.right_ = Clamp(rectCopy.right_, 0, rtSize.x_);
-    rectCopy.bottom_ = Clamp(rectCopy.bottom_, 0, rtSize.y_);
-
-    // Use Direct3D convention with the vertical coordinates ie. 0 is top
-    glViewport(rectCopy.left_, rtSize.y_ - rectCopy.bottom_, rectCopy.Width(), rectCopy.Height());
-    viewport_ = rectCopy;
-
-    // Disable scissor test, needs to be re-enabled by the user
-    SetScissorTest_OGL(false);
-}
-
-void Graphics::SetBlendMode_OGL(BlendMode mode, bool alphaToCoverage)
-{
-    if (mode != blendMode_)
-    {
-        if (mode == BLEND_REPLACE)
-            glDisable(GL_BLEND);
-        else
-        {
-            glEnable(GL_BLEND);
-            glBlendFunc(glSrcBlend[mode], glDestBlend[mode]);
-            glBlendEquation(glBlendOp[mode]);
-        }
-
-        blendMode_ = mode;
-    }
-
-    if (alphaToCoverage != alphaToCoverage_)
-    {
-        if (alphaToCoverage)
-            glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
-        else
-            glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
-
-        alphaToCoverage_ = alphaToCoverage;
-    }
-}
-
-void Graphics::SetColorWrite_OGL(bool enable)
-{
-    if (enable != colorWrite_)
-    {
-        if (enable)
-            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-        else
-            glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
-
-        colorWrite_ = enable;
-    }
-}
-
-void Graphics::SetCullMode_OGL(CullMode mode)
-{
-    if (mode != cullMode_)
-    {
-        if (mode == CULL_NONE)
-            glDisable(GL_CULL_FACE);
-        else
-        {
-            // Use Direct3D convention, ie. clockwise vertices define a front face
-            glEnable(GL_CULL_FACE);
-            glCullFace(mode == CULL_CCW ? GL_FRONT : GL_BACK);
-        }
-
-        cullMode_ = mode;
-    }
-}
-
-void Graphics::SetDepthBias_OGL(float constantBias, float slopeScaledBias)
-{
-    if (constantBias != constantDepthBias_ || slopeScaledBias != slopeScaledDepthBias_)
-    {
-#ifndef GL_ES_VERSION_2_0
-        if (slopeScaledBias != 0.0f)
-        {
-            // OpenGL constant bias is unreliable and dependent on depth buffer bitdepth, apply in the projection matrix instead
-            glEnable(GL_POLYGON_OFFSET_FILL);
-            glPolygonOffset(slopeScaledBias, 0.0f);
-        }
-        else
-            glDisable(GL_POLYGON_OFFSET_FILL);
-#endif
-
-        constantDepthBias_ = constantBias;
-        slopeScaledDepthBias_ = slopeScaledBias;
-        // Force update of the projection matrix shader parameter
-        ClearParameterSource_OGL(SP_CAMERA);
-    }
-}
-
-void Graphics::SetDepthTest_OGL(CompareMode mode)
-{
-    if (mode != depthTestMode_)
-    {
-        glDepthFunc(glCmpFunc[mode]);
-        depthTestMode_ = mode;
-    }
-}
-
-void Graphics::SetDepthWrite_OGL(bool enable)
-{
-    if (enable != depthWrite_)
-    {
-        glDepthMask(enable ? GL_TRUE : GL_FALSE);
-        depthWrite_ = enable;
-    }
-}
-
-void Graphics::SetFillMode_OGL(FillMode mode)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (mode != fillMode_)
-    {
-        glPolygonMode(GL_FRONT_AND_BACK, glFillMode[mode]);
-        fillMode_ = mode;
-    }
-#endif
-}
-
-void Graphics::SetLineAntiAlias_OGL(bool enable)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (enable != lineAntiAlias_)
-    {
-        if (enable)
-            glEnable(GL_LINE_SMOOTH);
-        else
-            glDisable(GL_LINE_SMOOTH);
-        lineAntiAlias_ = enable;
-    }
-#endif
-}
-
-void Graphics::SetScissorTest_OGL(bool enable, const Rect& rect, bool borderInclusive)
-{
-    // During some light rendering loops, a full rect is toggled on/off repeatedly.
-    // Disable scissor in that case to reduce state changes
-    if (rect.min_.x_ <= 0.0f && rect.min_.y_ <= 0.0f && rect.max_.x_ >= 1.0f && rect.max_.y_ >= 1.0f)
-        enable = false;
-
-    if (enable)
-    {
-        IntVector2 rtSize(GetRenderTargetDimensions_OGL());
-        IntVector2 viewSize(viewport_.Size());
-        IntVector2 viewPos(viewport_.left_, viewport_.top_);
-        IntRect intRect;
-        int expand = borderInclusive ? 1 : 0;
-
-        intRect.left_ = Clamp((int)((rect.min_.x_ + 1.0f) * 0.5f * viewSize.x_) + viewPos.x_, 0, rtSize.x_ - 1);
-        intRect.top_ = Clamp((int)((-rect.max_.y_ + 1.0f) * 0.5f * viewSize.y_) + viewPos.y_, 0, rtSize.y_ - 1);
-        intRect.right_ = Clamp((int)((rect.max_.x_ + 1.0f) * 0.5f * viewSize.x_) + viewPos.x_ + expand, 0, rtSize.x_);
-        intRect.bottom_ = Clamp((int)((-rect.min_.y_ + 1.0f) * 0.5f * viewSize.y_) + viewPos.y_ + expand, 0, rtSize.y_);
-
-        if (intRect.right_ == intRect.left_)
-            intRect.right_++;
-        if (intRect.bottom_ == intRect.top_)
-            intRect.bottom_++;
-
-        if (intRect.right_ < intRect.left_ || intRect.bottom_ < intRect.top_)
-            enable = false;
-
-        if (enable && scissorRect_ != intRect)
-        {
-            // Use Direct3D convention with the vertical coordinates ie. 0 is top
-            glScissor(intRect.left_, rtSize.y_ - intRect.bottom_, intRect.Width(), intRect.Height());
-            scissorRect_ = intRect;
-        }
-    }
-    else
-        scissorRect_ = IntRect::ZERO;
-
-    if (enable != scissorTest_)
-    {
-        if (enable)
-            glEnable(GL_SCISSOR_TEST);
-        else
-            glDisable(GL_SCISSOR_TEST);
-        scissorTest_ = enable;
-    }
-}
-
-void Graphics::SetScissorTest_OGL(bool enable, const IntRect& rect)
-{
-    IntVector2 rtSize(GetRenderTargetDimensions_OGL());
-    IntVector2 viewPos(viewport_.left_, viewport_.top_);
-
-    if (enable)
-    {
-        IntRect intRect;
-        intRect.left_ = Clamp(rect.left_ + viewPos.x_, 0, rtSize.x_ - 1);
-        intRect.top_ = Clamp(rect.top_ + viewPos.y_, 0, rtSize.y_ - 1);
-        intRect.right_ = Clamp(rect.right_ + viewPos.x_, 0, rtSize.x_);
-        intRect.bottom_ = Clamp(rect.bottom_ + viewPos.y_, 0, rtSize.y_);
-
-        if (intRect.right_ == intRect.left_)
-            intRect.right_++;
-        if (intRect.bottom_ == intRect.top_)
-            intRect.bottom_++;
-
-        if (intRect.right_ < intRect.left_ || intRect.bottom_ < intRect.top_)
-            enable = false;
-
-        if (enable && scissorRect_ != intRect)
-        {
-            // Use Direct3D convention with the vertical coordinates ie. 0 is top
-            glScissor(intRect.left_, rtSize.y_ - intRect.bottom_, intRect.Width(), intRect.Height());
-            scissorRect_ = intRect;
-        }
-    }
-    else
-        scissorRect_ = IntRect::ZERO;
-
-    if (enable != scissorTest_)
-    {
-        if (enable)
-            glEnable(GL_SCISSOR_TEST);
-        else
-            glDisable(GL_SCISSOR_TEST);
-        scissorTest_ = enable;
-    }
-}
-
-void Graphics::SetClipPlane_OGL(bool enable, const Plane& clipPlane, const Matrix3x4& view, const Matrix4& projection)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (enable != useClipPlane_)
-    {
-        if (enable)
-            glEnable(GL_CLIP_PLANE0);
-        else
-            glDisable(GL_CLIP_PLANE0);
-
-        useClipPlane_ = enable;
-    }
-
-    if (enable)
-    {
-        Matrix4 viewProj = projection * view;
-        clipPlane_ = clipPlane.Transformed(viewProj).ToVector4();
-
-        if (!gl3Support)
-        {
-            GLdouble planeData[4];
-            planeData[0] = clipPlane_.x_;
-            planeData[1] = clipPlane_.y_;
-            planeData[2] = clipPlane_.z_;
-            planeData[3] = clipPlane_.w_;
-
-            glClipPlane(GL_CLIP_PLANE0, &planeData[0]);
-        }
-    }
-#endif
-}
-
-void Graphics::SetStencilTest_OGL(bool enable, CompareMode mode, StencilOp pass, StencilOp fail, StencilOp zFail, u32 stencilRef,
-    u32 compareMask, u32 writeMask)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (enable != stencilTest_)
-    {
-        if (enable)
-            glEnable(GL_STENCIL_TEST);
-        else
-            glDisable(GL_STENCIL_TEST);
-
-        stencilTest_ = enable;
-    }
-
-    if (enable)
-    {
-        if (mode != stencilTestMode_ || stencilRef != stencilRef_ || compareMask != stencilCompareMask_)
-        {
-            glStencilFunc(glCmpFunc[mode], (GLint)stencilRef, compareMask);
-            stencilTestMode_ = mode;
-            stencilRef_ = stencilRef;
-            stencilCompareMask_ = compareMask;
-        }
-        if (writeMask != stencilWriteMask_)
-        {
-            glStencilMask(writeMask);
-            stencilWriteMask_ = writeMask;
-        }
-        if (pass != stencilPass_ || fail != stencilFail_ || zFail != stencilZFail_)
-        {
-            glStencilOp(glStencilOps[fail], glStencilOps[zFail], glStencilOps[pass]);
-            stencilPass_ = pass;
-            stencilFail_ = fail;
-            stencilZFail_ = zFail;
-        }
-    }
-#endif
-}
-
-bool Graphics::IsInitialized_OGL() const
-{
-    return window_ != nullptr;
-}
-
-bool Graphics::GetDither_OGL() const
-{
-    return glIsEnabled(GL_DITHER) ? true : false;
-}
-
-bool Graphics::IsDeviceLost_OGL() const
-{
-    // On iOS and tvOS treat window minimization as device loss, as it is forbidden to access OpenGL when minimized
-#if defined(IOS) || defined(TVOS)
-    if (window_ && (SDL_GetWindowFlags(window_) & SDL_WINDOW_MINIMIZED) != 0)
-        return true;
-#endif
-
-    return GetImpl_OGL()->context_ == nullptr;
-}
-
-Vector<int> Graphics::GetMultiSampleLevels_OGL() const
-{
-    Vector<int> ret;
-    // No multisampling always supported
-    ret.Push(1);
-
-#ifndef GL_ES_VERSION_2_0
-    int maxSamples = 0;
-    glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
-    for (int i = 2; i <= maxSamples && i <= 16; i *= 2)
-        ret.Push(i);
-#endif
-
-    return ret;
-}
-
-unsigned Graphics::GetFormat_OGL(CompressedFormat format) const
-{
-    switch (format)
-    {
-    case CF_RGBA:
-        return GL_RGBA;
-
-    case CF_DXT1:
-        return dxtTextureSupport_ ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : 0;
-
-#if !defined(GL_ES_VERSION_2_0) || defined(__EMSCRIPTEN__)
-    case CF_DXT3:
-        return dxtTextureSupport_ ? GL_COMPRESSED_RGBA_S3TC_DXT3_EXT : 0;
-
-    case CF_DXT5:
-        return dxtTextureSupport_ ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : 0;
-#endif
-#ifdef GL_ES_VERSION_2_0
-    case CF_ETC1:
-        return etcTextureSupport_ ? GL_ETC1_RGB8_OES : 0;
-
-    case CF_ETC2_RGB:
-        return etc2TextureSupport_ ? GL_ETC2_RGB8_OES : 0;
-
-    case CF_ETC2_RGBA:
-        return etc2TextureSupport_ ? GL_ETC2_RGBA8_OES : 0;
-
-    case CF_PVRTC_RGB_2BPP:
-        return pvrtcTextureSupport_ ? COMPRESSED_RGB_PVRTC_2BPPV1_IMG : 0;
-
-    case CF_PVRTC_RGB_4BPP:
-        return pvrtcTextureSupport_ ? COMPRESSED_RGB_PVRTC_4BPPV1_IMG : 0;
-
-    case CF_PVRTC_RGBA_2BPP:
-        return pvrtcTextureSupport_ ? COMPRESSED_RGBA_PVRTC_2BPPV1_IMG : 0;
-
-    case CF_PVRTC_RGBA_4BPP:
-        return pvrtcTextureSupport_ ? COMPRESSED_RGBA_PVRTC_4BPPV1_IMG : 0;
-#endif
-
-    default:
-        return 0;
-    }
-}
-
-unsigned Graphics::GetMaxBones_OGL()
-{
-#ifdef RPI
-    // At the moment all RPI GPUs are low powered and only have limited number of uniforms
-    return 32;
-#else
-    return gl3Support ? 128 : 64;
-#endif
-}
-
-bool Graphics::GetGL3Support_OGL()
-{
-    return gl3Support;
-}
-
-ShaderVariation* Graphics::GetShader_OGL(ShaderType type, const String& name, const String& defines) const
-{
-    return GetShader_OGL(type, name.CString(), defines.CString());
-}
-
-ShaderVariation* Graphics::GetShader_OGL(ShaderType type, const char* name, const char* defines) const
-{
-    if (lastShaderName_ != name || !lastShader_)
-    {
-        auto* cache = GetSubsystem<ResourceCache>();
-
-        String fullShaderName = shaderPath_ + name + shaderExtension_;
-        // Try to reduce repeated error log prints because of missing shaders
-        if (lastShaderName_ == name && !cache->Exists(fullShaderName))
-            return nullptr;
-
-        lastShader_ = cache->GetResource<Shader>(fullShaderName);
-        lastShaderName_ = name;
-    }
-
-    return lastShader_ ? lastShader_->GetVariation(type, defines) : nullptr;
-}
-
-VertexBuffer* Graphics::GetVertexBuffer_OGL(unsigned index) const
-{
-    return index < MAX_VERTEX_STREAMS ? vertexBuffers_[index] : nullptr;
-}
-
-ShaderProgram_OGL* Graphics::GetShaderProgram_OGL() const
-{
-    return GetImpl_OGL()->shaderProgram_;
-}
-
-TextureUnit Graphics::GetTextureUnit_OGL(const String& name)
-{
-    HashMap<String, TextureUnit>::Iterator i = textureUnits_.Find(name);
-    if (i != textureUnits_.End())
-        return i->second_;
-    else
-        return MAX_TEXTURE_UNITS;
-}
-
-const String& Graphics::GetTextureUnitName_OGL(TextureUnit unit)
-{
-    for (HashMap<String, TextureUnit>::Iterator i = textureUnits_.Begin(); i != textureUnits_.End(); ++i)
-    {
-        if (i->second_ == unit)
-            return i->first_;
-    }
-    return String::EMPTY;
-}
-
-Texture* Graphics::GetTexture_OGL(unsigned index) const
-{
-    return index < MAX_TEXTURE_UNITS ? textures_[index] : nullptr;
-}
-
-RenderSurface* Graphics::GetRenderTarget_OGL(unsigned index) const
-{
-    return index < MAX_RENDERTARGETS ? renderTargets_[index] : nullptr;
-}
-
-IntVector2 Graphics::GetRenderTargetDimensions_OGL() const
-{
-    int width, height;
-
-    if (renderTargets_[0])
-    {
-        width = renderTargets_[0]->GetWidth();
-        height = renderTargets_[0]->GetHeight();
-    }
-    else if (depthStencil_)
-    {
-        width = depthStencil_->GetWidth();
-        height = depthStencil_->GetHeight();
-    }
-    else
-    {
-        width = width_;
-        height = height_;
-    }
-
-    return IntVector2(width, height);
-}
-
-void Graphics::OnWindowResized_OGL()
-{
-    if (!window_)
-        return;
-
-    int newWidth, newHeight;
-
-    SDL_GL_GetDrawableSize(window_, &newWidth, &newHeight);
-    if (newWidth == width_ && newHeight == height_)
-        return;
-
-    width_ = newWidth;
-    height_ = newHeight;
-
-    int logicalWidth, logicalHeight;
-    SDL_GetWindowSize(window_, &logicalWidth, &logicalHeight);
-    screenParams_.highDPI_ = (width_ != logicalWidth) || (height_ != logicalHeight);
-
-    // Reset rendertargets and viewport for the new screen size. Also clean up any FBO's, as they may be screen size dependent
-    CleanupFramebuffers_OGL();
-    ResetRenderTargets_OGL();
-
-    URHO3D_LOGDEBUGF("Window was resized to %dx%d", width_, height_);
-
-#ifdef __EMSCRIPTEN__
-    EM_ASM({
-        Module.SetRendererSize($0, $1);
-    }, width_, height_);
-#endif
-
-    using namespace ScreenMode;
-
-    VariantMap& eventData = GetEventDataMap();
-    eventData[P_WIDTH] = width_;
-    eventData[P_HEIGHT] = height_;
-    eventData[P_FULLSCREEN] = screenParams_.fullscreen_;
-    eventData[P_RESIZABLE] = screenParams_.resizable_;
-    eventData[P_BORDERLESS] = screenParams_.borderless_;
-    eventData[P_HIGHDPI] = screenParams_.highDPI_;
-    SendEvent(E_SCREENMODE, eventData);
-}
-
-void Graphics::OnWindowMoved_OGL()
-{
-    if (!window_ || screenParams_.fullscreen_)
-        return;
-
-    int newX, newY;
-
-    SDL_GetWindowPosition(window_, &newX, &newY);
-    if (newX == position_.x_ && newY == position_.y_)
-        return;
-
-    position_.x_ = newX;
-    position_.y_ = newY;
-
-    URHO3D_LOGTRACEF("Window was moved to %d,%d", position_.x_, position_.y_);
-
-    using namespace WindowPos;
-
-    VariantMap& eventData = GetEventDataMap();
-    eventData[P_X] = position_.x_;
-    eventData[P_Y] = position_.y_;
-    SendEvent(E_WINDOWPOS, eventData);
-}
-
-void Graphics::CleanupRenderSurface_OGL(RenderSurface* surface)
-{
-    if (!surface)
-        return;
-
-    // Flush pending FBO changes first if any
-    PrepareDraw_OGL();
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    unsigned currentFBO = impl->boundFBO_;
-
-    // Go through all FBOs and clean up the surface from them
+// Copyright (c) 2008-2022 the Urho3D project
+// License: MIT
+
+#include "../../Precompiled.h"
+
+#include "../../Core/Context.h"
+#include "../../Core/Mutex.h"
+#include "../../Core/ProcessUtils.h"
+#include "../../Core/Profiler.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/GraphicsEvents.h"
+#include "../../GraphicsAPI/ConstantBuffer.h"
+#include "../../GraphicsAPI/IndexBuffer.h"
+#include "../../GraphicsAPI/OpenGL/OGLGraphicsImpl.h"
+#include "../../GraphicsAPI/OpenGL/OGLShaderProgram.h"
+#include "../../GraphicsAPI/RenderSurface.h"
+#include "../../GraphicsAPI/Shader.h"
+#include "../../GraphicsAPI/ShaderPrecache.h"
+#include "../../GraphicsAPI/ShaderVariation.h"
+#include "../../GraphicsAPI/Texture2D.h"
+#include "../../GraphicsAPI/TextureCube.h"
+#include "../../GraphicsAPI/VertexBuffer.h"
+#include "../../IO/File.h"
+#include "../../IO/Log.h"
+#include "../../Resource/ResourceCache.h"
+
+#include <SDL/SDL.h>
+
+#include "../../DebugNew.h"
+
+#ifdef URHO3D_GLES2
+#ifndef GL_DEPTH_COMPONENT24
+#define GL_DEPTH_COMPONENT24 GL_DEPTH_COMPONENT24_OES
+#endif
+#endif
+
+#ifdef GL_ES_VERSION_2_0
+#define glClearDepth glClearDepthf
+#endif
+
+#ifdef GL_ES_VERSION_3_0
+#define GL_DEPTH24_STENCIL8_EXT GL_DEPTH24_STENCIL8
+#endif
+
+
+#ifdef __EMSCRIPTEN__
+#include "../../Input/Input.h"
+#include "../../UI/Cursor.h"
+#include "../../UI/UI.h"
+#include <emscripten/emscripten.h>
+#include <emscripten/bind.h>
+
+// Emscripten provides even all GL extension functions via static linking. However there is
+// no GLES2-specific extension header at the moment to include instanced rendering declarations,
+// so declare them manually from GLES3 gl2ext.h. Emscripten will provide these when linking final output.
+extern "C"
+{
+    GL_APICALL void GL_APIENTRY glDrawArraysInstancedANGLE (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
+    GL_APICALL void GL_APIENTRY glDrawElementsInstancedANGLE (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount);
+    GL_APICALL void GL_APIENTRY glVertexAttribDivisorANGLE (GLuint index, GLuint divisor);
+}
+
+// Helper functions to support emscripten canvas resolution change
+static const Urho3D::Context *appContext;
+
+static void JSCanvasSize(int width, int height, bool fullscreen, float scale)
+{
+    URHO3D_LOGINFOF("JSCanvasSize: width=%d height=%d fullscreen=%d ui scale=%f", width, height, fullscreen, scale);
+
+    using namespace Urho3D;
+
+    if (appContext)
+    {
+        bool uiCursorVisible = false;
+        bool systemCursorVisible = false;
+        MouseMode mouseMode{};
+
+        // Detect current system pointer state
+        Input* input = appContext->GetSubsystem<Input>();
+        if (input)
+        {
+            systemCursorVisible = input->IsMouseVisible();
+            mouseMode = input->GetMouseMode();
+        }
+
+        UI* ui = appContext->GetSubsystem<UI>();
+        if (ui)
+        {
+            ui->SetScale(scale);
+
+            // Detect current UI pointer state
+            Cursor* cursor = ui->GetCursor();
+            if (cursor)
+                uiCursorVisible = cursor->IsVisible();
+        }
+
+        // Apply new resolution
+        appContext->GetSubsystem<Graphics>()->SetMode(width, height);
+
+        // Reset the pointer state as it was before resolution change
+        if (input)
+        {
+            if (uiCursorVisible)
+                input->SetMouseVisible(false);
+            else
+                input->SetMouseVisible(systemCursorVisible);
+
+            input->SetMouseMode(mouseMode);
+        }
+
+        if (ui)
+        {
+            Cursor* cursor = ui->GetCursor();
+            if (cursor)
+            {
+                cursor->SetVisible(uiCursorVisible);
+
+                IntVector2 pos = input->GetMousePosition();
+                pos = ui->ConvertSystemToUI(pos);
+
+                cursor->SetPosition(pos);
+            }
+        }
+    }
+}
+
+using namespace emscripten;
+EMSCRIPTEN_BINDINGS(Module) {
+    function("JSCanvasSize", &JSCanvasSize);
+}
+#endif
+
+namespace Urho3D
+{
+
+static const GLenum glCmpFunc[] =
+{
+    GL_ALWAYS,
+    GL_EQUAL,
+    GL_NOTEQUAL,
+    GL_LESS,
+    GL_LEQUAL,
+    GL_GREATER,
+    GL_GEQUAL
+};
+
+static const GLenum glSrcBlend[] =
+{
+    GL_ONE,
+    GL_ONE,
+    GL_DST_COLOR,
+    GL_SRC_ALPHA,
+    GL_SRC_ALPHA,
+    GL_ONE,
+    GL_ONE_MINUS_DST_ALPHA,
+    GL_ONE,
+    GL_SRC_ALPHA
+};
+
+static const GLenum glDestBlend[] =
+{
+    GL_ZERO,
+    GL_ONE,
+    GL_ZERO,
+    GL_ONE_MINUS_SRC_ALPHA,
+    GL_ONE,
+    GL_ONE_MINUS_SRC_ALPHA,
+    GL_DST_ALPHA,
+    GL_ONE,
+    GL_ONE
+};
+
+static const GLenum glBlendOp[] =
+{
+    GL_FUNC_ADD,
+    GL_FUNC_ADD,
+    GL_FUNC_ADD,
+    GL_FUNC_ADD,
+    GL_FUNC_ADD,
+    GL_FUNC_ADD,
+    GL_FUNC_ADD,
+    GL_FUNC_REVERSE_SUBTRACT,
+    GL_FUNC_REVERSE_SUBTRACT
+};
+
+#ifndef GL_ES_VERSION_2_0
+static const GLenum glFillMode[] =
+{
+    GL_FILL,
+    GL_LINE,
+    GL_POINT
+};
+#endif
+#ifndef URHO3D_GLES2
+static const GLenum glStencilOps[] =
+{
+    GL_KEEP,
+    GL_ZERO,
+    GL_REPLACE,
+    GL_INCR_WRAP,
+    GL_DECR_WRAP
+};
+#endif
+
+static const GLenum glElementTypes[] =
+{
+    GL_INT,
+    GL_FLOAT,
+    GL_FLOAT,
+    GL_FLOAT,
+    GL_FLOAT,
+    GL_UNSIGNED_BYTE,
+    GL_UNSIGNED_BYTE
+};
+
+static const GLint glElementComponents[] =
+{
+    1,
+    1,
+    2,
+    3,
+    4,
+    4,
+    4
+};
+
+#ifdef URHO3D_GLES2
+static unsigned glesDepthStencilFormat = GL_DEPTH_COMPONENT16;
+static unsigned glesReadableDepthFormat = GL_DEPTH_COMPONENT;
+#endif
+
+static String extensions;
+
+bool CheckExtension(const String& name)
+{
+    if (extensions.Empty())
+        extensions = (const char*)glGetString(GL_EXTENSIONS);
+    return extensions.Contains(name);
+}
+
+static void GetGLPrimitiveType(unsigned elementCount, PrimitiveType type, unsigned& primitiveCount, GLenum& glPrimitiveType)
+{
+    switch (type)
+    {
+    case TRIANGLE_LIST:
+        primitiveCount = elementCount / 3;
+        glPrimitiveType = GL_TRIANGLES;
+        break;
+
+    case LINE_LIST:
+        primitiveCount = elementCount / 2;
+        glPrimitiveType = GL_LINES;
+        break;
+
+    case POINT_LIST:
+        primitiveCount = elementCount;
+        glPrimitiveType = GL_POINTS;
+        break;
+
+    case TRIANGLE_STRIP:
+        primitiveCount = elementCount - 2;
+        glPrimitiveType = GL_TRIANGLE_STRIP;
+        break;
+
+    case LINE_STRIP:
+        primitiveCount = elementCount - 1;
+        glPrimitiveType = GL_LINE_STRIP;
+        break;
+
+    case TRIANGLE_FAN:
+        primitiveCount = elementCount - 2;
+        glPrimitiveType = GL_TRIANGLE_FAN;
+        break;
+    }
+}
+
+void Graphics::Constructor_OGL()
+{
+    impl_ = new GraphicsImpl_OGL();
+    position_ = IntVector2(SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED);
+    shadowMapFormat_ = GL_DEPTH_COMPONENT16;
+    hiresShadowMapFormat_ = GL_DEPTH_COMPONENT24;
+    shaderPath_ = "Shaders/GLSL/";
+    shaderExtension_ = ".glsl";
+    orientations_ = "LandscapeLeft LandscapeRight";
+#ifndef GL_ES_VERSION_2_0
+    apiName_ = "GL2";
+#else
+    apiName_ = "GLES2";
+#endif
+
+    Graphics::gl3Support = false;
+
+    SetTextureUnitMappings_OGL();
+    ResetCachedState_OGL();
+
+    context_->RequireSDL(SDL_INIT_VIDEO);
+
+    // Register Graphics library object factories
+    RegisterGraphicsLibrary(context_);
+
+#ifdef __EMSCRIPTEN__
+    appContext = context_;
+#endif
+}
+
+void Graphics::Destructor_OGL()
+{
+    Close_OGL();
+
+    delete static_cast<GraphicsImpl_OGL*>(impl_);
+    impl_ = nullptr;
+
+    context_->ReleaseSDL();
+}
+
+bool Graphics::SetScreenMode_OGL(int width, int height, const ScreenModeParams& params, bool maximize)
+{
+    URHO3D_PROFILE(SetScreenMode_OGL);
+
+    // Ensure that parameters are properly filled
+    ScreenModeParams newParams = params;
+    AdjustScreenMode(width, height, newParams, maximize);
+
+    if (IsInitialized_OGL() && width == width_ && height == height_ && screenParams_ == newParams)
+        return true;
+
+    // If only vsync changes, do not destroy/recreate the context
+    if (IsInitialized_OGL() && width == width_ && height == height_
+        && screenParams_.EqualsExceptVSync(newParams) && screenParams_.vsync_ != newParams.vsync_)
+    {
+        SDL_GL_SetSwapInterval(newParams.vsync_ ? 1 : 0);
+        screenParams_.vsync_ = newParams.vsync_;
+        return true;
+    }
+
+    // Track if the window was repositioned and don't update window position in this case
+    bool reposition = false;
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    // With an external window, only the size can change after initial setup, so do not recreate context
+    if (!externalWindow_ || !impl->context_)
+    {
+        // Close the existing window and OpenGL context, mark GPU objects as lost
+        Release_OGL(false, true);
+
+        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+
+#ifndef GL_ES_VERSION_2_0
+        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+
+        if (externalWindow_)
+            SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
+        else
+            SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
+
+        SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
+
+        if (!forceGL2_)
+        {
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+            apiName_ = "GL3";
+        }
+        else
+        {
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
+            apiName_ = "GL2";
+        }
+#else
+#if defined(GL_ES_VERSION_3_0)
+        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+        apiName_ = "GLES3";
+        gl3Support = true;
+#else
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+        apiName_ = "GLES2";
+#endif
+        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+#endif
+
+        SDL_Rect display_rect;
+        SDL_GetDisplayBounds(newParams.monitor_, &display_rect);
+        reposition = newParams.fullscreen_ || (newParams.borderless_ && width >= display_rect.w && height >= display_rect.h);
+
+        const int x = reposition ? display_rect.x : position_.x_;
+        const int y = reposition ? display_rect.y : position_.y_;
+
+        unsigned flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
+        if (newParams.fullscreen_)
+            flags |= SDL_WINDOW_FULLSCREEN;
+        if (newParams.borderless_)
+            flags |= SDL_WINDOW_BORDERLESS;
+        if (newParams.resizable_)
+            flags |= SDL_WINDOW_RESIZABLE;
+
+#ifndef __EMSCRIPTEN__
+        if (newParams.highDPI_)
+            flags |= SDL_WINDOW_ALLOW_HIGHDPI;
+#endif
+
+        SDL_SetHint(SDL_HINT_ORIENTATIONS, orientations_.CString());
+
+        // Try 24-bit depth first, fallback to 16-bit
+        for (const int depthSize : { 24, 16 })
+        {
+            SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthSize);
+
+            // Try requested multisample level first, fallback to lower levels and no multisample
+            for (int multiSample = newParams.multiSample_; multiSample > 0; multiSample /= 2)
+            {
+                if (multiSample > 1)
+                {
+                    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+                    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multiSample);
+                }
+                else
+                {
+                    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+                    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
+                }
+
+                if (!externalWindow_)
+                    window_ = SDL_CreateWindow(windowTitle_.CString(), x, y, width, height, flags);
+                else
+                {
+    #ifndef __EMSCRIPTEN__
+                    if (!window_)
+                        window_ = SDL_CreateWindowFrom(externalWindow_, SDL_WINDOW_OPENGL);
+                    newParams.fullscreen_ = false;
+    #endif
+                }
+
+                if (window_)
+                {
+                    // TODO: We probably want to keep depthSize as well
+                    newParams.multiSample_ = multiSample;
+                    break;
+                }
+            }
+
+            if (window_)
+                break;
+        }
+
+        if (!window_)
+        {
+            URHO3D_LOGERRORF("Could not create window, root cause: '%s'", SDL_GetError());
+            return false;
+        }
+
+        // Reposition the window on the specified monitor
+        if (reposition)
+            SDL_SetWindowPosition(window_, display_rect.x, display_rect.y);
+
+        CreateWindowIcon();
+
+        if (maximize)
+        {
+            Maximize();
+            SDL_GL_GetDrawableSize(window_, &width, &height);
+        }
+
+        // Create/restore context and GPU objects and set initial renderstate
+        Restore_OGL();
+
+        // Specific error message is already logged by Restore_OGL() when context creation or OpenGL extensions check fails
+        if (!impl->context_)
+            return false;
+    }
+
+    // Set vsync
+    SDL_GL_SetSwapInterval(newParams.vsync_ ? 1 : 0);
+
+    // Store the system FBO on iOS/tvOS now
+#if defined(IOS) || defined(TVOS)
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl->systemFBO_);
+#endif
+
+    screenParams_ = newParams;
+
+    SDL_GL_GetDrawableSize(window_, &width_, &height_);
+    if (!reposition)
+        SDL_GetWindowPosition(window_, &position_.x_, &position_.y_);
+
+    int logicalWidth, logicalHeight;
+    SDL_GetWindowSize(window_, &logicalWidth, &logicalHeight);
+    screenParams_.highDPI_ = (width_ != logicalWidth) || (height_ != logicalHeight);
+
+    // Reset rendertargets and viewport for the new screen mode
+    ResetRenderTargets_OGL();
+
+    // Clear the initial window contents to black
+    Clear_OGL(CLEAR_COLOR);
+    SDL_GL_SwapWindow(window_);
+
+    CheckFeatureSupport_OGL();
+
+#ifdef URHO3D_LOGGING
+    URHO3D_LOGINFOF("Adapter used %s %s", (const char *) glGetString(GL_VENDOR), (const char *) glGetString(GL_RENDERER));
+#endif
+
+    OnScreenModeChanged();
+    return true;
+}
+
+void Graphics::SetSRGB_OGL(bool enable)
+{
+    enable &= sRGBWriteSupport_;
+
+    if (enable != sRGB_)
+    {
+        sRGB_ = enable;
+        GetImpl_OGL()->fboDirty_ = true;
+    }
+}
+
+void Graphics::SetDither_OGL(bool enable)
+{
+    if (enable)
+        glEnable(GL_DITHER);
+    else
+        glDisable(GL_DITHER);
+}
+
+void Graphics::SetFlushGPU_OGL(bool enable)
+{
+    // Currently unimplemented on OpenGL
+}
+
+void Graphics::SetForceGL2_OGL(bool enable)
+{
+    if (IsInitialized_OGL())
+    {
+        URHO3D_LOGERROR("OpenGL 2 can only be forced before setting the initial screen mode");
+        return;
+    }
+
+    forceGL2_ = enable;
+}
+
+void Graphics::Close_OGL()
+{
+    if (!IsInitialized_OGL())
+        return;
+
+    // Actually close the window
+    Release_OGL(true, true);
+}
+
+bool Graphics::TakeScreenShot_OGL(Image& destImage)
+{
+    URHO3D_PROFILE(TakeScreenShot_OGL);
+
+    if (!IsInitialized_OGL())
+        return false;
+
+    if (IsDeviceLost_OGL())
+    {
+        URHO3D_LOGERROR("Can not take screenshot while device is lost");
+        return false;
+    }
+
+    ResetRenderTargets_OGL();
+
+#ifndef GL_ES_VERSION_2_0
+    destImage.SetSize(width_, height_, 3);
+    glReadPixels(0, 0, width_, height_, GL_RGB, GL_UNSIGNED_BYTE, destImage.GetData());
+#else
+    // Use RGBA format on OpenGL ES, as otherwise (at least on Android) the produced image is all black
+    destImage.SetSize(width_, height_, 4);
+    glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, destImage.GetData());
+#endif
+
+    // On OpenGL we need to flip the image vertically after reading
+    destImage.FlipVertical();
+
+    return true;
+}
+
+bool Graphics::BeginFrame_OGL()
+{
+    if (!IsInitialized_OGL() || IsDeviceLost_OGL())
+        return false;
+
+    // If using an external window, check it for size changes, and reset screen mode if necessary
+    if (externalWindow_)
+    {
+        int width, height;
+
+        SDL_GL_GetDrawableSize(window_, &width, &height);
+        if (width != width_ || height != height_)
+            SetMode(width, height);
+    }
+
+    // Re-enable depth test and depth func in case a third party program has modified it
+    glEnable(GL_DEPTH_TEST);
+    glDepthFunc(glCmpFunc[depthTestMode_]);
+
+    // Set default rendertarget and depth buffer
+    ResetRenderTargets_OGL();
+
+    // Cleanup textures from previous frame
+    for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
+        SetTexture_OGL(i, nullptr);
+
+    // Enable color and depth write
+    SetColorWrite_OGL(true);
+    SetDepthWrite_OGL(true);
+
+    numPrimitives_ = 0;
+    numBatches_ = 0;
+
+    SendEvent(E_BEGINRENDERING);
+
+    return true;
+}
+
+void Graphics::EndFrame_OGL()
+{
+    if (!IsInitialized_OGL())
+        return;
+
+    URHO3D_PROFILE(Present);
+
+    SendEvent(E_ENDRENDERING);
+
+    SDL_GL_SwapWindow(window_);
+
+    // Clean up too large scratch buffers
+    CleanupScratchBuffers();
+}
+
+void Graphics::Clear_OGL(ClearTargetFlags flags, const Color& color, float depth, u32 stencil)
+{
+    PrepareDraw_OGL();
+
+#ifdef URHO3D_GLES2
+    flags &= ~CLEAR_STENCIL;
+#endif
+
+    bool oldColorWrite = colorWrite_;
+    bool oldDepthWrite = depthWrite_;
+
+    if (flags & CLEAR_COLOR && !oldColorWrite)
+        SetColorWrite_OGL(true);
+    if (flags & CLEAR_DEPTH && !oldDepthWrite)
+        SetDepthWrite_OGL(true);
+    if (flags & CLEAR_STENCIL && stencilWriteMask_ != M_U32_MASK_ALL_BITS)
+        glStencilMask(M_U32_MASK_ALL_BITS);
+
+    GLbitfield glFlags = 0;
+    if (flags & CLEAR_COLOR)
+    {
+        glFlags |= GL_COLOR_BUFFER_BIT;
+        glClearColor(color.r_, color.g_, color.b_, color.a_);
+    }
+    if (flags & CLEAR_DEPTH)
+    {
+        glFlags |= GL_DEPTH_BUFFER_BIT;
+        glClearDepth(depth);
+    }
+    if (flags & CLEAR_STENCIL)
+    {
+        glFlags |= GL_STENCIL_BUFFER_BIT;
+        glClearStencil((GLint)stencil);
+    }
+
+    // If viewport is less than full screen, set a scissor to limit the clear
+    /// \todo Any user-set scissor test will be lost
+    IntVector2 viewSize = GetRenderTargetDimensions_OGL();
+    if (viewport_.left_ != 0 || viewport_.top_ != 0 || viewport_.right_ != viewSize.x_ || viewport_.bottom_ != viewSize.y_)
+        SetScissorTest_OGL(true, IntRect(0, 0, viewport_.Width(), viewport_.Height()));
+    else
+        SetScissorTest_OGL(false);
+
+    glClear(glFlags);
+
+    SetScissorTest_OGL(false);
+    SetColorWrite_OGL(oldColorWrite);
+    SetDepthWrite_OGL(oldDepthWrite);
+    if (flags & CLEAR_STENCIL && stencilWriteMask_ != M_U32_MASK_ALL_BITS)
+        glStencilMask(stencilWriteMask_);
+}
+
+bool Graphics::ResolveToTexture_OGL(Texture2D* destination, const IntRect& viewport)
+{
+    if (!destination || !destination->GetRenderSurface())
+        return false;
+
+    URHO3D_PROFILE(ResolveToTexture_OGL);
+
+    IntRect vpCopy = viewport;
+    if (vpCopy.right_ <= vpCopy.left_)
+        vpCopy.right_ = vpCopy.left_ + 1;
+    if (vpCopy.bottom_ <= vpCopy.top_)
+        vpCopy.bottom_ = vpCopy.top_ + 1;
+    vpCopy.left_ = Clamp(vpCopy.left_, 0, width_);
+    vpCopy.top_ = Clamp(vpCopy.top_, 0, height_);
+    vpCopy.right_ = Clamp(vpCopy.right_, 0, width_);
+    vpCopy.bottom_ = Clamp(vpCopy.bottom_, 0, height_);
+
+    // Make sure the FBO is not in use
+    ResetRenderTargets_OGL();
+
+    // Use Direct3D convention with the vertical coordinates ie. 0 is top
+    SetTextureForUpdate_OGL(destination);
+    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, vpCopy.left_, height_ - vpCopy.bottom_, vpCopy.Width(), vpCopy.Height());
+    SetTexture_OGL(0, nullptr);
+
+    return true;
+}
+
+bool Graphics::ResolveToTexture_OGL(Texture2D* texture)
+{
+#ifndef URHO3D_GLES2
+    if (!texture)
+        return false;
+    RenderSurface* surface = texture->GetRenderSurface();
+    if (!surface || !surface->GetRenderBuffer())
+        return false;
+
+    URHO3D_PROFILE(ResolveToTexture_OGL);
+
+    texture->SetResolveDirty(false);
+    surface->SetResolveDirty(false);
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    // Use separate FBOs for resolve to not disturb the currently set rendertarget(s)
+    if (!impl->resolveSrcFBO_)
+        impl->resolveSrcFBO_ = CreateFramebuffer_OGL();
+    if (!impl->resolveDestFBO_)
+        impl->resolveDestFBO_ = CreateFramebuffer_OGL();
+#ifndef GL_ES_VERSION_3_0
+    if (!gl3Support)
+    {
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, impl->resolveSrcFBO_);
+        glFramebufferRenderbufferEXT(GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT,
+            surface->GetRenderBuffer());
+        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, impl->resolveDestFBO_);
+        glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texture->GetGPUObjectName(),
+            0);
+        glBlitFramebufferEXT(0, 0, texture->GetWidth(), texture->GetHeight(), 0, 0, texture->GetWidth(), texture->GetHeight(),
+            GL_COLOR_BUFFER_BIT, GL_NEAREST);
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
+        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
+    }
+    else
+#endif
+    {
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, impl->resolveSrcFBO_);
+        glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, surface->GetRenderBuffer());
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, impl->resolveDestFBO_);
+        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->GetGPUObjectName(), 0);
+        glBlitFramebuffer(0, 0, texture->GetWidth(), texture->GetHeight(), 0, 0, texture->GetWidth(), texture->GetHeight(),
+            GL_COLOR_BUFFER_BIT, GL_NEAREST);
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+    }
+
+    // Restore previously bound FBO
+    BindFramebuffer_OGL(impl->boundFBO_);
+    return true;
+#else
+    // Not supported on GLES 2
+    return false;
+#endif
+}
+
+bool Graphics::ResolveToTexture_OGL(TextureCube* texture)
+{
+#ifndef URHO3D_GLES2
+    if (!texture)
+        return false;
+
+    URHO3D_PROFILE(ResolveToTexture_OGL);
+
+    texture->SetResolveDirty(false);
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    // Use separate FBOs for resolve to not disturb the currently set rendertarget(s)
+    if (!impl->resolveSrcFBO_)
+        impl->resolveSrcFBO_ = CreateFramebuffer_OGL();
+    if (!impl->resolveDestFBO_)
+        impl->resolveDestFBO_ = CreateFramebuffer_OGL();
+
+#ifndef GL_ES_VERSION_3_0
+    if (!gl3Support)
+    {
+        for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
+        {
+            // Resolve only the surface(s) that were actually rendered to
+            RenderSurface* surface = texture->GetRenderSurface((CubeMapFace)i);
+            if (!surface->IsResolveDirty())
+                continue;
+
+            surface->SetResolveDirty(false);
+            glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, impl->resolveSrcFBO_);
+            glFramebufferRenderbufferEXT(GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT,
+                surface->GetRenderBuffer());
+            glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, impl->resolveDestFBO_);
+            glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
+                texture->GetGPUObjectName(), 0);
+            glBlitFramebufferEXT(0, 0, texture->GetWidth(), texture->GetHeight(), 0, 0, texture->GetWidth(), texture->GetHeight(),
+                GL_COLOR_BUFFER_BIT, GL_NEAREST);
+        }
+
+        glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
+        glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
+    }
+    else
+#endif
+    {
+        for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
+        {
+            RenderSurface* surface = texture->GetRenderSurface((CubeMapFace)i);
+            if (!surface->IsResolveDirty())
+                continue;
+
+            surface->SetResolveDirty(false);
+            glBindFramebuffer(GL_READ_FRAMEBUFFER, impl->resolveSrcFBO_);
+            glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, surface->GetRenderBuffer());
+            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, impl->resolveDestFBO_);
+            glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
+                texture->GetGPUObjectName(), 0);
+            glBlitFramebuffer(0, 0, texture->GetWidth(), texture->GetHeight(), 0, 0, texture->GetWidth(), texture->GetHeight(),
+                GL_COLOR_BUFFER_BIT, GL_NEAREST);
+        }
+
+        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+    }
+
+    // Restore previously bound FBO
+    BindFramebuffer_OGL(impl->boundFBO_);
+    return true;
+#else
+    // Not supported on GLES
+    return false;
+#endif
+}
+
+void Graphics::Draw_OGL(PrimitiveType type, unsigned vertexStart, unsigned vertexCount)
+{
+    if (!vertexCount)
+        return;
+
+    PrepareDraw_OGL();
+
+    unsigned primitiveCount;
+    GLenum glPrimitiveType;
+
+    GetGLPrimitiveType(vertexCount, type, primitiveCount, glPrimitiveType);
+    glDrawArrays(glPrimitiveType, vertexStart, vertexCount);
+
+    numPrimitives_ += primitiveCount;
+    ++numBatches_;
+}
+
+void Graphics::Draw_OGL(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned minVertex, unsigned vertexCount)
+{
+    if (!indexCount || !indexBuffer_ || !indexBuffer_->GetGPUObjectName())
+        return;
+
+    PrepareDraw_OGL();
+
+    unsigned indexSize = indexBuffer_->GetIndexSize();
+    unsigned primitiveCount;
+    GLenum glPrimitiveType;
+
+    GetGLPrimitiveType(indexCount, type, primitiveCount, glPrimitiveType);
+    GLenum indexType = indexSize == sizeof(unsigned short) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
+    intptr_t offset = (intptr_t)indexStart * indexSize;
+    glDrawElements(glPrimitiveType, indexCount, indexType, (const void*)offset);
+
+    numPrimitives_ += primitiveCount;
+    ++numBatches_;
+}
+
+void Graphics::Draw_OGL(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned baseVertexIndex, unsigned minVertex, unsigned vertexCount)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support || !indexCount || !indexBuffer_ || !indexBuffer_->GetGPUObjectName())
+        return;
+
+    PrepareDraw_OGL();
+
+    unsigned indexSize = indexBuffer_->GetIndexSize();
+    unsigned primitiveCount;
+    GLenum glPrimitiveType;
+
+    GetGLPrimitiveType(indexCount, type, primitiveCount, glPrimitiveType);
+    GLenum indexType = indexSize == sizeof(unsigned short) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
+    intptr_t offset = (intptr_t)indexStart * indexSize;
+    glDrawElementsBaseVertex(glPrimitiveType, indexCount, indexType, (const void*)offset, baseVertexIndex);
+
+    numPrimitives_ += primitiveCount;
+    ++numBatches_;
+#endif
+}
+
+void Graphics::DrawInstanced_OGL(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned minVertex, unsigned vertexCount,
+    unsigned instanceCount)
+{
+#if !defined(URHO3D_GLES2) || defined(__EMSCRIPTEN__)
+    if (!indexCount || !indexBuffer_ || !indexBuffer_->GetGPUObjectName() || !instancingSupport_)
+        return;
+
+    PrepareDraw_OGL();
+
+    unsigned indexSize = indexBuffer_->GetIndexSize();
+    unsigned primitiveCount;
+    GLenum glPrimitiveType;
+
+    GetGLPrimitiveType(indexCount, type, primitiveCount, glPrimitiveType);
+    GLenum indexType = indexSize == sizeof(unsigned short) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
+    intptr_t offset = (intptr_t)indexStart * indexSize;
+#ifdef __EMSCRIPTEN__
+    glDrawElementsInstancedANGLE(glPrimitiveType, indexCount, indexType, (const void*)offset, instanceCount);
+#else
+    if (gl3Support)
+    {
+        glDrawElementsInstanced(glPrimitiveType, indexCount, indexType, (const void*)offset, instanceCount);
+    }
+#ifndef GL_ES_VERSION_3_0
+    else
+    {
+        glDrawElementsInstancedARB(glPrimitiveType, indexCount, indexType, (const void*)offset, instanceCount);
+    }
+#endif
+#endif
+
+    numPrimitives_ += instanceCount * primitiveCount;
+    ++numBatches_;
+#endif
+}
+
+void Graphics::DrawInstanced_OGL(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned baseVertexIndex, unsigned minVertex,
+        unsigned vertexCount, unsigned instanceCount)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support || !indexCount || !indexBuffer_ || !indexBuffer_->GetGPUObjectName() || !instancingSupport_)
+        return;
+
+    PrepareDraw_OGL();
+
+    unsigned indexSize = indexBuffer_->GetIndexSize();
+    unsigned primitiveCount;
+    GLenum glPrimitiveType;
+
+    GetGLPrimitiveType(indexCount, type, primitiveCount, glPrimitiveType);
+    GLenum indexType = indexSize == sizeof(unsigned short) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
+    intptr_t offset = (intptr_t)indexStart * indexSize;
+    glDrawElementsInstancedBaseVertex(glPrimitiveType, indexCount, indexType, (const void*)offset, instanceCount, baseVertexIndex);
+
+    numPrimitives_ += instanceCount * primitiveCount;
+    ++numBatches_;
+#endif
+}
+
+void Graphics::SetVertexBuffer_OGL(VertexBuffer* buffer)
+{
+    // Note: this is not multi-instance safe
+    static Vector<VertexBuffer*> vertexBuffers(1);
+    vertexBuffers[0] = buffer;
+    SetVertexBuffers_OGL(vertexBuffers);
+}
+
+bool Graphics::SetVertexBuffers_OGL(const Vector<VertexBuffer*>& buffers, unsigned instanceOffset)
+{
+    if (buffers.Size() > MAX_VERTEX_STREAMS)
+    {
+        URHO3D_LOGERROR("Too many vertex buffers");
+        return false;
+    }
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (instanceOffset != impl->lastInstanceOffset_)
+    {
+        impl->lastInstanceOffset_ = instanceOffset;
+        impl->vertexBuffersDirty_ = true;
+    }
+
+    for (i32 i = 0; i < MAX_VERTEX_STREAMS; ++i)
+    {
+        VertexBuffer* buffer = nullptr;
+        if (i < buffers.Size())
+            buffer = buffers[i];
+        if (buffer != vertexBuffers_[i])
+        {
+            vertexBuffers_[i] = buffer;
+            impl->vertexBuffersDirty_ = true;
+        }
+    }
+
+    return true;
+}
+
+bool Graphics::SetVertexBuffers_OGL(const Vector<SharedPtr<VertexBuffer>>& buffers, unsigned instanceOffset)
+{
+    return SetVertexBuffers_OGL(reinterpret_cast<const Vector<VertexBuffer*>&>(buffers), instanceOffset);
+}
+
+void Graphics::SetIndexBuffer_OGL(IndexBuffer* buffer)
+{
+    if (indexBuffer_ == buffer)
+        return;
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer ? buffer->GetGPUObjectName() : 0);
+    indexBuffer_ = buffer;
+}
+
+void Graphics::SetShaders_OGL(ShaderVariation* vs, ShaderVariation* ps)
+{
+    if (vs == vertexShader_ && ps == pixelShader_)
+        return;
+
+    // Compile the shaders now if not yet compiled. If already attempted, do not retry
+    if (vs && !vs->GetGPUObjectName())
+    {
+        if (vs->GetCompilerOutput().Empty())
+        {
+            URHO3D_PROFILE(CompileVertexShader);
+
+            bool success = vs->Create();
+            if (success)
+                URHO3D_LOGDEBUG("Compiled vertex shader " + vs->GetFullName());
+            else
+            {
+                URHO3D_LOGERROR("Failed to compile vertex shader " + vs->GetFullName() + ":\n" + vs->GetCompilerOutput());
+                vs = nullptr;
+            }
+        }
+        else
+            vs = nullptr;
+    }
+
+    if (ps && !ps->GetGPUObjectName())
+    {
+        if (ps->GetCompilerOutput().Empty())
+        {
+            URHO3D_PROFILE(CompilePixelShader);
+
+            bool success = ps->Create();
+            if (success)
+                URHO3D_LOGDEBUG("Compiled pixel shader " + ps->GetFullName());
+            else
+            {
+                URHO3D_LOGERROR("Failed to compile pixel shader " + ps->GetFullName() + ":\n" + ps->GetCompilerOutput());
+                ps = nullptr;
+            }
+        }
+        else
+            ps = nullptr;
+    }
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (!vs || !ps)
+    {
+        glUseProgram(0);
+        vertexShader_ = nullptr;
+        pixelShader_ = nullptr;
+        impl->shaderProgram_ = nullptr;
+    }
+    else
+    {
+        vertexShader_ = vs;
+        pixelShader_ = ps;
+
+        Pair<ShaderVariation*, ShaderVariation*> combination(vs, ps);
+        ShaderProgramMap_OGL::Iterator i = impl->shaderPrograms_.Find(combination);
+
+        if (i != impl->shaderPrograms_.End())
+        {
+            // Use the existing linked program
+            if (i->second_->GetGPUObjectName())
+            {
+                glUseProgram(i->second_->GetGPUObjectName());
+                impl->shaderProgram_ = i->second_;
+            }
+            else
+            {
+                glUseProgram(0);
+                impl->shaderProgram_ = nullptr;
+            }
+        }
+        else
+        {
+            // Link a new combination
+            URHO3D_PROFILE(LinkShaders);
+
+            SharedPtr<ShaderProgram_OGL> newProgram(new ShaderProgram_OGL(this, vs, ps));
+            if (newProgram->Link())
+            {
+                URHO3D_LOGDEBUG("Linked vertex shader " + vs->GetFullName() + " and pixel shader " + ps->GetFullName());
+                // Note: Link() calls glUseProgram() to set the texture sampler uniforms,
+                // so it is not necessary to call it again
+                impl->shaderProgram_ = newProgram;
+            }
+            else
+            {
+                URHO3D_LOGERROR("Failed to link vertex shader " + vs->GetFullName() + " and pixel shader " + ps->GetFullName() + ":\n" +
+                         newProgram->GetLinkerOutput());
+                glUseProgram(0);
+                impl->shaderProgram_ = nullptr;
+            }
+
+            impl->shaderPrograms_[combination] = newProgram;
+        }
+    }
+
+    // Update the clip plane uniform on GL3, and set constant buffers
+#ifndef URHO3D_GLES2
+    if (gl3Support && impl->shaderProgram_)
+    {
+        const SharedPtr<ConstantBuffer>* constantBuffers = impl->shaderProgram_->GetConstantBuffers();
+        for (unsigned i = 0; i < MAX_SHADER_PARAMETER_GROUPS * 2; ++i)
+        {
+            ConstantBuffer* buffer = constantBuffers[i].Get();
+            if (buffer != impl->constantBuffers_[i])
+            {
+                unsigned object = buffer ? buffer->GetGPUObjectName() : 0;
+                glBindBufferBase(GL_UNIFORM_BUFFER, i, object);
+                // Calling glBindBufferBase also affects the generic buffer binding point
+                impl->boundUBO_ = object;
+                impl->constantBuffers_[i] = buffer;
+                ShaderProgram_OGL::ClearGlobalParameterSource((ShaderParameterGroup)(i % MAX_SHADER_PARAMETER_GROUPS));
+            }
+        }
+
+        SetShaderParameter_OGL(VSP_CLIPPLANE, useClipPlane_ ? clipPlane_ : Vector4(0.0f, 0.0f, 0.0f, 1.0f));
+    }
+#endif
+
+    // Store shader combination if shader dumping in progress
+    if (shaderPrecache_)
+        shaderPrecache_->StoreShaders(vertexShader_, pixelShader_);
+
+    if (impl->shaderProgram_)
+    {
+        impl->usedVertexAttributes_ = impl->shaderProgram_->GetUsedVertexAttributes();
+        impl->vertexAttributes_ = &impl->shaderProgram_->GetVertexAttributes();
+    }
+    else
+    {
+        impl->usedVertexAttributes_ = 0;
+        impl->vertexAttributes_ = nullptr;
+    }
+
+    impl->vertexBuffersDirty_ = true;
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, const float* data, unsigned count)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, (unsigned)(count * sizeof(float)), data);
+                return;
+            }
+
+            switch (info->glType_)
+            {
+            case GL_FLOAT:
+                glUniform1fv(info->location_, count, data);
+                break;
+
+            case GL_FLOAT_VEC2:
+                glUniform2fv(info->location_, count / 2, data);
+                break;
+
+            case GL_FLOAT_VEC3:
+                glUniform3fv(info->location_, count / 3, data);
+                break;
+
+            case GL_FLOAT_VEC4:
+                glUniform4fv(info->location_, count / 4, data);
+                break;
+
+            case GL_FLOAT_MAT3:
+                glUniformMatrix3fv(info->location_, count / 9, GL_FALSE, data);
+                break;
+
+            case GL_FLOAT_MAT4:
+                glUniformMatrix4fv(info->location_, count / 16, GL_FALSE, data);
+                break;
+
+            default: break;
+            }
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, float value)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, sizeof(float), &value);
+                return;
+            }
+
+            glUniform1fv(info->location_, 1, &value);
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, int value)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, sizeof(int), &value);
+                return;
+            }
+
+            glUniform1i(info->location_, value);
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, bool value)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    // \todo Not tested
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, sizeof(bool), &value);
+                return;
+            }
+
+            glUniform1i(info->location_, (int)value);
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, const Color& color)
+{
+    SetShaderParameter_OGL(param, color.Data(), 4);
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, const Vector2& vector)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, sizeof(Vector2), &vector);
+                return;
+            }
+
+            // Check the uniform type to avoid mismatch
+            switch (info->glType_)
+            {
+            case GL_FLOAT:
+                glUniform1fv(info->location_, 1, vector.Data());
+                break;
+
+            case GL_FLOAT_VEC2:
+                glUniform2fv(info->location_, 1, vector.Data());
+                break;
+
+            default: break;
+            }
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, const Matrix3& matrix)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetVector3ArrayParameter(info->offset_, 3, &matrix);
+                return;
+            }
+
+            glUniformMatrix3fv(info->location_, 1, GL_FALSE, matrix.Data());
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, const Vector3& vector)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, sizeof(Vector3), &vector);
+                return;
+            }
+
+            // Check the uniform type to avoid mismatch
+            switch (info->glType_)
+            {
+            case GL_FLOAT:
+                glUniform1fv(info->location_, 1, vector.Data());
+                break;
+
+            case GL_FLOAT_VEC2:
+                glUniform2fv(info->location_, 1, vector.Data());
+                break;
+
+            case GL_FLOAT_VEC3:
+                glUniform3fv(info->location_, 1, vector.Data());
+                break;
+
+            default: break;
+            }
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, const Matrix4& matrix)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, sizeof(Matrix4), &matrix);
+                return;
+            }
+
+            glUniformMatrix4fv(info->location_, 1, GL_FALSE, matrix.Data());
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, const Vector4& vector)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, sizeof(Vector4), &vector);
+                return;
+            }
+
+            // Check the uniform type to avoid mismatch
+            switch (info->glType_)
+            {
+            case GL_FLOAT:
+                glUniform1fv(info->location_, 1, vector.Data());
+                break;
+
+            case GL_FLOAT_VEC2:
+                glUniform2fv(info->location_, 1, vector.Data());
+                break;
+
+            case GL_FLOAT_VEC3:
+                glUniform3fv(info->location_, 1, vector.Data());
+                break;
+
+            case GL_FLOAT_VEC4:
+                glUniform4fv(info->location_, 1, vector.Data());
+                break;
+
+            default: break;
+            }
+        }
+    }
+}
+
+void Graphics::SetShaderParameter_OGL(StringHash param, const Matrix3x4& matrix)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->shaderProgram_)
+    {
+        const ShaderParameter* info = impl->shaderProgram_->GetParameter(param);
+        if (info)
+        {
+            // Expand to a full Matrix4
+            static Matrix4 fullMatrix;
+            fullMatrix.m00_ = matrix.m00_;
+            fullMatrix.m01_ = matrix.m01_;
+            fullMatrix.m02_ = matrix.m02_;
+            fullMatrix.m03_ = matrix.m03_;
+            fullMatrix.m10_ = matrix.m10_;
+            fullMatrix.m11_ = matrix.m11_;
+            fullMatrix.m12_ = matrix.m12_;
+            fullMatrix.m13_ = matrix.m13_;
+            fullMatrix.m20_ = matrix.m20_;
+            fullMatrix.m21_ = matrix.m21_;
+            fullMatrix.m22_ = matrix.m22_;
+            fullMatrix.m23_ = matrix.m23_;
+
+            if (info->bufferPtr_)
+            {
+                ConstantBuffer* buffer = info->bufferPtr_;
+                if (!buffer->IsDirty())
+                    impl->dirtyConstantBuffers_.Push(buffer);
+                buffer->SetParameter(info->offset_, sizeof(Matrix4), &fullMatrix);
+                return;
+            }
+
+            glUniformMatrix4fv(info->location_, 1, GL_FALSE, fullMatrix.Data());
+        }
+    }
+}
+
+bool Graphics::NeedParameterUpdate_OGL(ShaderParameterGroup group, const void* source)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+    return impl->shaderProgram_ ? impl->shaderProgram_->NeedParameterUpdate(group, source) : false;
+}
+
+bool Graphics::HasShaderParameter_OGL(StringHash param)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+    return impl->shaderProgram_ && impl->shaderProgram_->HasParameter(param);
+}
+
+bool Graphics::HasTextureUnit_OGL(TextureUnit unit)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+    return impl->shaderProgram_ && impl->shaderProgram_->HasTextureUnit(unit);
+}
+
+void Graphics::ClearParameterSource_OGL(ShaderParameterGroup group)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+    if (impl->shaderProgram_)
+        impl->shaderProgram_->ClearParameterSource(group);
+}
+
+void Graphics::ClearParameterSources_OGL()
+{
+    ShaderProgram_OGL::ClearParameterSources();
+}
+
+void Graphics::ClearTransformSources_OGL()
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+    if (impl->shaderProgram_)
+    {
+        impl->shaderProgram_->ClearParameterSource(SP_CAMERA);
+        impl->shaderProgram_->ClearParameterSource(SP_OBJECT);
+    }
+}
+
+void Graphics::SetTexture_OGL(unsigned index, Texture* texture)
+{
+    if (index >= MAX_TEXTURE_UNITS)
+        return;
+
+    // Check if texture is currently bound as a rendertarget. In that case, use its backup texture, or blank if not defined
+    if (texture)
+    {
+        if (renderTargets_[0] && renderTargets_[0]->GetParentTexture() == texture)
+            texture = texture->GetBackupTexture();
+        else
+        {
+            // Resolve multisampled texture now as necessary
+            if (texture->GetMultiSample() > 1 && texture->GetAutoResolve() && texture->IsResolveDirty())
+            {
+                if (texture->GetType() == Texture2D::GetTypeStatic())
+                    ResolveToTexture_OGL(static_cast<Texture2D*>(texture));
+                if (texture->GetType() == TextureCube::GetTypeStatic())
+                    ResolveToTexture_OGL(static_cast<TextureCube*>(texture));
+            }
+        }
+    }
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (textures_[index] != texture)
+    {
+        if (impl->activeTexture_ != index)
+        {
+            glActiveTexture(GL_TEXTURE0 + index);
+            impl->activeTexture_ = index;
+        }
+
+        if (texture)
+        {
+            unsigned glType = texture->GetTarget();
+            // Unbind old texture type if necessary
+            if (impl->textureTypes_[index] && impl->textureTypes_[index] != glType)
+                glBindTexture(impl->textureTypes_[index], 0);
+            glBindTexture(glType, texture->GetGPUObjectName());
+            impl->textureTypes_[index] = glType;
+
+            if (texture->GetParametersDirty())
+                texture->UpdateParameters();
+            if (texture->GetLevelsDirty())
+                texture->RegenerateLevels();
+        }
+        else if (impl->textureTypes_[index])
+        {
+            glBindTexture(impl->textureTypes_[index], 0);
+            impl->textureTypes_[index] = 0;
+        }
+
+        textures_[index] = texture;
+    }
+    else
+    {
+        if (texture && (texture->GetParametersDirty() || texture->GetLevelsDirty()))
+        {
+            if (impl->activeTexture_ != index)
+            {
+                glActiveTexture(GL_TEXTURE0 + index);
+                impl->activeTexture_ = index;
+            }
+
+            glBindTexture(texture->GetTarget(), texture->GetGPUObjectName());
+            if (texture->GetParametersDirty())
+                texture->UpdateParameters();
+            if (texture->GetLevelsDirty())
+                texture->RegenerateLevels();
+        }
+    }
+}
+
+void Graphics::SetTextureForUpdate_OGL(Texture* texture)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->activeTexture_ != 0)
+    {
+        glActiveTexture(GL_TEXTURE0);
+        impl->activeTexture_ = 0;
+    }
+
+    unsigned glType = texture->GetTarget();
+    // Unbind old texture type if necessary
+    if (impl->textureTypes_[0] && impl->textureTypes_[0] != glType)
+        glBindTexture(impl->textureTypes_[0], 0);
+    glBindTexture(glType, texture->GetGPUObjectName());
+    impl->textureTypes_[0] = glType;
+    textures_[0] = texture;
+}
+
+void Graphics::SetDefaultTextureFilterMode_OGL(TextureFilterMode mode)
+{
+    if (mode != defaultTextureFilterMode_)
+    {
+        defaultTextureFilterMode_ = mode;
+        SetTextureParametersDirty_OGL();
+    }
+}
+
+void Graphics::SetDefaultTextureAnisotropy_OGL(unsigned level)
+{
+    level = Max(level, 1U);
+
+    if (level != defaultTextureAnisotropy_)
+    {
+        defaultTextureAnisotropy_ = level;
+        SetTextureParametersDirty_OGL();
+    }
+}
+
+void Graphics::SetTextureParametersDirty_OGL()
+{
+    MutexLock lock(gpuObjectMutex_);
+
+    for (Vector<GPUObject*>::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i)
+    {
+        auto* texture = dynamic_cast<Texture*>(*i);
+        if (texture)
+            texture->SetParametersDirty();
+    }
+}
+
+void Graphics::ResetRenderTargets_OGL()
+{
+    for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i)
+        SetRenderTarget_OGL(i, (RenderSurface*)nullptr);
+    SetDepthStencil_OGL((RenderSurface*)nullptr);
+    SetViewport_OGL(IntRect(0, 0, width_, height_));
+}
+
+void Graphics::ResetRenderTarget_OGL(unsigned index)
+{
+    SetRenderTarget_OGL(index, (RenderSurface*)nullptr);
+}
+
+void Graphics::ResetDepthStencil_OGL()
+{
+    SetDepthStencil_OGL((RenderSurface*)nullptr);
+}
+
+void Graphics::SetRenderTarget_OGL(unsigned index, RenderSurface* renderTarget)
+{
+    if (index >= MAX_RENDERTARGETS)
+        return;
+
+    if (renderTarget != renderTargets_[index])
+    {
+        renderTargets_[index] = renderTarget;
+
+        // If the rendertarget is also bound as a texture, replace with backup texture or null
+        if (renderTarget)
+        {
+            Texture* parentTexture = renderTarget->GetParentTexture();
+
+            for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
+            {
+                if (textures_[i] == parentTexture)
+                    SetTexture_OGL(i, textures_[i]->GetBackupTexture());
+            }
+
+            // If multisampled, mark the texture & surface needing resolve
+            if (parentTexture->GetMultiSample() > 1 && parentTexture->GetAutoResolve())
+            {
+                parentTexture->SetResolveDirty(true);
+                renderTarget->SetResolveDirty(true);
+            }
+
+            // If mipmapped, mark the levels needing regeneration
+            if (parentTexture->GetLevels() > 1)
+                parentTexture->SetLevelsDirty();
+        }
+
+        GetImpl_OGL()->fboDirty_ = true;
+    }
+}
+
+void Graphics::SetRenderTarget_OGL(unsigned index, Texture2D* texture)
+{
+    RenderSurface* renderTarget = nullptr;
+    if (texture)
+        renderTarget = texture->GetRenderSurface();
+
+    SetRenderTarget_OGL(index, renderTarget);
+}
+
+void Graphics::SetDepthStencil_OGL(RenderSurface* depthStencil)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    // If we are using a rendertarget texture, it is required in OpenGL to also have an own depth-stencil
+    // Create a new depth-stencil texture as necessary to be able to provide similar behaviour as Direct3D9
+    // Only do this for non-multisampled rendertargets; when using multisampled target a similarly multisampled
+    // depth-stencil should also be provided (backbuffer depth isn't compatible)
+    if (renderTargets_[0] && renderTargets_[0]->GetMultiSample() == 1 && !depthStencil)
+    {
+        int width = renderTargets_[0]->GetWidth();
+        int height = renderTargets_[0]->GetHeight();
+
+        // Direct3D9 default depth-stencil can not be used when rendertarget is larger than the window.
+        // Check size similarly
+        if (width <= width_ && height <= height_)
+        {
+            unsigned searchKey = (width << 16u) | height;
+            HashMap<unsigned, SharedPtr<Texture2D>>::Iterator i = impl->depthTextures_.Find(searchKey);
+            if (i != impl->depthTextures_.End())
+                depthStencil = i->second_->GetRenderSurface();
+            else
+            {
+                SharedPtr<Texture2D> newDepthTexture(new Texture2D(context_));
+                newDepthTexture->SetSize(width, height, GetDepthStencilFormat_OGL(), TEXTURE_DEPTHSTENCIL);
+                impl->depthTextures_[searchKey] = newDepthTexture;
+                depthStencil = newDepthTexture->GetRenderSurface();
+            }
+        }
+    }
+
+    if (depthStencil != depthStencil_)
+    {
+        depthStencil_ = depthStencil;
+        impl->fboDirty_ = true;
+    }
+}
+
+void Graphics::SetDepthStencil_OGL(Texture2D* texture)
+{
+    RenderSurface* depthStencil = nullptr;
+    if (texture)
+        depthStencil = texture->GetRenderSurface();
+
+    SetDepthStencil_OGL(depthStencil);
+}
+
+void Graphics::SetViewport_OGL(const IntRect& rect)
+{
+    PrepareDraw_OGL();
+
+    IntVector2 rtSize = GetRenderTargetDimensions_OGL();
+
+    IntRect rectCopy = rect;
+
+    if (rectCopy.right_ <= rectCopy.left_)
+        rectCopy.right_ = rectCopy.left_ + 1;
+    if (rectCopy.bottom_ <= rectCopy.top_)
+        rectCopy.bottom_ = rectCopy.top_ + 1;
+    rectCopy.left_ = Clamp(rectCopy.left_, 0, rtSize.x_);
+    rectCopy.top_ = Clamp(rectCopy.top_, 0, rtSize.y_);
+    rectCopy.right_ = Clamp(rectCopy.right_, 0, rtSize.x_);
+    rectCopy.bottom_ = Clamp(rectCopy.bottom_, 0, rtSize.y_);
+
+    // Use Direct3D convention with the vertical coordinates ie. 0 is top
+    glViewport(rectCopy.left_, rtSize.y_ - rectCopy.bottom_, rectCopy.Width(), rectCopy.Height());
+    viewport_ = rectCopy;
+
+    // Disable scissor test, needs to be re-enabled by the user
+    SetScissorTest_OGL(false);
+}
+
+void Graphics::SetBlendMode_OGL(BlendMode mode, bool alphaToCoverage)
+{
+    if (mode != blendMode_)
+    {
+        if (mode == BLEND_REPLACE)
+            glDisable(GL_BLEND);
+        else
+        {
+            glEnable(GL_BLEND);
+            glBlendFunc(glSrcBlend[mode], glDestBlend[mode]);
+            glBlendEquation(glBlendOp[mode]);
+        }
+
+        blendMode_ = mode;
+    }
+
+    if (alphaToCoverage != alphaToCoverage_)
+    {
+        if (alphaToCoverage)
+            glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+        else
+            glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
+
+        alphaToCoverage_ = alphaToCoverage;
+    }
+}
+
+void Graphics::SetColorWrite_OGL(bool enable)
+{
+    if (enable != colorWrite_)
+    {
+        if (enable)
+            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+        else
+            glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+
+        colorWrite_ = enable;
+    }
+}
+
+void Graphics::SetCullMode_OGL(CullMode mode)
+{
+    if (mode != cullMode_)
+    {
+        if (mode == CULL_NONE)
+            glDisable(GL_CULL_FACE);
+        else
+        {
+            // Use Direct3D convention, ie. clockwise vertices define a front face
+            glEnable(GL_CULL_FACE);
+            glCullFace(mode == CULL_CCW ? GL_FRONT : GL_BACK);
+        }
+
+        cullMode_ = mode;
+    }
+}
+
+void Graphics::SetDepthBias_OGL(float constantBias, float slopeScaledBias)
+{
+    if (constantBias != constantDepthBias_ || slopeScaledBias != slopeScaledDepthBias_)
+    {
+#ifndef GL_ES_VERSION_2_0
+        if (slopeScaledBias != 0.0f)
+        {
+            // OpenGL constant bias is unreliable and dependent on depth buffer bitdepth, apply in the projection matrix instead
+            glEnable(GL_POLYGON_OFFSET_FILL);
+            glPolygonOffset(slopeScaledBias, 0.0f);
+        }
+        else
+            glDisable(GL_POLYGON_OFFSET_FILL);
+#endif
+
+        constantDepthBias_ = constantBias;
+        slopeScaledDepthBias_ = slopeScaledBias;
+        // Force update of the projection matrix shader parameter
+        ClearParameterSource_OGL(SP_CAMERA);
+    }
+}
+
+void Graphics::SetDepthTest_OGL(CompareMode mode)
+{
+    if (mode != depthTestMode_)
+    {
+        glDepthFunc(glCmpFunc[mode]);
+        depthTestMode_ = mode;
+    }
+}
+
+void Graphics::SetDepthWrite_OGL(bool enable)
+{
+    if (enable != depthWrite_)
+    {
+        glDepthMask(enable ? GL_TRUE : GL_FALSE);
+        depthWrite_ = enable;
+    }
+}
+
+void Graphics::SetFillMode_OGL(FillMode mode)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (mode != fillMode_)
+    {
+        glPolygonMode(GL_FRONT_AND_BACK, glFillMode[mode]);
+        fillMode_ = mode;
+    }
+#endif
+}
+
+void Graphics::SetLineAntiAlias_OGL(bool enable)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (enable != lineAntiAlias_)
+    {
+        if (enable)
+            glEnable(GL_LINE_SMOOTH);
+        else
+            glDisable(GL_LINE_SMOOTH);
+        lineAntiAlias_ = enable;
+    }
+#endif
+}
+
+void Graphics::SetScissorTest_OGL(bool enable, const Rect& rect, bool borderInclusive)
+{
+    // During some light rendering loops, a full rect is toggled on/off repeatedly.
+    // Disable scissor in that case to reduce state changes
+    if (rect.min_.x_ <= 0.0f && rect.min_.y_ <= 0.0f && rect.max_.x_ >= 1.0f && rect.max_.y_ >= 1.0f)
+        enable = false;
+
+    if (enable)
+    {
+        IntVector2 rtSize(GetRenderTargetDimensions_OGL());
+        IntVector2 viewSize(viewport_.Size());
+        IntVector2 viewPos(viewport_.left_, viewport_.top_);
+        IntRect intRect;
+        int expand = borderInclusive ? 1 : 0;
+
+        intRect.left_ = Clamp((int)((rect.min_.x_ + 1.0f) * 0.5f * viewSize.x_) + viewPos.x_, 0, rtSize.x_ - 1);
+        intRect.top_ = Clamp((int)((-rect.max_.y_ + 1.0f) * 0.5f * viewSize.y_) + viewPos.y_, 0, rtSize.y_ - 1);
+        intRect.right_ = Clamp((int)((rect.max_.x_ + 1.0f) * 0.5f * viewSize.x_) + viewPos.x_ + expand, 0, rtSize.x_);
+        intRect.bottom_ = Clamp((int)((-rect.min_.y_ + 1.0f) * 0.5f * viewSize.y_) + viewPos.y_ + expand, 0, rtSize.y_);
+
+        if (intRect.right_ == intRect.left_)
+            intRect.right_++;
+        if (intRect.bottom_ == intRect.top_)
+            intRect.bottom_++;
+
+        if (intRect.right_ < intRect.left_ || intRect.bottom_ < intRect.top_)
+            enable = false;
+
+        if (enable && scissorRect_ != intRect)
+        {
+            // Use Direct3D convention with the vertical coordinates ie. 0 is top
+            glScissor(intRect.left_, rtSize.y_ - intRect.bottom_, intRect.Width(), intRect.Height());
+            scissorRect_ = intRect;
+        }
+    }
+    else
+        scissorRect_ = IntRect::ZERO;
+
+    if (enable != scissorTest_)
+    {
+        if (enable)
+            glEnable(GL_SCISSOR_TEST);
+        else
+            glDisable(GL_SCISSOR_TEST);
+        scissorTest_ = enable;
+    }
+}
+
+void Graphics::SetScissorTest_OGL(bool enable, const IntRect& rect)
+{
+    IntVector2 rtSize(GetRenderTargetDimensions_OGL());
+    IntVector2 viewPos(viewport_.left_, viewport_.top_);
+
+    if (enable)
+    {
+        IntRect intRect;
+        intRect.left_ = Clamp(rect.left_ + viewPos.x_, 0, rtSize.x_ - 1);
+        intRect.top_ = Clamp(rect.top_ + viewPos.y_, 0, rtSize.y_ - 1);
+        intRect.right_ = Clamp(rect.right_ + viewPos.x_, 0, rtSize.x_);
+        intRect.bottom_ = Clamp(rect.bottom_ + viewPos.y_, 0, rtSize.y_);
+
+        if (intRect.right_ == intRect.left_)
+            intRect.right_++;
+        if (intRect.bottom_ == intRect.top_)
+            intRect.bottom_++;
+
+        if (intRect.right_ < intRect.left_ || intRect.bottom_ < intRect.top_)
+            enable = false;
+
+        if (enable && scissorRect_ != intRect)
+        {
+            // Use Direct3D convention with the vertical coordinates ie. 0 is top
+            glScissor(intRect.left_, rtSize.y_ - intRect.bottom_, intRect.Width(), intRect.Height());
+            scissorRect_ = intRect;
+        }
+    }
+    else
+        scissorRect_ = IntRect::ZERO;
+
+    if (enable != scissorTest_)
+    {
+        if (enable)
+            glEnable(GL_SCISSOR_TEST);
+        else
+            glDisable(GL_SCISSOR_TEST);
+        scissorTest_ = enable;
+    }
+}
+
+void Graphics::SetClipPlane_OGL(bool enable, const Plane& clipPlane, const Matrix3x4& view, const Matrix4& projection)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (enable != useClipPlane_)
+    {
+        if (enable)
+            glEnable(GL_CLIP_PLANE0);
+        else
+            glDisable(GL_CLIP_PLANE0);
+
+        useClipPlane_ = enable;
+    }
+
+    if (enable)
+    {
+        Matrix4 viewProj = projection * view;
+        clipPlane_ = clipPlane.Transformed(viewProj).ToVector4();
+
+        if (!gl3Support)
+        {
+            GLdouble planeData[4];
+            planeData[0] = clipPlane_.x_;
+            planeData[1] = clipPlane_.y_;
+            planeData[2] = clipPlane_.z_;
+            planeData[3] = clipPlane_.w_;
+
+            glClipPlane(GL_CLIP_PLANE0, &planeData[0]);
+        }
+    }
+#endif
+}
+
+void Graphics::SetStencilTest_OGL(bool enable, CompareMode mode, StencilOp pass, StencilOp fail, StencilOp zFail, u32 stencilRef,
+    u32 compareMask, u32 writeMask)
+{
+#ifndef URHO3D_GLES2
+    if (enable != stencilTest_)
+    {
+        if (enable)
+            glEnable(GL_STENCIL_TEST);
+        else
+            glDisable(GL_STENCIL_TEST);
+
+        stencilTest_ = enable;
+    }
+
+    if (enable)
+    {
+        if (mode != stencilTestMode_ || stencilRef != stencilRef_ || compareMask != stencilCompareMask_)
+        {
+            glStencilFunc(glCmpFunc[mode], (GLint)stencilRef, compareMask);
+            stencilTestMode_ = mode;
+            stencilRef_ = stencilRef;
+            stencilCompareMask_ = compareMask;
+        }
+        if (writeMask != stencilWriteMask_)
+        {
+            glStencilMask(writeMask);
+            stencilWriteMask_ = writeMask;
+        }
+        if (pass != stencilPass_ || fail != stencilFail_ || zFail != stencilZFail_)
+        {
+            glStencilOp(glStencilOps[fail], glStencilOps[zFail], glStencilOps[pass]);
+            stencilPass_ = pass;
+            stencilFail_ = fail;
+            stencilZFail_ = zFail;
+        }
+    }
+#endif
+}
+
+bool Graphics::IsInitialized_OGL() const
+{
+    return window_ != nullptr;
+}
+
+bool Graphics::GetDither_OGL() const
+{
+    return glIsEnabled(GL_DITHER) ? true : false;
+}
+
+bool Graphics::IsDeviceLost_OGL() const
+{
+    // On iOS and tvOS treat window minimization as device loss, as it is forbidden to access OpenGL when minimized
+#if defined(IOS) || defined(TVOS)
+    if (window_ && (SDL_GetWindowFlags(window_) & SDL_WINDOW_MINIMIZED) != 0)
+        return true;
+#endif
+
+    return GetImpl_OGL()->context_ == nullptr;
+}
+
+Vector<int> Graphics::GetMultiSampleLevels_OGL() const
+{
+    Vector<int> ret;
+    // No multisampling always supported
+    ret.Push(1);
+
+#ifndef URHO3D_GLES2
+    int maxSamples = 0;
+    glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
+    for (int i = 2; i <= maxSamples && i <= 16; i *= 2)
+        ret.Push(i);
+#endif
+
+    return ret;
+}
+
+unsigned Graphics::GetFormat_OGL(CompressedFormat format) const
+{
+    switch (format)
+    {
+    case CF_RGBA:
+        return GL_RGBA;
+
+    case CF_DXT1:
+        return dxtTextureSupport_ ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : 0;
+
+#if !defined(URHO3D_GLES2) || defined(__EMSCRIPTEN__)
+    case CF_DXT3:
+        return dxtTextureSupport_ ? GL_COMPRESSED_RGBA_S3TC_DXT3_EXT : 0;
+
+    case CF_DXT5:
+        return dxtTextureSupport_ ? GL_COMPRESSED_RGBA_S3TC_DXT5_EXT : 0;
+#endif
+#ifdef GL_ES_VERSION_2_0
+    case CF_ETC1:
+        return etcTextureSupport_ ? GL_ETC1_RGB8_OES : 0;
+
+    case CF_ETC2_RGB:
+        return etc2TextureSupport_ ? GL_ETC2_RGB8_OES : 0;
+
+    case CF_ETC2_RGBA:
+        return etc2TextureSupport_ ? GL_ETC2_RGBA8_OES : 0;
+
+    case CF_PVRTC_RGB_2BPP:
+        return pvrtcTextureSupport_ ? COMPRESSED_RGB_PVRTC_2BPPV1_IMG : 0;
+
+    case CF_PVRTC_RGB_4BPP:
+        return pvrtcTextureSupport_ ? COMPRESSED_RGB_PVRTC_4BPPV1_IMG : 0;
+
+    case CF_PVRTC_RGBA_2BPP:
+        return pvrtcTextureSupport_ ? COMPRESSED_RGBA_PVRTC_2BPPV1_IMG : 0;
+
+    case CF_PVRTC_RGBA_4BPP:
+        return pvrtcTextureSupport_ ? COMPRESSED_RGBA_PVRTC_4BPPV1_IMG : 0;
+#endif
+
+    default:
+        return 0;
+    }
+}
+
+unsigned Graphics::GetMaxBones_OGL()
+{
+#ifdef RPI
+    // At the moment all RPI GPUs are low powered and only have limited number of uniforms
+    return 32;
+#elif defined(MOBILE_GRAPHICS)
+    return 64;
+#else
+    return gl3Support ? 128 : 64;
+#endif
+}
+
+bool Graphics::GetGL3Support_OGL()
+{
+    return gl3Support;
+}
+
+ShaderVariation* Graphics::GetShader_OGL(ShaderType type, const String& name, const String& defines) const
+{
+    return GetShader_OGL(type, name.CString(), defines.CString());
+}
+
+ShaderVariation* Graphics::GetShader_OGL(ShaderType type, const char* name, const char* defines) const
+{
+    if (lastShaderName_ != name || !lastShader_)
+    {
+        auto* cache = GetSubsystem<ResourceCache>();
+
+        String fullShaderName = shaderPath_ + name + shaderExtension_;
+        // Try to reduce repeated error log prints because of missing shaders
+        if (lastShaderName_ == name && !cache->Exists(fullShaderName))
+            return nullptr;
+
+        lastShader_ = cache->GetResource<Shader>(fullShaderName);
+        lastShaderName_ = name;
+    }
+
+    return lastShader_ ? lastShader_->GetVariation(type, defines) : nullptr;
+}
+
+VertexBuffer* Graphics::GetVertexBuffer_OGL(unsigned index) const
+{
+    return index < MAX_VERTEX_STREAMS ? vertexBuffers_[index] : nullptr;
+}
+
+ShaderProgram_OGL* Graphics::GetShaderProgram_OGL() const
+{
+    return GetImpl_OGL()->shaderProgram_;
+}
+
+TextureUnit Graphics::GetTextureUnit_OGL(const String& name)
+{
+    HashMap<String, TextureUnit>::Iterator i = textureUnits_.Find(name);
+    if (i != textureUnits_.End())
+        return i->second_;
+    else
+        return MAX_TEXTURE_UNITS;
+}
+
+const String& Graphics::GetTextureUnitName_OGL(TextureUnit unit)
+{
+    for (HashMap<String, TextureUnit>::Iterator i = textureUnits_.Begin(); i != textureUnits_.End(); ++i)
+    {
+        if (i->second_ == unit)
+            return i->first_;
+    }
+    return String::EMPTY;
+}
+
+Texture* Graphics::GetTexture_OGL(unsigned index) const
+{
+    return index < MAX_TEXTURE_UNITS ? textures_[index] : nullptr;
+}
+
+RenderSurface* Graphics::GetRenderTarget_OGL(unsigned index) const
+{
+    return index < MAX_RENDERTARGETS ? renderTargets_[index] : nullptr;
+}
+
+IntVector2 Graphics::GetRenderTargetDimensions_OGL() const
+{
+    int width, height;
+
+    if (renderTargets_[0])
+    {
+        width = renderTargets_[0]->GetWidth();
+        height = renderTargets_[0]->GetHeight();
+    }
+    else if (depthStencil_)
+    {
+        width = depthStencil_->GetWidth();
+        height = depthStencil_->GetHeight();
+    }
+    else
+    {
+        width = width_;
+        height = height_;
+    }
+
+    return IntVector2(width, height);
+}
+
+void Graphics::OnWindowResized_OGL()
+{
+    if (!window_)
+        return;
+
+    int newWidth, newHeight;
+
+    SDL_GL_GetDrawableSize(window_, &newWidth, &newHeight);
+    if (newWidth == width_ && newHeight == height_)
+        return;
+
+    width_ = newWidth;
+    height_ = newHeight;
+
+    int logicalWidth, logicalHeight;
+    SDL_GetWindowSize(window_, &logicalWidth, &logicalHeight);
+    screenParams_.highDPI_ = (width_ != logicalWidth) || (height_ != logicalHeight);
+
+    // Reset rendertargets and viewport for the new screen size. Also clean up any FBO's, as they may be screen size dependent
+    CleanupFramebuffers_OGL();
+    ResetRenderTargets_OGL();
+
+    URHO3D_LOGDEBUGF("Window was resized to %dx%d", width_, height_);
+
+#ifdef __EMSCRIPTEN__
+    EM_ASM({
+        Module.SetRendererSize($0, $1);
+    }, width_, height_);
+#endif
+
+    using namespace ScreenMode;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_WIDTH] = width_;
+    eventData[P_HEIGHT] = height_;
+    eventData[P_FULLSCREEN] = screenParams_.fullscreen_;
+    eventData[P_RESIZABLE] = screenParams_.resizable_;
+    eventData[P_BORDERLESS] = screenParams_.borderless_;
+    eventData[P_HIGHDPI] = screenParams_.highDPI_;
+    SendEvent(E_SCREENMODE, eventData);
+}
+
+void Graphics::OnWindowMoved_OGL()
+{
+    if (!window_ || screenParams_.fullscreen_)
+        return;
+
+    int newX, newY;
+
+    SDL_GetWindowPosition(window_, &newX, &newY);
+    if (newX == position_.x_ && newY == position_.y_)
+        return;
+
+    position_.x_ = newX;
+    position_.y_ = newY;
+
+    URHO3D_LOGTRACEF("Window was moved to %d,%d", position_.x_, position_.y_);
+
+    using namespace WindowPos;
+
+    VariantMap& eventData = GetEventDataMap();
+    eventData[P_X] = position_.x_;
+    eventData[P_Y] = position_.y_;
+    SendEvent(E_WINDOWPOS, eventData);
+}
+
+void Graphics::CleanupRenderSurface_OGL(RenderSurface* surface)
+{
+    if (!surface)
+        return;
+
+    // Flush pending FBO changes first if any
+    PrepareDraw_OGL();
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    unsigned currentFBO = impl->boundFBO_;
+
+    // Go through all FBOs and clean up the surface from them
     for (HashMap<hash64, FrameBufferObject>::Iterator i = impl->frameBuffers_.Begin();
-         i != impl->frameBuffers_.End(); ++i)
-    {
-        for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j)
-        {
-            if (i->second_.colorAttachments_[j] == surface)
-            {
-                if (currentFBO != i->second_.fbo_)
-                {
-                    BindFramebuffer_OGL(i->second_.fbo_);
-                    currentFBO = i->second_.fbo_;
-                }
-                BindColorAttachment_OGL(j, GL_TEXTURE_2D, 0, false);
-                i->second_.colorAttachments_[j] = nullptr;
-                // Mark drawbuffer bits to need recalculation
-                i->second_.drawBuffers_ = M_MAX_UNSIGNED;
-            }
-        }
-        if (i->second_.depthAttachment_ == surface)
-        {
-            if (currentFBO != i->second_.fbo_)
-            {
-                BindFramebuffer_OGL(i->second_.fbo_);
-                currentFBO = i->second_.fbo_;
-            }
-            BindDepthAttachment_OGL(0, false);
-            BindStencilAttachment_OGL(0, false);
-            i->second_.depthAttachment_ = nullptr;
-        }
-    }
-
-    // Restore previously bound FBO now if needed
-    if (currentFBO != impl->boundFBO_)
-        BindFramebuffer_OGL(impl->boundFBO_);
-}
-
-void Graphics::CleanupShaderPrograms_OGL(ShaderVariation* variation)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    for (ShaderProgramMap_OGL::Iterator i = impl->shaderPrograms_.Begin(); i != impl->shaderPrograms_.End();)
-    {
-        if (i->second_->GetVertexShader() == variation || i->second_->GetPixelShader() == variation)
-            i = impl->shaderPrograms_.Erase(i);
-        else
-            ++i;
-    }
-
-    if (vertexShader_ == variation || pixelShader_ == variation)
-        impl->shaderProgram_ = nullptr;
-}
-
-ConstantBuffer* Graphics::GetOrCreateConstantBuffer_OGL(ShaderType type,  unsigned index, unsigned size)
-{
-    // Note: shaderType parameter is not used on OpenGL, instead binding index should already use the PS range
-    // for PS constant buffers
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    unsigned key = (index << 16u) | size;
-    HashMap<unsigned, SharedPtr<ConstantBuffer>>::Iterator i = impl->allConstantBuffers_.Find(key);
-    if (i == impl->allConstantBuffers_.End())
-    {
-        i = impl->allConstantBuffers_.Insert(MakePair(key, SharedPtr<ConstantBuffer>(new ConstantBuffer(context_))));
-        i->second_->SetSize(size);
-    }
-    return i->second_.Get();
-}
-
-void Graphics::Release_OGL(bool clearGPUObjects, bool closeWindow)
-{
-    if (!window_)
-        return;
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    {
-        MutexLock lock(gpuObjectMutex_);
-
-        if (clearGPUObjects)
-        {
-            // Shutting down: release all GPU objects that still exist
-            // Shader programs are also GPU objects; clear them first to avoid list modification during iteration
-            impl->shaderPrograms_.Clear();
-
-            for (Vector<GPUObject*>::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i)
-                (*i)->Release();
-            gpuObjects_.Clear();
-        }
-        else
-        {
-            // We are not shutting down, but recreating the context: mark GPU objects lost
-            for (Vector<GPUObject*>::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i)
-                (*i)->OnDeviceLost();
-
-            // In this case clear shader programs last so that they do not attempt to delete their OpenGL program
-            // from a context that may no longer exist
-            impl->shaderPrograms_.Clear();
-
-            SendEvent(E_DEVICELOST);
-        }
-    }
-
-    CleanupFramebuffers_OGL();
-    impl->depthTextures_.Clear();
-
-    // End fullscreen mode first to counteract transition and getting stuck problems on OS X
-#if defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
-    if (closeWindow && screenParams_.fullscreen_ && !externalWindow_)
-        SDL_SetWindowFullscreen(window_, 0);
-#endif
-
-    if (impl->context_)
-    {
-        // Do not log this message if we are exiting
-        if (!clearGPUObjects)
-            URHO3D_LOGINFO("OpenGL context lost");
-
-        SDL_GL_DeleteContext(impl->context_);
-        impl->context_ = nullptr;
-    }
-
-    if (closeWindow)
-    {
-        SDL_ShowCursor(SDL_TRUE);
-
-        // Do not destroy external window except when shutting down
-        if (!externalWindow_ || clearGPUObjects)
-        {
-            SDL_DestroyWindow(window_);
-            window_ = nullptr;
-        }
-    }
-}
-
-void Graphics::Restore_OGL()
-{
-    if (!window_)
-        return;
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-#ifdef __ANDROID__
-    // On Android the context may be lost behind the scenes as the application is minimized
-    if (impl->context_ && !SDL_GL_GetCurrentContext())
-    {
-        impl->context_ = 0;
-        // Mark GPU objects lost without a current context. In this case they just mark their internal state lost
-        // but do not perform OpenGL commands to delete the GL objects
-        Release_OGL(false, false);
-    }
-#endif
-
-    // Ensure first that the context exists
-    if (!impl->context_)
-    {
-        impl->context_ = SDL_GL_CreateContext(window_);
-
-#ifndef GL_ES_VERSION_2_0
-        // If we're trying to use OpenGL 3, but context creation fails, retry with 2
-        if (!forceGL2_ && !impl->context_)
-        {
-            forceGL2_ = true;
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-            SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
+         i != impl->frameBuffers_.End(); ++i)
+    {
+        for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j)
+        {
+            if (i->second_.colorAttachments_[j] == surface)
+            {
+                if (currentFBO != i->second_.fbo_)
+                {
+                    BindFramebuffer_OGL(i->second_.fbo_);
+                    currentFBO = i->second_.fbo_;
+                }
+                BindColorAttachment_OGL(j, GL_TEXTURE_2D, 0, false);
+                i->second_.colorAttachments_[j] = nullptr;
+                // Mark drawbuffer bits to need recalculation
+                i->second_.drawBuffers_ = M_MAX_UNSIGNED;
+            }
+        }
+        if (i->second_.depthAttachment_ == surface)
+        {
+            if (currentFBO != i->second_.fbo_)
+            {
+                BindFramebuffer_OGL(i->second_.fbo_);
+                currentFBO = i->second_.fbo_;
+            }
+            BindDepthAttachment_OGL(0, false);
+            BindStencilAttachment_OGL(0, false);
+            i->second_.depthAttachment_ = nullptr;
+        }
+    }
+
+    // Restore previously bound FBO now if needed
+    if (currentFBO != impl->boundFBO_)
+        BindFramebuffer_OGL(impl->boundFBO_);
+}
+
+void Graphics::CleanupShaderPrograms_OGL(ShaderVariation* variation)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    for (ShaderProgramMap_OGL::Iterator i = impl->shaderPrograms_.Begin(); i != impl->shaderPrograms_.End();)
+    {
+        if (i->second_->GetVertexShader() == variation || i->second_->GetPixelShader() == variation)
+            i = impl->shaderPrograms_.Erase(i);
+        else
+            ++i;
+    }
+
+    if (vertexShader_ == variation || pixelShader_ == variation)
+        impl->shaderProgram_ = nullptr;
+}
+
+ConstantBuffer* Graphics::GetOrCreateConstantBuffer_OGL(ShaderType type,  unsigned index, unsigned size)
+{
+    // Note: shaderType parameter is not used on OpenGL, instead binding index should already use the PS range
+    // for PS constant buffers
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    unsigned key = (index << 16u) | size;
+    HashMap<unsigned, SharedPtr<ConstantBuffer>>::Iterator i = impl->allConstantBuffers_.Find(key);
+    if (i == impl->allConstantBuffers_.End())
+    {
+        i = impl->allConstantBuffers_.Insert(MakePair(key, SharedPtr<ConstantBuffer>(new ConstantBuffer(context_))));
+        i->second_->SetSize(size);
+    }
+    return i->second_.Get();
+}
+
+void Graphics::Release_OGL(bool clearGPUObjects, bool closeWindow)
+{
+    if (!window_)
+        return;
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    {
+        MutexLock lock(gpuObjectMutex_);
+
+        if (clearGPUObjects)
+        {
+            // Shutting down: release all GPU objects that still exist
+            // Shader programs are also GPU objects; clear them first to avoid list modification during iteration
+            impl->shaderPrograms_.Clear();
+
+            for (Vector<GPUObject*>::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i)
+                (*i)->Release();
+            gpuObjects_.Clear();
+        }
+        else
+        {
+            // We are not shutting down, but recreating the context: mark GPU objects lost
+            for (Vector<GPUObject*>::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i)
+                (*i)->OnDeviceLost();
+
+            // In this case clear shader programs last so that they do not attempt to delete their OpenGL program
+            // from a context that may no longer exist
+            impl->shaderPrograms_.Clear();
+
+            SendEvent(E_DEVICELOST);
+        }
+    }
+
+    CleanupFramebuffers_OGL();
+    impl->depthTextures_.Clear();
+
+    // End fullscreen mode first to counteract transition and getting stuck problems on OS X
+#if defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
+    if (closeWindow && screenParams_.fullscreen_ && !externalWindow_)
+        SDL_SetWindowFullscreen(window_, 0);
+#endif
+
+    if (impl->context_)
+    {
+        // Do not log this message if we are exiting
+        if (!clearGPUObjects)
+            URHO3D_LOGINFO("OpenGL context lost");
+
+        SDL_GL_DeleteContext(impl->context_);
+        impl->context_ = nullptr;
+    }
+
+    if (closeWindow)
+    {
+        SDL_ShowCursor(SDL_TRUE);
+
+        // Do not destroy external window except when shutting down
+        if (!externalWindow_ || clearGPUObjects)
+        {
+            SDL_DestroyWindow(window_);
+            window_ = nullptr;
+        }
+    }
+}
+
+void Graphics::Restore_OGL()
+{
+    if (!window_)
+        return;
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+#ifdef __ANDROID__
+    // On Android the context may be lost behind the scenes as the application is minimized
+    if (impl->context_ && !SDL_GL_GetCurrentContext())
+    {
+        impl->context_ = 0;
+        // Mark GPU objects lost without a current context. In this case they just mark their internal state lost
+        // but do not perform OpenGL commands to delete the GL objects
+        Release_OGL(false, false);
+    }
+#endif
+
+    // Ensure first that the context exists
+    if (!impl->context_)
+    {
+        impl->context_ = SDL_GL_CreateContext(window_);
+
+#ifndef GL_ES_VERSION_2_0
+        // If we're trying to use OpenGL 3, but context creation fails, retry with 2
+        if (!forceGL2_ && !impl->context_)
+        {
+            forceGL2_ = true;
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+            SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
             impl->context_ = SDL_GL_CreateContext(window_);
-        }
-#endif
-
-#if defined(IOS) || defined(TVOS)
-        glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl->systemFBO_);
-#endif
-
-        if (!impl->context_)
-        {
-            URHO3D_LOGERRORF("Could not create OpenGL context, root cause '%s'", SDL_GetError());
-            return;
-        }
-
-        // Clear cached extensions string from the previous context
-        extensions.Clear();
-
-        // Initialize OpenGL extensions library (desktop only)
-#ifndef GL_ES_VERSION_2_0
-        GLenum err = glewInit();
-        if (GLEW_OK != err)
-        {
-            URHO3D_LOGERRORF("Could not initialize OpenGL extensions, root cause: '%s'", glewGetErrorString(err));
-            return;
-        }
-
-        if (!forceGL2_ && GLEW_VERSION_3_2)
-        {
-            gl3Support = true;
-            apiName_ = "GL3";
-
-            // Create and bind a vertex array object that will stay in use throughout
-            unsigned vertexArrayObject;
-            glGenVertexArrays(1, &vertexArrayObject);
-            glBindVertexArray(vertexArrayObject);
-        }
-        else if (GLEW_VERSION_2_0)
-        {
-            if (!GLEW_EXT_framebuffer_object || !GLEW_EXT_packed_depth_stencil)
-            {
-                URHO3D_LOGERROR("EXT_framebuffer_object and EXT_packed_depth_stencil OpenGL extensions are required");
-                return;
-            }
-
-            gl3Support = false;
             apiName_ = "GL2";
-        }
-        else
-        {
-            URHO3D_LOGERROR("OpenGL 2.0 is required");
-            return;
-        }
-
-        // Enable seamless cubemap if possible
-        // Note: even though we check the extension, this can lead to software fallback on some old GPU's
-        // See https://github.com/urho3d/Urho3D/issues/1380 or
-        // http://distrustsimplicity.net/articles/gl_texture_cube_map_seamless-on-os-x/
-        // In case of trouble or for wanting maximum compatibility, simply remove the glEnable below.
-        if (gl3Support || GLEW_ARB_seamless_cube_map)
-            glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
-#endif
-
-        // Set up texture data read/write alignment. It is important that this is done before uploading any texture data
-        glPixelStorei(GL_PACK_ALIGNMENT, 1);
-        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-        ResetCachedState_OGL();
-    }
-
-    {
-        MutexLock lock(gpuObjectMutex_);
-
-        for (Vector<GPUObject*>::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i)
-            (*i)->OnDeviceReset();
-    }
-
-    SendEvent(E_DEVICERESET);
-}
-
-void Graphics::MarkFBODirty_OGL()
-{
-    GetImpl_OGL()->fboDirty_ = true;
-}
-
-void Graphics::SetVBO_OGL(unsigned object)
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (impl->boundVBO_ != object)
-    {
-        if (object)
-            glBindBuffer(GL_ARRAY_BUFFER, object);
-        impl->boundVBO_ = object;
-    }
-}
-
-void Graphics::SetUBO_OGL(unsigned object)
-{
-#ifndef GL_ES_VERSION_2_0
+        }
+#endif
+
+#if defined(IOS) || defined(TVOS)
+        glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&impl->systemFBO_);
+#endif
+
+        if (!impl->context_)
+        {
+            URHO3D_LOGERRORF("Could not create OpenGL context, root cause '%s'", SDL_GetError());
+            return;
+        }
+
+        // Clear cached extensions string from the previous context
+        extensions.Clear();
+
+        // Initialize OpenGL extensions library (desktop only)
+#ifndef GL_ES_VERSION_2_0
+        // desktop GL
+        GLenum err = glewInit();
+        if (GLEW_OK != err)
+        {
+            URHO3D_LOGERRORF("Could not initialize OpenGL extensions, root cause: '%s'", glewGetErrorString(err));
+            return;
+        }
+
+        if (!forceGL2_ && GLEW_VERSION_3_2)
+        {
+            gl3Support = true;
+            apiName_ = "GL3";
+
+            // Create and bind a vertex array object that will stay in use throughout
+            unsigned vertexArrayObject;
+            glGenVertexArrays(1, &vertexArrayObject);
+            glBindVertexArray(vertexArrayObject);
+        }
+        else if (GLEW_VERSION_2_0)
+        {
+            if (!GLEW_EXT_framebuffer_object || !GLEW_EXT_packed_depth_stencil)
+            {
+                URHO3D_LOGERROR("EXT_framebuffer_object and EXT_packed_depth_stencil OpenGL extensions are required");
+                return;
+            }
+
+            gl3Support = false;
+            apiName_ = "GL2";
+        }
+        else
+        {
+            URHO3D_LOGERROR("OpenGL 2.0 is required");
+            return;
+        }
+
+        // Enable seamless cubemap if possible
+        // Note: even though we check the extension, this can lead to software fallback on some old GPU's
+        // See https://github.com/urho3d/Urho3D/issues/1380 or
+        // http://distrustsimplicity.net/articles/gl_texture_cube_map_seamless-on-os-x/
+        // In case of trouble or for wanting maximum compatibility, simply remove the glEnable below.
+        if (gl3Support || GLEW_ARB_seamless_cube_map)
+            glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
+#endif
+
+        // Set up texture data read/write alignment. It is important that this is done before uploading any texture data
+        glPixelStorei(GL_PACK_ALIGNMENT, 1);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        ResetCachedState_OGL();
+    }
+
+    {
+        MutexLock lock(gpuObjectMutex_);
+
+        for (Vector<GPUObject*>::Iterator i = gpuObjects_.Begin(); i != gpuObjects_.End(); ++i)
+            (*i)->OnDeviceReset();
+    }
+
+    SendEvent(E_DEVICERESET);
+}
+
+void Graphics::MarkFBODirty_OGL()
+{
+    GetImpl_OGL()->fboDirty_ = true;
+}
+
+void Graphics::SetVBO_OGL(unsigned object)
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (impl->boundVBO_ != object)
+    {
+        if (object)
+            glBindBuffer(GL_ARRAY_BUFFER, object);
+        impl->boundVBO_ = object;
+    }
+}
+
+void Graphics::SetUBO_OGL(unsigned object)
+{
+#ifndef URHO3D_GLES2
     GraphicsImpl_OGL* impl = GetImpl_OGL();
     if (impl->boundUBO_ != object)
-    {
-        if (object)
-            glBindBuffer(GL_UNIFORM_BUFFER, object);
-        impl->boundUBO_ = object;
-    }
-#endif
-}
-
-unsigned Graphics::GetAlphaFormat_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    // Alpha format is deprecated on OpenGL 3+
-    if (gl3Support)
-        return GL_R8;
-#endif
-    return GL_ALPHA;
-}
-
-unsigned Graphics::GetLuminanceFormat_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    // Luminance format is deprecated on OpenGL 3+
-    if (gl3Support)
-        return GL_R8;
-#endif
-    return GL_LUMINANCE;
-}
-
-unsigned Graphics::GetLuminanceAlphaFormat_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    // Luminance alpha format is deprecated on OpenGL 3+
-    if (gl3Support)
-        return GL_RG8;
-#endif
-    return GL_LUMINANCE_ALPHA;
-}
-
-unsigned Graphics::GetRGBFormat_OGL()
-{
-    return GL_RGB;
-}
-
-unsigned Graphics::GetRGBAFormat_OGL()
-{
-    return GL_RGBA;
-}
-
-unsigned Graphics::GetRGBA16Format_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_RGBA16;
-#else
-    return GL_RGBA;
-#endif
-}
-
-unsigned Graphics::GetRGBAFloat16Format_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_RGBA16F_ARB;
-#else
-    return GL_RGBA;
-#endif
-}
-
-unsigned Graphics::GetRGBAFloat32Format_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_RGBA32F_ARB;
-#else
-    return GL_RGBA;
-#endif
-}
-
-unsigned Graphics::GetRG16Format_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_RG16;
-#else
-    return GL_RGBA;
-#endif
-}
-
-unsigned Graphics::GetRGFloat16Format_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_RG16F;
-#else
-    return GL_RGBA;
-#endif
-}
-
-unsigned Graphics::GetRGFloat32Format_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_RG32F;
-#else
-    return GL_RGBA;
-#endif
-}
-
-unsigned Graphics::GetFloat16Format_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_R16F;
-#else
-    return GL_LUMINANCE;
-#endif
-}
-
-unsigned Graphics::GetFloat32Format_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_R32F;
-#else
-    return GL_LUMINANCE;
-#endif
-}
-
-unsigned Graphics::GetLinearDepthFormat_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    // OpenGL 3 can use different color attachment formats
-    if (gl3Support)
-        return GL_R32F;
-#endif
-    // OpenGL 2 requires color attachments to have the same format, therefore encode deferred depth to RGBA manually
-    // if not using a readable hardware depth texture
-    return GL_RGBA;
-}
-
-unsigned Graphics::GetDepthStencilFormat_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_DEPTH24_STENCIL8_EXT;
-#else
-    return glesDepthStencilFormat;
-#endif
-}
-
-unsigned Graphics::GetReadableDepthFormat_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    return GL_DEPTH_COMPONENT24;
-#else
-    return glesReadableDepthFormat;
-#endif
-}
-
-unsigned Graphics::GetFormat_OGL(const String& formatName)
-{
-    String nameLower = formatName.ToLower().Trimmed();
-
-    if (nameLower == "a")
-        return GetAlphaFormat_OGL();
-    if (nameLower == "l")
-        return GetLuminanceFormat_OGL();
-    if (nameLower == "la")
-        return GetLuminanceAlphaFormat_OGL();
-    if (nameLower == "rgb")
-        return GetRGBFormat_OGL();
-    if (nameLower == "rgba")
-        return GetRGBAFormat_OGL();
-    if (nameLower == "rgba16")
-        return GetRGBA16Format_OGL();
-    if (nameLower == "rgba16f")
-        return GetRGBAFloat16Format_OGL();
-    if (nameLower == "rgba32f")
-        return GetRGBAFloat32Format_OGL();
-    if (nameLower == "rg16")
-        return GetRG16Format_OGL();
-    if (nameLower == "rg16f")
-        return GetRGFloat16Format_OGL();
-    if (nameLower == "rg32f")
-        return GetRGFloat32Format_OGL();
-    if (nameLower == "r16f")
-        return GetFloat16Format_OGL();
-    if (nameLower == "r32f" || nameLower == "float")
-        return GetFloat32Format_OGL();
-    if (nameLower == "lineardepth" || nameLower == "depth")
-        return GetLinearDepthFormat_OGL();
-    if (nameLower == "d24s8")
-        return GetDepthStencilFormat_OGL();
-    if (nameLower == "readabledepth" || nameLower == "hwdepth")
-        return GetReadableDepthFormat_OGL();
-
-    return GetRGBFormat_OGL();
-}
-
-void Graphics::CheckFeatureSupport_OGL()
-{
-    // Check supported features: light pre-pass, deferred rendering and hardware depth texture
-    lightPrepassSupport_ = false;
-    deferredSupport_ = false;
-
-#ifndef GL_ES_VERSION_2_0
-    int numSupportedRTs = 1;
-    if (gl3Support)
-    {
-        // Work around GLEW failure to check extensions properly from a GL3 context
-        instancingSupport_ = glDrawElementsInstanced != nullptr && glVertexAttribDivisor != nullptr;
-        dxtTextureSupport_ = true;
-        anisotropySupport_ = true;
-        sRGBSupport_ = true;
-        sRGBWriteSupport_ = true;
-
-        glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &numSupportedRTs);
-    }
-    else
-    {
-        instancingSupport_ = GLEW_ARB_instanced_arrays != 0;
-        dxtTextureSupport_ = GLEW_EXT_texture_compression_s3tc != 0;
-        anisotropySupport_ = GLEW_EXT_texture_filter_anisotropic != 0;
-        sRGBSupport_ = GLEW_EXT_texture_sRGB != 0;
-        sRGBWriteSupport_ = GLEW_EXT_framebuffer_sRGB != 0;
-
-        glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &numSupportedRTs);
-    }
-
-    // Must support 2 rendertargets for light pre-pass, and 4 for deferred
-    if (numSupportedRTs >= 2)
-        lightPrepassSupport_ = true;
-    if (numSupportedRTs >= 4)
-        deferredSupport_ = true;
-
-#if defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
-    // On macOS check for an Intel driver and use shadow map RGBA dummy color textures, because mixing
-    // depth-only FBO rendering and backbuffer rendering will bug, resulting in a black screen in full
-    // screen mode, and incomplete shadow maps in windowed mode
-    String renderer((const char*)glGetString(GL_RENDERER));
-    if (renderer.Contains("Intel", false))
-        dummyColorFormat_ = GetRGBAFormat_OGL();
-#endif
-#else
-    // Check for supported compressed texture formats
+    {
+        if (object)
+            glBindBuffer(GL_UNIFORM_BUFFER, object);
+        impl->boundUBO_ = object;
+    }
+#endif
+}
+
+unsigned Graphics::GetAlphaFormat_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    // Alpha format is deprecated on OpenGL 3+
+    if (gl3Support)
+        return GL_R8;
+#endif
+    return GL_ALPHA;
+}
+
+unsigned Graphics::GetLuminanceFormat_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    // Luminance format is deprecated on OpenGL 3+
+    if (gl3Support)
+        return GL_R8;
+#endif
+    return GL_LUMINANCE;
+}
+
+unsigned Graphics::GetLuminanceAlphaFormat_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    // Luminance alpha format is deprecated on OpenGL 3+
+    if (gl3Support)
+        return GL_RG8;
+#endif
+    return GL_LUMINANCE_ALPHA;
+}
+
+unsigned Graphics::GetRGBFormat_OGL()
+{
+    return GL_RGB;
+}
+
+unsigned Graphics::GetRGBAFormat_OGL()
+{
+    return GL_RGBA;
+}
+
+unsigned Graphics::GetRGBA16Format_OGL()
+{
+#ifdef GL_ES_VERSION_3_0
+    return GL_RGBA16UI;
+#elif !defined(GL_ES_VERSION_2_0)
+    return GL_RGBA16;
+#else
+    return GL_RGBA;
+#endif
+}
+
+unsigned Graphics::GetRGBAFloat16Format_OGL()
+{
+#ifdef GL_ES_VERSION_3_0
+    return GL_RGBA16F;
+#elif !defined(GL_ES_VERSION_2_0)
+    return GL_RGBA16F_ARB;
+#else
+    return GL_RGBA;
+#endif
+}
+
+unsigned Graphics::GetRGBAFloat32Format_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    return GL_RGBA32F_ARB;
+#elif URHO3D_GLES3
+    return GL_RGBA32F;
+#else
+    return GL_RGBA;
+#endif
+}
+
+unsigned Graphics::GetRG16Format_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    return GL_RG16;
+#elif URHO3D_GLES3
+    return GL_RG16UI;
+#else
+    return GL_RGBA;
+#endif
+}
+
+unsigned Graphics::GetRGFloat16Format_OGL()
+{
+#ifndef URHO3D_GLES2
+    return GL_RG16F;
+#else
+    return GL_RGBA;
+#endif
+}
+
+unsigned Graphics::GetRGFloat32Format_OGL()
+{
+#ifndef URHO3D_GLES2
+    return GL_RG32F;
+#else
+    return GL_RGBA;
+#endif
+}
+
+unsigned Graphics::GetFloat16Format_OGL()
+{
+#ifndef URHO3D_GLES2
+    return GL_R16F;
+#else
+    return GL_LUMINANCE;
+#endif
+}
+
+unsigned Graphics::GetFloat32Format_OGL()
+{
+#ifndef URHO3D_GLES2
+    return GL_R32F;
+#else
+    return GL_LUMINANCE;
+#endif
+}
+
+unsigned Graphics::GetLinearDepthFormat_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    // OpenGL 3 can use different color attachment formats
+    if (gl3Support)
+        return GL_R32F;
+    else
+#endif
+#ifdef GL_ES_VERSION_3_0
+        return GL_R16F;
+#else
+    // OpenGL 2 requires color attachments to have the same format, therefore encode deferred depth to RGBA manually
+    // if not using a readable hardware depth texture
+    return GL_RGBA;
+#endif
+}
+
+unsigned Graphics::GetDepthStencilFormat_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    return GL_DEPTH24_STENCIL8_EXT;
+#elif defined(GL_ES_VERSION_3_0)
+    return GL_DEPTH24_STENCIL8;
+#else
+    return glesDepthStencilFormat;
+#endif
+}
+
+unsigned Graphics::GetReadableDepthFormat_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    return GL_DEPTH_COMPONENT24;
+#elif defined(GL_ES_VERSION_3_0)
+    return GL_DEPTH_COMPONENT16;
+#else
+    return glesReadableDepthFormat;
+#endif
+}
+
+unsigned Graphics::GetFormat_OGL(const String& formatName)
+{
+    String nameLower = formatName.ToLower().Trimmed();
+
+    if (nameLower == "a")
+        return GetAlphaFormat_OGL();
+    if (nameLower == "l")
+        return GetLuminanceFormat_OGL();
+    if (nameLower == "la")
+        return GetLuminanceAlphaFormat_OGL();
+    if (nameLower == "rgb")
+        return GetRGBFormat_OGL();
+    if (nameLower == "rgba")
+        return GetRGBAFormat_OGL();
+    if (nameLower == "rgba16")
+        return GetRGBA16Format_OGL();
+    if (nameLower == "rgba16f")
+        return GetRGBAFloat16Format_OGL();
+    if (nameLower == "rgba32f")
+        return GetRGBAFloat32Format_OGL();
+    if (nameLower == "rg16")
+        return GetRG16Format_OGL();
+    if (nameLower == "rg16f")
+        return GetRGFloat16Format_OGL();
+    if (nameLower == "rg32f")
+        return GetRGFloat32Format_OGL();
+    if (nameLower == "r16f")
+        return GetFloat16Format_OGL();
+    if (nameLower == "r32f" || nameLower == "float")
+        return GetFloat32Format_OGL();
+    if (nameLower == "lineardepth" || nameLower == "depth")
+        return GetLinearDepthFormat_OGL();
+    if (nameLower == "d24s8")
+        return GetDepthStencilFormat_OGL();
+    if (nameLower == "readabledepth" || nameLower == "hwdepth")
+        return GetReadableDepthFormat_OGL();
+
+    return GetRGBFormat_OGL();
+}
+
+void Graphics::CheckFeatureSupport_OGL()
+{
+    // Check supported features: light pre-pass, deferred rendering and hardware depth texture
+    lightPrepassSupport_ = false;
+    deferredSupport_ = false;
+    rendererName_ = (const char*) glGetString(GL_RENDERER);
+    versionString_ = (const char*) glGetString(GL_VERSION);
+
+#ifndef GL_ES_VERSION_2_0
+    int numSupportedRTs = 1;
+    if (gl3Support)
+    {
+        // Work around GLEW failure to check extensions properly from a GL3 context
+        instancingSupport_ = glDrawElementsInstanced != nullptr && glVertexAttribDivisor != nullptr;
+        dxtTextureSupport_ = true;
+        anisotropySupport_ = true;
+        sRGBSupport_ = true;
+        sRGBWriteSupport_ = true;
+
+        glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &numSupportedRTs);
+    }
+    else
+    {
+        instancingSupport_ = GLEW_ARB_instanced_arrays != 0;
+        dxtTextureSupport_ = GLEW_EXT_texture_compression_s3tc != 0;
+        anisotropySupport_ = GLEW_EXT_texture_filter_anisotropic != 0;
+        sRGBSupport_ = GLEW_EXT_texture_sRGB != 0;
+        sRGBWriteSupport_ = GLEW_EXT_framebuffer_sRGB != 0;
+
+        glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &numSupportedRTs);
+    }
+
+    // Must support 2 rendertargets for light pre-pass, and 4 for deferred
+    if (numSupportedRTs >= 2)
+        lightPrepassSupport_ = true;
+    if (numSupportedRTs >= 4)
+        deferredSupport_ = true;
+
+#if defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
+    // On macOS check for an Intel driver and use shadow map RGBA dummy color textures, because mixing
+    // depth-only FBO rendering and backbuffer rendering will bug, resulting in a black screen in full
+    // screen mode, and incomplete shadow maps in windowed mode
+    String renderer((const char*)glGetString(GL_RENDERER));
+    if (renderer.Contains("Intel", false))
+        dummyColorFormat_ = GetRGBAFormat_OGL();
+#endif
+#else // GL_ES_VERSION_2_0
+    // Check for supported compressed texture formats
+#ifdef __EMSCRIPTEN__
+    dxtTextureSupport_ = CheckExtension("WEBGL_compressed_texture_s3tc"); // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
+    etcTextureSupport_ = CheckExtension("WEBGL_compressed_texture_etc1"); // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/
+    pvrtcTextureSupport_ = CheckExtension("WEBGL_compressed_texture_pvrtc"); // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
+    etc2TextureSupport_ = gl3Support || CheckExtension("WEBGL_compressed_texture_etc"); // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/
+    // Instancing is in core in WebGL 2, so the extension may not be present anymore. In WebGL 1, find https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
+    // TODO: In the distant future, this may break if WebGL 3 is introduced, so either improve the GL_VERSION parsing here, or keep track of which WebGL version we attempted to initialize.
+    instancingSupport_ = (strstr((const char *)glGetString(GL_VERSION), "WebGL 2.") != 0) || CheckExtension("ANGLE_instanced_arrays");
+#else
+    dxtTextureSupport_ = CheckExtension("EXT_texture_compression_dxt1");
+    etcTextureSupport_ = CheckExtension("OES_compressed_ETC1_RGB8_texture");
+    etc2TextureSupport_ = gl3Support || CheckExtension("OES_compressed_ETC2_RGBA8_texture");
+    pvrtcTextureSupport_ = CheckExtension("IMG_texture_compression_pvrtc");
+#ifdef GL_ES_VERSION_3_0
+    instancingSupport_ = true;
+    int numSupportedRTs = 1;
+    glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &numSupportedRTs);
+    // Must support 2 rendertargets for light pre-pass, and 4 for deferred
+    if (numSupportedRTs >= 2)
+        lightPrepassSupport_ = true;
+    if (numSupportedRTs >= 4)
+        deferredSupport_ = true;
+    anisotropySupport_ = CheckExtension("EXT_texture_filter_anisotropic");
+#endif
+#endif
+
+    // Check for best supported depth renderbuffer format for GLES2
+#ifndef GL_ES_VERSION_3_0
+    if (CheckExtension("GL_OES_depth24"))
+        glesDepthStencilFormat = GL_DEPTH_COMPONENT24_OES;
+    if (CheckExtension("GL_OES_packed_depth_stencil"))
+        glesDepthStencilFormat = GL_DEPTH24_STENCIL8_OES;
+
+#ifdef __EMSCRIPTEN__
+    if (!CheckExtension("WEBGL_depth_texture"))
+#else
+    if (!CheckExtension("GL_OES_depth_texture"))
+#endif
+    {
+        shadowMapFormat_ = 0;
+        hiresShadowMapFormat_ = 0;
+#ifndef GL_ES_VERSION_3_0
+        glesReadableDepthFormat = 0;
+#endif
+    }
+    else
+    {
+#if defined(IOS) || defined(TVOS)
+        // iOS hack: depth renderbuffer seems to fail, so use depth textures for everything if supported
+        glesDepthStencilFormat = GL_DEPTH_COMPONENT;
+#endif
+        shadowMapFormat_ = GL_DEPTH_COMPONENT;
+        hiresShadowMapFormat_ = 0;
+        // WebGL shadow map rendering seems to be extremely slow without an attached dummy color texture
 #ifdef __EMSCRIPTEN__
-    dxtTextureSupport_ = CheckExtension("WEBGL_compressed_texture_s3tc"); // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
-    etcTextureSupport_ = CheckExtension("WEBGL_compressed_texture_etc1"); // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/
-    pvrtcTextureSupport_ = CheckExtension("WEBGL_compressed_texture_pvrtc"); // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
-    etc2TextureSupport_ = gl3Support || CheckExtension("WEBGL_compressed_texture_etc"); // https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/
-    // Instancing is in core in WebGL 2, so the extension may not be present anymore. In WebGL 1, find https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/
-    // TODO: In the distant future, this may break if WebGL 3 is introduced, so either improve the GL_VERSION parsing here, or keep track of which WebGL version we attempted to initialize.
-    instancingSupport_ = (strstr((const char *)glGetString(GL_VERSION), "WebGL 2.") != 0) || CheckExtension("ANGLE_instanced_arrays");
-#else
-    dxtTextureSupport_ = CheckExtension("EXT_texture_compression_dxt1");
-    etcTextureSupport_ = CheckExtension("OES_compressed_ETC1_RGB8_texture");
-    etc2TextureSupport_ = gl3Support || CheckExtension("OES_compressed_ETC2_RGBA8_texture");
-    pvrtcTextureSupport_ = CheckExtension("IMG_texture_compression_pvrtc");
-#endif
-
-    // Check for best supported depth renderbuffer format for GLES2
-    if (CheckExtension("GL_OES_depth24"))
-        glesDepthStencilFormat = GL_DEPTH_COMPONENT24_OES;
-    if (CheckExtension("GL_OES_packed_depth_stencil"))
-        glesDepthStencilFormat = GL_DEPTH24_STENCIL8_OES;
-    #ifdef __EMSCRIPTEN__
-    if (!CheckExtension("WEBGL_depth_texture"))
-#else
-    if (!CheckExtension("GL_OES_depth_texture"))
-#endif
-    {
-        shadowMapFormat_ = 0;
-        hiresShadowMapFormat_ = 0;
-        glesReadableDepthFormat = 0;
-    }
-    else
-    {
-#if defined(IOS) || defined(TVOS)
-        // iOS hack: depth renderbuffer seems to fail, so use depth textures for everything if supported
-        glesDepthStencilFormat = GL_DEPTH_COMPONENT;
-#endif
-        shadowMapFormat_ = GL_DEPTH_COMPONENT;
-        hiresShadowMapFormat_ = 0;
-        // WebGL shadow map rendering seems to be extremely slow without an attached dummy color texture
-        #ifdef __EMSCRIPTEN__
         dummyColorFormat_ = GetRGBAFormat_OGL();
-#endif
-    }
-#endif
-
-    // Consider OpenGL shadows always hardware sampled, if supported at all
-    hardwareShadowSupport_ = shadowMapFormat_ != 0;
-}
-
-void Graphics::PrepareDraw_OGL()
-{
+#endif
+    }
+#endif
+#endif
+
+    // Consider OpenGL shadows always hardware sampled, if supported at all
+    hardwareShadowSupport_ = shadowMapFormat_ != 0;
+}
+
+void Graphics::PrepareDraw_OGL()
+{
     GraphicsImpl_OGL* impl = GetImpl_OGL();
 
-#ifndef GL_ES_VERSION_2_0
-    if (gl3Support)
-    {
-        for (Vector<ConstantBuffer*>::Iterator i = impl->dirtyConstantBuffers_.Begin(); i != impl->dirtyConstantBuffers_.End(); ++i)
-            (*i)->Apply();
-        impl->dirtyConstantBuffers_.Clear();
-    }
-#endif
-
-    if (impl->fboDirty_)
-    {
-        impl->fboDirty_ = false;
-
-        // First check if no framebuffer is needed. In that case simply return to backbuffer rendering
-        bool noFbo = !depthStencil_;
-        if (noFbo)
-        {
-            for (auto& renderTarget : renderTargets_)
-            {
-                if (renderTarget)
-                {
-                    noFbo = false;
-                    break;
-                }
-            }
-        }
-
-        if (noFbo)
-        {
-            if (impl->boundFBO_ != impl->systemFBO_)
-            {
-                BindFramebuffer_OGL(impl->systemFBO_);
-                impl->boundFBO_ = impl->systemFBO_;
-            }
-
-#ifndef GL_ES_VERSION_2_0
-            // Disable/enable sRGB write
-            if (sRGBWriteSupport_)
-            {
-                bool sRGBWrite = sRGB_;
-                if (sRGBWrite != impl->sRGBWrite_)
-                {
-                    if (sRGBWrite)
-                        glEnable(GL_FRAMEBUFFER_SRGB_EXT);
-                    else
-                        glDisable(GL_FRAMEBUFFER_SRGB_EXT);
-                    impl->sRGBWrite_ = sRGBWrite;
-                }
-            }
-#endif
-
-            return;
-        }
-
-        // Search for a new framebuffer based on format & size, or create new
-        IntVector2 rtSize = Graphics::GetRenderTargetDimensions_OGL();
-        unsigned format = 0;
-        if (renderTargets_[0])
-            format = renderTargets_[0]->GetParentTexture()->GetFormat();
-        else if (depthStencil_)
-            format = depthStencil_->GetParentTexture()->GetFormat();
-
+#ifndef URHO3D_GLES2
+#ifndef GL_ES_VERSION_3_0
+    if (gl3Support)
+#endif
+    {
+        for (Vector<ConstantBuffer*>::Iterator i = impl->dirtyConstantBuffers_.Begin(); i != impl->dirtyConstantBuffers_.End(); ++i)
+            (*i)->Apply();
+        impl->dirtyConstantBuffers_.Clear();
+    }
+#endif
+
+    if (impl->fboDirty_)
+    {
+        impl->fboDirty_ = false;
+
+        // First check if no framebuffer is needed. In that case simply return to backbuffer rendering
+        bool noFbo = !depthStencil_;
+        if (noFbo)
+        {
+            for (auto& renderTarget : renderTargets_)
+            {
+                if (renderTarget)
+                {
+                    noFbo = false;
+                    break;
+                }
+            }
+        }
+
+        if (noFbo)
+        {
+            if (impl->boundFBO_ != impl->systemFBO_)
+            {
+                BindFramebuffer_OGL(impl->systemFBO_);
+                impl->boundFBO_ = impl->systemFBO_;
+            }
+
+#ifndef GL_ES_VERSION_2_0
+            // Disable/enable sRGB write
+            if (sRGBWriteSupport_)
+            {
+                bool sRGBWrite = sRGB_;
+                if (sRGBWrite != impl->sRGBWrite_)
+                {
+                    if (sRGBWrite)
+                        glEnable(GL_FRAMEBUFFER_SRGB_EXT);
+                    else
+                        glDisable(GL_FRAMEBUFFER_SRGB_EXT);
+                    impl->sRGBWrite_ = sRGBWrite;
+                }
+            }
+#endif
+            return;
+        }
+
+        // Search for a new framebuffer based on format & size, or create new
+        IntVector2 rtSize = Graphics::GetRenderTargetDimensions_OGL();
+        unsigned format = 0;
+        if (renderTargets_[0])
+            format = renderTargets_[0]->GetParentTexture()->GetFormat();
+        else if (depthStencil_)
+            format = depthStencil_->GetParentTexture()->GetFormat();
+
         hash64 fboKey = (hash64)format << 32u | rtSize.x_ << 16u | rtSize.y_;
         HashMap<hash64, FrameBufferObject>::Iterator i = impl->frameBuffers_.Find(fboKey);
-        if (i == impl->frameBuffers_.End())
-        {
-            FrameBufferObject newFbo;
-            newFbo.fbo_ = CreateFramebuffer_OGL();
-            i = impl->frameBuffers_.Insert(MakePair(fboKey, newFbo));
-        }
-
-        if (impl->boundFBO_ != i->second_.fbo_)
-        {
-            BindFramebuffer_OGL(i->second_.fbo_);
-            impl->boundFBO_ = i->second_.fbo_;
-        }
-
-#ifndef GL_ES_VERSION_2_0
-        // Setup readbuffers & drawbuffers if needed
-        if (i->second_.readBuffers_ != GL_NONE)
-        {
-            glReadBuffer(GL_NONE);
-            i->second_.readBuffers_ = GL_NONE;
-        }
-
-        // Calculate the bit combination of non-zero color rendertargets to first check if the combination changed
-        unsigned newDrawBuffers = 0;
-        for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j)
-        {
-            if (renderTargets_[j])
-                newDrawBuffers |= 1u << j;
-        }
-
-        if (newDrawBuffers != i->second_.drawBuffers_)
-        {
-            // Check for no color rendertargets (depth rendering only)
-            if (!newDrawBuffers)
-                glDrawBuffer(GL_NONE);
-            else
-            {
-                int drawBufferIds[MAX_RENDERTARGETS];
-                unsigned drawBufferCount = 0;
-
-                for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j)
-                {
-                    if (renderTargets_[j])
-                    {
-                        if (!gl3Support)
-                            drawBufferIds[drawBufferCount++] = GL_COLOR_ATTACHMENT0_EXT + j;
-                        else
-                            drawBufferIds[drawBufferCount++] = GL_COLOR_ATTACHMENT0 + j;
-                    }
-                }
-                glDrawBuffers(drawBufferCount, (const GLenum*)drawBufferIds);
-            }
-
-            i->second_.drawBuffers_ = newDrawBuffers;
-        }
-#endif
-
-        for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j)
-        {
-            if (renderTargets_[j])
-            {
-                Texture* texture = renderTargets_[j]->GetParentTexture();
-
-                // Bind either a renderbuffer or texture, depending on what is available
-                unsigned renderBufferID = renderTargets_[j]->GetRenderBuffer();
-                if (!renderBufferID)
-                {
-                    // If texture's parameters are dirty, update before attaching
-                    if (texture->GetParametersDirty())
-                    {
-                        SetTextureForUpdate_OGL(texture);
-                        texture->UpdateParameters();
-                        SetTexture_OGL(0, nullptr);
-                    }
-
-                    if (i->second_.colorAttachments_[j] != renderTargets_[j])
-                    {
-                        BindColorAttachment_OGL(j, renderTargets_[j]->GetTarget(), texture->GetGPUObjectName(), false);
-                        i->second_.colorAttachments_[j] = renderTargets_[j];
-                    }
-                }
-                else
-                {
-                    if (i->second_.colorAttachments_[j] != renderTargets_[j])
-                    {
-                        BindColorAttachment_OGL(j, renderTargets_[j]->GetTarget(), renderBufferID, true);
-                        i->second_.colorAttachments_[j] = renderTargets_[j];
-                    }
-                }
-            }
-            else
-            {
-                if (i->second_.colorAttachments_[j])
-                {
-                    BindColorAttachment_OGL(j, GL_TEXTURE_2D, 0, false);
-                    i->second_.colorAttachments_[j] = nullptr;
-                }
-            }
-        }
-
-        if (depthStencil_)
-        {
-            // Bind either a renderbuffer or a depth texture, depending on what is available
-            Texture* texture = depthStencil_->GetParentTexture();
-#ifndef GL_ES_VERSION_2_0
-            bool hasStencil = texture->GetFormat() == GL_DEPTH24_STENCIL8_EXT;
-#else
-            bool hasStencil = texture->GetFormat() == GL_DEPTH24_STENCIL8_OES;
-#endif
-            unsigned renderBufferID = depthStencil_->GetRenderBuffer();
-            if (!renderBufferID)
-            {
-                // If texture's parameters are dirty, update before attaching
-                if (texture->GetParametersDirty())
-                {
-                    SetTextureForUpdate_OGL(texture);
-                    texture->UpdateParameters();
-                    SetTexture_OGL(0, nullptr);
-                }
-
-                if (i->second_.depthAttachment_ != depthStencil_)
-                {
-                    BindDepthAttachment_OGL(texture->GetGPUObjectName(), false);
-                    BindStencilAttachment_OGL(hasStencil ? texture->GetGPUObjectName() : 0, false);
-                    i->second_.depthAttachment_ = depthStencil_;
-                }
-            }
-            else
-            {
-                if (i->second_.depthAttachment_ != depthStencil_)
-                {
-                    BindDepthAttachment_OGL(renderBufferID, true);
-                    BindStencilAttachment_OGL(hasStencil ? renderBufferID : 0, true);
-                    i->second_.depthAttachment_ = depthStencil_;
-                }
-            }
-        }
-        else
-        {
-            if (i->second_.depthAttachment_)
-            {
-                BindDepthAttachment_OGL(0, false);
-                BindStencilAttachment_OGL(0, false);
-                i->second_.depthAttachment_ = nullptr;
-            }
-        }
-
-#ifndef GL_ES_VERSION_2_0
-        // Disable/enable sRGB write
-        if (sRGBWriteSupport_)
-        {
-            bool sRGBWrite = renderTargets_[0] ? renderTargets_[0]->GetParentTexture()->GetSRGB() : sRGB_;
-            if (sRGBWrite != impl->sRGBWrite_)
-            {
-                if (sRGBWrite)
-                    glEnable(GL_FRAMEBUFFER_SRGB_EXT);
-                else
-                    glDisable(GL_FRAMEBUFFER_SRGB_EXT);
-                impl->sRGBWrite_ = sRGBWrite;
-            }
-        }
-#endif
-    }
-
-    if (impl->vertexBuffersDirty_)
-    {
-        // Go through currently bound vertex buffers and set the attribute pointers that are available & required
-        // Use reverse order so that elements from higher index buffers will override lower index buffers
-        unsigned assignedLocations = 0;
-
-        for (i32 i = MAX_VERTEX_STREAMS - 1; i >= 0; --i)
-        {
-            VertexBuffer* buffer = vertexBuffers_[i];
-            // Beware buffers with missing OpenGL objects, as binding a zero buffer object means accessing CPU memory for vertex data,
-            // in which case the pointer will be invalid and cause a crash
-            if (!buffer || !buffer->GetGPUObjectName() || !impl->vertexAttributes_)
-                continue;
-
-            const Vector<VertexElement>& elements = buffer->GetElements();
-
-            for (Vector<VertexElement>::ConstIterator j = elements.Begin(); j != elements.End(); ++j)
-            {
-                const VertexElement& element = *j;
-                HashMap<Pair<i8, i8>, unsigned>::ConstIterator k =
-                    impl->vertexAttributes_->Find(MakePair((i8)element.semantic_, element.index_));
-
-                if (k != impl->vertexAttributes_->End())
-                {
-                    unsigned location = k->second_;
-                    unsigned locationMask = 1u << location;
-                    if (assignedLocations & locationMask)
-                        continue; // Already assigned by higher index vertex buffer
-                    assignedLocations |= locationMask;
-
-                    // Enable attribute if not enabled yet
-                    if (!(impl->enabledVertexAttributes_ & locationMask))
-                    {
-                        glEnableVertexAttribArray(location);
-                        impl->enabledVertexAttributes_ |= locationMask;
-                    }
-
-                    // Enable/disable instancing divisor as necessary
-                    unsigned dataStart = element.offset_;
-                    if (element.perInstance_)
-                    {
-                        dataStart += impl->lastInstanceOffset_ * buffer->GetVertexSize();
-                        if (!(impl->instancingVertexAttributes_ & locationMask))
-                        {
-                            SetVertexAttribDivisor_OGL(location, 1);
-                            impl->instancingVertexAttributes_ |= locationMask;
-                        }
-                    }
-                    else
-                    {
-                        if (impl->instancingVertexAttributes_ & locationMask)
-                        {
-                            SetVertexAttribDivisor_OGL(location, 0);
-                            impl->instancingVertexAttributes_ &= ~locationMask;
-                        }
-                    }
-
-                    SetVBO_OGL(buffer->GetGPUObjectName());
-                    glVertexAttribPointer(location, glElementComponents[element.type_], glElementTypes[element.type_],
-                        element.type_ == TYPE_UBYTE4_NORM ? GL_TRUE : GL_FALSE, (unsigned)buffer->GetVertexSize(),
-                        (const void *)(size_t)dataStart);
-                }
-            }
-        }
-
-        // Finally disable unnecessary vertex attributes
-        unsigned disableVertexAttributes = impl->enabledVertexAttributes_ & (~impl->usedVertexAttributes_);
-        unsigned location = 0;
-        while (disableVertexAttributes)
-        {
-            if (disableVertexAttributes & 1u)
-            {
-                glDisableVertexAttribArray(location);
-                impl->enabledVertexAttributes_ &= ~(1u << location);
-            }
-            ++location;
-            disableVertexAttributes >>= 1;
-        }
-
-        impl->vertexBuffersDirty_ = false;
-    }
-}
-
-void Graphics::CleanupFramebuffers_OGL()
-{
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    if (!IsDeviceLost_OGL())
-    {
-        BindFramebuffer_OGL(impl->systemFBO_);
-        impl->boundFBO_ = impl->systemFBO_;
-        impl->fboDirty_ = true;
-
+        if (i == impl->frameBuffers_.End())
+        {
+            FrameBufferObject newFbo;
+            newFbo.fbo_ = CreateFramebuffer_OGL();
+            i = impl->frameBuffers_.Insert(MakePair(fboKey, newFbo));
+        }
+
+        if (impl->boundFBO_ != i->second_.fbo_)
+        {
+            BindFramebuffer_OGL(i->second_.fbo_);
+            impl->boundFBO_ = i->second_.fbo_;
+        }
+
+#ifndef URHO3D_GLES2
+        // Setup readbuffers & drawbuffers if needed
+        if (i->second_.readBuffers_ != GL_NONE)
+        {
+            glReadBuffer(GL_NONE);
+            i->second_.readBuffers_ = GL_NONE;
+        }
+
+        // Calculate the bit combination of non-zero color rendertargets to first check if the combination changed
+        unsigned newDrawBuffers = 0;
+        for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j)
+        {
+            if (renderTargets_[j])
+                newDrawBuffers |= 1u << j;
+        }
+
+        if (newDrawBuffers != i->second_.drawBuffers_)
+        {
+            // Check for no color rendertargets (depth rendering only)
+            if (!newDrawBuffers)
+            {
+#ifndef GL_ES_VERSION_3_0
+                glDrawBuffer(GL_NONE);
+#else
+                const GLenum bufs[] = {GL_NONE, GL_NONE, GL_NONE, GL_NONE};
+                glDrawBuffers(sizeof(bufs) / sizeof(bufs[0]), bufs);
+#endif
+            }
+            else
+            {
+                int drawBufferIds[MAX_RENDERTARGETS];
+                unsigned drawBufferCount = 0;
+
+                for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j)
+                {
+                    if (renderTargets_[j])
+                    {
+#ifndef GL_ES_VERSION_3_0
+                        if (!gl3Support)
+                            drawBufferIds[drawBufferCount++] = GL_COLOR_ATTACHMENT0_EXT + j;
+                        else
+#endif
+                            drawBufferIds[drawBufferCount++] = GL_COLOR_ATTACHMENT0 + j;
+                    }
+                }
+                glDrawBuffers(drawBufferCount, (const GLenum*)drawBufferIds);
+            }
+
+            i->second_.drawBuffers_ = newDrawBuffers;
+        }
+#endif
+        for (unsigned j = 0; j < MAX_RENDERTARGETS; ++j)
+        {
+            if (renderTargets_[j])
+            {
+                Texture* texture = renderTargets_[j]->GetParentTexture();
+
+                // Bind either a renderbuffer or texture, depending on what is available
+                unsigned renderBufferID = renderTargets_[j]->GetRenderBuffer();
+                if (!renderBufferID)
+                {
+                    // If texture's parameters are dirty, update before attaching
+                    if (texture->GetParametersDirty())
+                    {
+                        SetTextureForUpdate_OGL(texture);
+                        texture->UpdateParameters();
+                        SetTexture_OGL(0, nullptr);
+                    }
+
+                    if (i->second_.colorAttachments_[j] != renderTargets_[j])
+                    {
+                        BindColorAttachment_OGL(j, renderTargets_[j]->GetTarget(), texture->GetGPUObjectName(), false);
+                        i->second_.colorAttachments_[j] = renderTargets_[j];
+                    }
+                }
+                else
+                {
+                    if (i->second_.colorAttachments_[j] != renderTargets_[j])
+                    {
+                        BindColorAttachment_OGL(j, renderTargets_[j]->GetTarget(), renderBufferID, true);
+                        i->second_.colorAttachments_[j] = renderTargets_[j];
+                    }
+                }
+            }
+            else
+            {
+                if (i->second_.colorAttachments_[j])
+                {
+                    BindColorAttachment_OGL(j, GL_TEXTURE_2D, 0, false);
+                    i->second_.colorAttachments_[j] = nullptr;
+                }
+            }
+        }
+
+        if (depthStencil_)
+        {
+            // Bind either a renderbuffer or a depth texture, depending on what is available
+            Texture* texture = depthStencil_->GetParentTexture();
+#ifndef URHO3D_GLES2
+            bool hasStencil = texture->GetFormat() == GL_DEPTH24_STENCIL8_EXT;
+#else
+            bool hasStencil = texture->GetFormat() == GL_DEPTH24_STENCIL8_OES;
+#endif
+            unsigned renderBufferID = depthStencil_->GetRenderBuffer();
+            if (!renderBufferID)
+            {
+                // If texture's parameters are dirty, update before attaching
+                if (texture->GetParametersDirty())
+                {
+                    SetTextureForUpdate_OGL(texture);
+                    texture->UpdateParameters();
+                    SetTexture_OGL(0, nullptr);
+                }
+
+                if (i->second_.depthAttachment_ != depthStencil_)
+                {
+                    BindDepthAttachment_OGL(texture->GetGPUObjectName(), false);
+                    BindStencilAttachment_OGL(hasStencil ? texture->GetGPUObjectName() : 0, false);
+                    i->second_.depthAttachment_ = depthStencil_;
+                }
+            }
+            else
+            {
+                if (i->second_.depthAttachment_ != depthStencil_)
+                {
+                    BindDepthAttachment_OGL(renderBufferID, true);
+                    BindStencilAttachment_OGL(hasStencil ? renderBufferID : 0, true);
+                    i->second_.depthAttachment_ = depthStencil_;
+                }
+            }
+        }
+        else
+        {
+            if (i->second_.depthAttachment_)
+            {
+                BindDepthAttachment_OGL(0, false);
+                BindStencilAttachment_OGL(0, false);
+                i->second_.depthAttachment_ = nullptr;
+            }
+        }
+
+#ifndef GL_ES_VERSION_2_0
+        // Disable/enable sRGB write
+        if (sRGBWriteSupport_)
+        {
+            bool sRGBWrite = renderTargets_[0] ? renderTargets_[0]->GetParentTexture()->GetSRGB() : sRGB_;
+            if (sRGBWrite != impl->sRGBWrite_)
+            {
+                if (sRGBWrite)
+                    glEnable(GL_FRAMEBUFFER_SRGB_EXT);
+                else
+                    glDisable(GL_FRAMEBUFFER_SRGB_EXT);
+                impl->sRGBWrite_ = sRGBWrite;
+            }
+        }
+#endif
+    }
+
+    if (impl->vertexBuffersDirty_)
+    {
+        // Go through currently bound vertex buffers and set the attribute pointers that are available & required
+        // Use reverse order so that elements from higher index buffers will override lower index buffers
+        unsigned assignedLocations = 0;
+
+        for (i32 i = MAX_VERTEX_STREAMS - 1; i >= 0; --i)
+        {
+            VertexBuffer* buffer = vertexBuffers_[i];
+            // Beware buffers with missing OpenGL objects, as binding a zero buffer object means accessing CPU memory for vertex data,
+            // in which case the pointer will be invalid and cause a crash
+            if (!buffer || !buffer->GetGPUObjectName() || !impl->vertexAttributes_)
+                continue;
+
+            const Vector<VertexElement>& elements = buffer->GetElements();
+
+            for (Vector<VertexElement>::ConstIterator j = elements.Begin(); j != elements.End(); ++j)
+            {
+                const VertexElement& element = *j;
+                HashMap<Pair<i8, i8>, unsigned>::ConstIterator k =
+                    impl->vertexAttributes_->Find(MakePair((i8)element.semantic_, element.index_));
+
+                if (k != impl->vertexAttributes_->End())
+                {
+                    unsigned location = k->second_;
+                    unsigned locationMask = 1u << location;
+                    if (assignedLocations & locationMask)
+                        continue; // Already assigned by higher index vertex buffer
+                    assignedLocations |= locationMask;
+
+                    // Enable attribute if not enabled yet
+                    if (!(impl->enabledVertexAttributes_ & locationMask))
+                    {
+                        glEnableVertexAttribArray(location);
+                        impl->enabledVertexAttributes_ |= locationMask;
+                    }
+
+                    // Enable/disable instancing divisor as necessary
+                    unsigned dataStart = element.offset_;
+                    if (element.perInstance_)
+                    {
+                        dataStart += impl->lastInstanceOffset_ * buffer->GetVertexSize();
+                        if (!(impl->instancingVertexAttributes_ & locationMask))
+                        {
+                            SetVertexAttribDivisor_OGL(location, 1);
+                            impl->instancingVertexAttributes_ |= locationMask;
+                        }
+                    }
+                    else
+                    {
+                        if (impl->instancingVertexAttributes_ & locationMask)
+                        {
+                            SetVertexAttribDivisor_OGL(location, 0);
+                            impl->instancingVertexAttributes_ &= ~locationMask;
+                        }
+                    }
+
+                    SetVBO_OGL(buffer->GetGPUObjectName());
+                    glVertexAttribPointer(location, glElementComponents[element.type_], glElementTypes[element.type_],
+                        element.type_ == TYPE_UBYTE4_NORM ? GL_TRUE : GL_FALSE, (unsigned)buffer->GetVertexSize(),
+                        (const void *)(size_t)dataStart);
+                }
+            }
+        }
+
+        // Finally disable unnecessary vertex attributes
+        unsigned disableVertexAttributes = impl->enabledVertexAttributes_ & (~impl->usedVertexAttributes_);
+        unsigned location = 0;
+        while (disableVertexAttributes)
+        {
+            if (disableVertexAttributes & 1u)
+            {
+                glDisableVertexAttribArray(location);
+                impl->enabledVertexAttributes_ &= ~(1u << location);
+            }
+            ++location;
+            disableVertexAttributes >>= 1;
+        }
+
+        impl->vertexBuffersDirty_ = false;
+    }
+}
+
+void Graphics::CleanupFramebuffers_OGL()
+{
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    if (!IsDeviceLost_OGL())
+    {
+        BindFramebuffer_OGL(impl->systemFBO_);
+        impl->boundFBO_ = impl->systemFBO_;
+        impl->fboDirty_ = true;
+
         for (HashMap<hash64, FrameBufferObject>::Iterator i = impl->frameBuffers_.Begin();
-             i != impl->frameBuffers_.End(); ++i)
-            DeleteFramebuffer_OGL(i->second_.fbo_);
-
-        if (impl->resolveSrcFBO_)
-            DeleteFramebuffer_OGL(impl->resolveSrcFBO_);
-        if (impl->resolveDestFBO_)
-            DeleteFramebuffer_OGL(impl->resolveDestFBO_);
-    }
-    else
-        impl->boundFBO_ = 0;
-
-    impl->resolveSrcFBO_ = 0;
-    impl->resolveDestFBO_ = 0;
-
-    impl->frameBuffers_.Clear();
-}
-
-void Graphics::ResetCachedState_OGL()
-{
-    for (auto& vertexBuffer : vertexBuffers_)
-        vertexBuffer = nullptr;
-
-    GraphicsImpl_OGL* impl = GetImpl_OGL();
-
-    for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
-    {
-        textures_[i] = nullptr;
-        impl->textureTypes_[i] = 0;
-    }
-
-    for (auto& renderTarget : renderTargets_)
-        renderTarget = nullptr;
-
-    depthStencil_ = nullptr;
-    viewport_ = IntRect(0, 0, 0, 0);
-    indexBuffer_ = nullptr;
-    vertexShader_ = nullptr;
-    pixelShader_ = nullptr;
-    blendMode_ = BLEND_REPLACE;
-    alphaToCoverage_ = false;
-    colorWrite_ = true;
-    cullMode_ = CULL_NONE;
-    constantDepthBias_ = 0.0f;
-    slopeScaledDepthBias_ = 0.0f;
-    depthTestMode_ = CMP_ALWAYS;
-    depthWrite_ = false;
-    lineAntiAlias_ = false;
-    fillMode_ = FILL_SOLID;
-    scissorTest_ = false;
-    scissorRect_ = IntRect::ZERO;
-    stencilTest_ = false;
-    stencilTestMode_ = CMP_ALWAYS;
-    stencilPass_ = OP_KEEP;
-    stencilFail_ = OP_KEEP;
-    stencilZFail_ = OP_KEEP;
-    stencilRef_ = 0;
-    stencilCompareMask_ = M_U32_MASK_ALL_BITS;
-    stencilWriteMask_ = M_U32_MASK_ALL_BITS;
-    useClipPlane_ = false;
-    impl->shaderProgram_ = nullptr;
-    impl->lastInstanceOffset_ = 0;
-    impl->activeTexture_ = 0;
-    impl->enabledVertexAttributes_ = 0;
-    impl->usedVertexAttributes_ = 0;
-    impl->instancingVertexAttributes_ = 0;
-    impl->boundFBO_ = impl->systemFBO_;
-    impl->boundVBO_ = 0;
-    impl->boundUBO_ = 0;
-    impl->sRGBWrite_ = false;
-
-    // Set initial state to match Direct3D
-    if (impl->context_)
-    {
-        glEnable(GL_DEPTH_TEST);
-        SetCullMode_OGL(CULL_CCW);
-        SetDepthTest_OGL(CMP_LESSEQUAL);
-        SetDepthWrite_OGL(true);
-    }
-
-    for (auto& constantBuffer : impl->constantBuffers_)
-        constantBuffer = nullptr;
-    impl->dirtyConstantBuffers_.Clear();
-}
-
-void Graphics::SetTextureUnitMappings_OGL()
-{
-    textureUnits_["DiffMap"] = TU_DIFFUSE;
-    textureUnits_["DiffCubeMap"] = TU_DIFFUSE;
-    textureUnits_["AlbedoBuffer"] = TU_ALBEDOBUFFER;
-    textureUnits_["NormalMap"] = TU_NORMAL;
-    textureUnits_["NormalBuffer"] = TU_NORMALBUFFER;
-    textureUnits_["SpecMap"] = TU_SPECULAR;
-    textureUnits_["EmissiveMap"] = TU_EMISSIVE;
-    textureUnits_["EnvMap"] = TU_ENVIRONMENT;
-    textureUnits_["EnvCubeMap"] = TU_ENVIRONMENT;
-    textureUnits_["LightRampMap"] = TU_LIGHTRAMP;
-    textureUnits_["LightSpotMap"] = TU_LIGHTSHAPE;
-    textureUnits_["LightCubeMap"] = TU_LIGHTSHAPE;
-    textureUnits_["ShadowMap"] = TU_SHADOWMAP;
-#ifndef GL_ES_VERSION_2_0
-    textureUnits_["VolumeMap"] = TU_VOLUMEMAP;
-    textureUnits_["FaceSelectCubeMap"] = TU_FACESELECT;
-    textureUnits_["IndirectionCubeMap"] = TU_INDIRECTION;
-    textureUnits_["DepthBuffer"] = TU_DEPTHBUFFER;
-    textureUnits_["LightBuffer"] = TU_LIGHTBUFFER;
-    textureUnits_["ZoneCubeMap"] = TU_ZONE;
-    textureUnits_["ZoneVolumeMap"] = TU_ZONE;
-#endif
-}
-
-unsigned Graphics::CreateFramebuffer_OGL()
-{
-    unsigned newFbo = 0;
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support)
-        glGenFramebuffersEXT(1, &newFbo);
-    else
-#endif
-        glGenFramebuffers(1, &newFbo);
-    return newFbo;
-}
-
-void Graphics::DeleteFramebuffer_OGL(unsigned fbo)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support)
-        glDeleteFramebuffersEXT(1, &fbo);
-    else
-#endif
-        glDeleteFramebuffers(1, &fbo);
-}
-
-void Graphics::BindFramebuffer_OGL(unsigned fbo)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support)
-        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
-    else
-#endif
-        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
-}
-
-void Graphics::BindColorAttachment_OGL(unsigned index, unsigned target, unsigned object, bool isRenderBuffer)
-{
-    if (!object)
-        isRenderBuffer = false;
-
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support)
-    {
-        if (!isRenderBuffer)
-            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, target, object, 0);
-        else
-            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, GL_RENDERBUFFER_EXT, object);
-    }
-    else
-#endif
-    {
-        if (!isRenderBuffer)
-            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, target, object, 0);
-        else
-            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, GL_RENDERBUFFER, object);
-    }
-}
-
-void Graphics::BindDepthAttachment_OGL(unsigned object, bool isRenderBuffer)
-{
-    if (!object)
-        isRenderBuffer = false;
-
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support)
-    {
-        if (!isRenderBuffer)
-            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, object, 0);
-        else
-            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, object);
-    }
-    else
-#endif
-    {
-        if (!isRenderBuffer)
-            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, object, 0);
-        else
-            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, object);
-    }
-}
-
-void Graphics::BindStencilAttachment_OGL(unsigned object, bool isRenderBuffer)
-{
-    if (!object)
-        isRenderBuffer = false;
-
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support)
-    {
-        if (!isRenderBuffer)
-            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, object, 0);
-        else
-            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, object);
-    }
-    else
-#endif
-    {
-        if (!isRenderBuffer)
-            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, object, 0);
-        else
-            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, object);
-    }
-}
-
-bool Graphics::CheckFramebuffer_OGL()
-{
-#ifndef GL_ES_VERSION_2_0
-    if (!gl3Support)
-        return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT;
-    else
-#endif
-        return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
-}
-
-void Graphics::SetVertexAttribDivisor_OGL(unsigned location, unsigned divisor)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (gl3Support && instancingSupport_)
-        glVertexAttribDivisor(location, divisor);
-    else if (instancingSupport_)
-        glVertexAttribDivisorARB(location, divisor);
-#else
-#ifdef __EMSCRIPTEN__
-    if (instancingSupport_)
-        glVertexAttribDivisorANGLE(location, divisor);
-#endif
-#endif
-}
-
-}
+             i != impl->frameBuffers_.End(); ++i)
+            DeleteFramebuffer_OGL(i->second_.fbo_);
+
+        if (impl->resolveSrcFBO_)
+            DeleteFramebuffer_OGL(impl->resolveSrcFBO_);
+        if (impl->resolveDestFBO_)
+            DeleteFramebuffer_OGL(impl->resolveDestFBO_);
+    }
+    else
+        impl->boundFBO_ = 0;
+
+    impl->resolveSrcFBO_ = 0;
+    impl->resolveDestFBO_ = 0;
+
+    impl->frameBuffers_.Clear();
+}
+
+void Graphics::ResetCachedState_OGL()
+{
+    for (auto& vertexBuffer : vertexBuffers_)
+        vertexBuffer = nullptr;
+
+    GraphicsImpl_OGL* impl = GetImpl_OGL();
+
+    for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
+    {
+        textures_[i] = nullptr;
+        impl->textureTypes_[i] = 0;
+    }
+
+    for (auto& renderTarget : renderTargets_)
+        renderTarget = nullptr;
+
+    depthStencil_ = nullptr;
+    viewport_ = IntRect(0, 0, 0, 0);
+    indexBuffer_ = nullptr;
+    vertexShader_ = nullptr;
+    pixelShader_ = nullptr;
+    blendMode_ = BLEND_REPLACE;
+    alphaToCoverage_ = false;
+    colorWrite_ = true;
+    cullMode_ = CULL_NONE;
+    constantDepthBias_ = 0.0f;
+    slopeScaledDepthBias_ = 0.0f;
+    depthTestMode_ = CMP_ALWAYS;
+    depthWrite_ = false;
+    lineAntiAlias_ = false;
+    fillMode_ = FILL_SOLID;
+    scissorTest_ = false;
+    scissorRect_ = IntRect::ZERO;
+    stencilTest_ = false;
+    stencilTestMode_ = CMP_ALWAYS;
+    stencilPass_ = OP_KEEP;
+    stencilFail_ = OP_KEEP;
+    stencilZFail_ = OP_KEEP;
+    stencilRef_ = 0;
+    stencilCompareMask_ = M_U32_MASK_ALL_BITS;
+    stencilWriteMask_ = M_U32_MASK_ALL_BITS;
+    useClipPlane_ = false;
+    impl->shaderProgram_ = nullptr;
+    impl->lastInstanceOffset_ = 0;
+    impl->activeTexture_ = 0;
+    impl->enabledVertexAttributes_ = 0;
+    impl->usedVertexAttributes_ = 0;
+    impl->instancingVertexAttributes_ = 0;
+    impl->boundFBO_ = impl->systemFBO_;
+    impl->boundVBO_ = 0;
+    impl->boundUBO_ = 0;
+    impl->sRGBWrite_ = false;
+
+    // Set initial state to match Direct3D
+    if (impl->context_)
+    {
+        glEnable(GL_DEPTH_TEST);
+        SetCullMode_OGL(CULL_CCW);
+        SetDepthTest_OGL(CMP_LESSEQUAL);
+        SetDepthWrite_OGL(true);
+    }
+
+    for (auto& constantBuffer : impl->constantBuffers_)
+        constantBuffer = nullptr;
+    impl->dirtyConstantBuffers_.Clear();
+}
+
+void Graphics::SetTextureUnitMappings_OGL()
+{
+    textureUnits_["DiffMap"] = TU_DIFFUSE;
+    textureUnits_["DiffCubeMap"] = TU_DIFFUSE;
+    textureUnits_["AlbedoBuffer"] = TU_ALBEDOBUFFER;
+    textureUnits_["NormalMap"] = TU_NORMAL;
+    textureUnits_["NormalBuffer"] = TU_NORMALBUFFER;
+    textureUnits_["SpecMap"] = TU_SPECULAR;
+    textureUnits_["EmissiveMap"] = TU_EMISSIVE;
+    textureUnits_["EnvMap"] = TU_ENVIRONMENT;
+    textureUnits_["EnvCubeMap"] = TU_ENVIRONMENT;
+    textureUnits_["LightRampMap"] = TU_LIGHTRAMP;
+    textureUnits_["LightSpotMap"] = TU_LIGHTSHAPE;
+    textureUnits_["LightCubeMap"] = TU_LIGHTSHAPE;
+    textureUnits_["ShadowMap"] = TU_SHADOWMAP;
+#ifndef URHO3D_GLES2
+    textureUnits_["VolumeMap"] = TU_VOLUMEMAP;
+    textureUnits_["FaceSelectCubeMap"] = TU_FACESELECT;
+    textureUnits_["IndirectionCubeMap"] = TU_INDIRECTION;
+    textureUnits_["DepthBuffer"] = TU_DEPTHBUFFER;
+    textureUnits_["LightBuffer"] = TU_LIGHTBUFFER;
+    textureUnits_["ZoneCubeMap"] = TU_ZONE;
+    textureUnits_["ZoneVolumeMap"] = TU_ZONE;
+#endif
+}
+
+unsigned Graphics::CreateFramebuffer_OGL()
+{
+    unsigned newFbo = 0;
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support)
+        glGenFramebuffersEXT(1, &newFbo);
+    else
+#endif
+        glGenFramebuffers(1, &newFbo);
+    return newFbo;
+}
+
+void Graphics::DeleteFramebuffer_OGL(unsigned fbo)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support)
+        glDeleteFramebuffersEXT(1, &fbo);
+    else
+#endif
+        glDeleteFramebuffers(1, &fbo);
+}
+
+void Graphics::BindFramebuffer_OGL(unsigned fbo)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support)
+        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
+    else
+#endif
+        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+}
+
+void Graphics::BindColorAttachment_OGL(unsigned index, unsigned target, unsigned object, bool isRenderBuffer)
+{
+    if (!object)
+        isRenderBuffer = false;
+
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support)
+    {
+        if (!isRenderBuffer)
+            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, target, object, 0);
+        else
+            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + index, GL_RENDERBUFFER_EXT, object);
+    }
+    else
+#endif
+    {
+        if (!isRenderBuffer)
+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, target, object, 0);
+        else
+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, GL_RENDERBUFFER, object);
+    }
+}
+
+void Graphics::BindDepthAttachment_OGL(unsigned object, bool isRenderBuffer)
+{
+    if (!object)
+        isRenderBuffer = false;
+
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support)
+    {
+        if (!isRenderBuffer)
+            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, object, 0);
+        else
+            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, object);
+    }
+    else
+#endif
+    {
+        if (!isRenderBuffer)
+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, object, 0);
+        else
+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, object);
+    }
+}
+
+void Graphics::BindStencilAttachment_OGL(unsigned object, bool isRenderBuffer)
+{
+    if (!object)
+        isRenderBuffer = false;
+
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support)
+    {
+        if (!isRenderBuffer)
+            glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_TEXTURE_2D, object, 0);
+        else
+            glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, object);
+    }
+    else
+#endif
+    {
+        if (!isRenderBuffer)
+            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, object, 0);
+        else
+            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, object);
+    }
+}
+
+bool Graphics::CheckFramebuffer_OGL()
+{
+#ifndef GL_ES_VERSION_2_0
+    if (!gl3Support)
+        return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT;
+    else
+#endif
+        return glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
+}
+
+void Graphics::SetVertexAttribDivisor_OGL(unsigned location, unsigned divisor)
+{
+#ifndef URHO3D_GLES2
+#ifndef GL_ES_VERSION_3_0
+    if (gl3Support && instancingSupport_)
+#endif
+        glVertexAttribDivisor(location, divisor);
+#ifndef GL_ES_VERSION_3_0
+    else if (instancingSupport_)
+        glVertexAttribDivisorARB(location, divisor);
+#endif
+#else
+#ifdef __EMSCRIPTEN__
+    if (instancingSupport_)
+        glVertexAttribDivisorANGLE(location, divisor);
+#endif
+#endif
+}
+
+}

+ 150 - 128
Source/Urho3D/GraphicsAPI/OpenGL/OGLGraphicsImpl.h

@@ -1,144 +1,166 @@
 // Copyright (c) 2008-2022 the Urho3D project
 // License: MIT
-
-#pragma once
-
-#include "../../Container/HashMap.h"
-#include "../../Core/Timer.h"
+
+#pragma once
+
+#include "../../Container/HashMap.h"
+#include "../../Core/Timer.h"
 #include "../../GraphicsAPI/ConstantBuffer.h"
 #include "../../GraphicsAPI/OpenGL/OGLShaderProgram.h"
 #include "../../GraphicsAPI/Texture2D.h"
-#include "../../Math/Color.h"
-
-#if defined(IOS) || defined(TVOS)
-#include <OpenGLES/ES2/gl.h>
-#include <OpenGLES/ES2/glext.h>
-#elif defined(__ANDROID__) || defined (__arm__) || defined(__aarch64__) || defined (__EMSCRIPTEN__)
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#else
-#include <GLEW/glew.h>
-#endif
-
-#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
-#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83f1
-#endif
-#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
-#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83f2
-#endif
-#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
-#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83f3
-#endif
-#ifndef GL_ETC1_RGB8_OES
-#define GL_ETC1_RGB8_OES 0x8d64
-#endif
-#ifndef GL_ETC2_RGB8_OES
-#define GL_ETC2_RGB8_OES 0x9274
-#endif
-#ifndef GL_ETC2_RGBA8_OES
-#define GL_ETC2_RGBA8_OES 0x9278
-#endif
-#ifndef COMPRESSED_RGB_PVRTC_4BPPV1_IMG
-#define COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8c00
-#endif
-#ifndef COMPRESSED_RGB_PVRTC_2BPPV1_IMG
-#define COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8c01
-#endif
-#ifndef COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
-#define COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8c02
-#endif
-#ifndef COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
-#define COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8c03
-#endif
-
-using SDL_GLContext = void *;
-
-namespace Urho3D
-{
-
-class Context;
-
+#include "../../Math/Color.h"
+
+#if defined(IOS) || defined(TVOS)
+#if URHO3D_GLES3
+#include <OpenGLES/ES3/gl.h>
+#include <OpenGLES/ES3/glext.h>
+#else
+#include <OpenGLES/ES2/gl.h>
+#include <OpenGLES/ES2/glext.h>
+#define URHO3D_GLES2
+#endif
+#elif defined(__ANDROID__) || defined (__arm__) || defined(__aarch64__) || defined (__EMSCRIPTEN__)
+#if URHO3D_GLES3
+#include <GLES3/gl3.h>
+#ifndef __EMSCRIPTEN__
+#include <GLES3/gl3ext.h>
+#else
+#include <GLES3/gl2ext.h>
+#endif
+#else
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#define URHO3D_GLES2
+#endif
+#else
+#include <GLEW/glew.h>
+#endif
+
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
+#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83f1
+#endif
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
+#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83f2
+#endif
+#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
+#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83f3
+#endif
+#ifndef GL_ETC1_RGB8_OES
+#define GL_ETC1_RGB8_OES 0x8d64
+#endif
+#ifndef GL_ETC2_RGB8_OES
+#define GL_ETC2_RGB8_OES 0x9274
+#endif
+#ifndef GL_ETC2_RGBA8_OES
+#define GL_ETC2_RGBA8_OES 0x9278
+#endif
+#ifndef GL_DEPTH_COMPONENT24_OES
+#define GL_DEPTH_COMPONENT24_OES 0x81A6
+#endif
+#ifndef GL_DEPTH24_STENCIL8_OES
+#define GL_DEPTH24_STENCIL8_OES 0x88F0
+#endif
+#ifndef COMPRESSED_RGB_PVRTC_4BPPV1_IMG
+#define COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8c00
+#endif
+#ifndef COMPRESSED_RGB_PVRTC_2BPPV1_IMG
+#define COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8c01
+#endif
+#ifndef COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
+#define COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8c02
+#endif
+#ifndef COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
+#define COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8c03
+#endif
+
+using SDL_GLContext = void *;
+
+namespace Urho3D
+{
+
+class Context;
+
 using ConstantBufferMap = HashMap<unsigned, SharedPtr<ConstantBuffer>>;
 using ShaderProgramMap_OGL = HashMap<Pair<ShaderVariation*, ShaderVariation*>, SharedPtr<ShaderProgram_OGL>>;
-
+
 /// Cached state of a frame buffer object.
-struct FrameBufferObject
-{
-    /// Frame buffer handle.
-    unsigned fbo_{};
-    /// Bound color attachment textures.
-    RenderSurface* colorAttachments_[MAX_RENDERTARGETS]{};
-    /// Bound depth/stencil attachment.
-    RenderSurface* depthAttachment_{};
-    /// Read buffer bits.
-    unsigned readBuffers_{M_MAX_UNSIGNED};
-    /// Draw buffer bits.
-    unsigned drawBuffers_{M_MAX_UNSIGNED};
-};
-
-/// %Graphics subsystem implementation. Holds API-specific objects.
+struct FrameBufferObject
+{
+    /// Frame buffer handle.
+    unsigned fbo_{};
+    /// Bound color attachment textures.
+    RenderSurface* colorAttachments_[MAX_RENDERTARGETS]{};
+    /// Bound depth/stencil attachment.
+    RenderSurface* depthAttachment_{};
+    /// Read buffer bits.
+    unsigned readBuffers_{M_MAX_UNSIGNED};
+    /// Draw buffer bits.
+    unsigned drawBuffers_{M_MAX_UNSIGNED};
+};
+
+/// %Graphics subsystem implementation. Holds API-specific objects.
 class URHO3D_API GraphicsImpl_OGL
-{
-    friend class Graphics;
-
-public:
-    /// Construct.
+{
+    friend class Graphics;
+
+public:
+    /// Construct.
     GraphicsImpl_OGL() = default;
-
-    /// Return the GL Context.
-    const SDL_GLContext& GetGLContext() { return context_; }
-
-private:
-    /// SDL OpenGL context.
-    SDL_GLContext context_{};
-    /// iOS/tvOS system framebuffer handle.
-    unsigned systemFBO_{};
-    /// Active texture unit.
-    unsigned activeTexture_{};
-    /// Enabled vertex attributes bitmask.
-    unsigned enabledVertexAttributes_{};
-    /// Vertex attributes bitmask used by the current shader program.
-    unsigned usedVertexAttributes_{};
-    /// Vertex attribute instancing bitmask for keeping track of divisors.
-    unsigned instancingVertexAttributes_{};
-    /// Current mapping of vertex attribute locations by semantic. The map is owned by the shader program, so care must be taken to switch a null shader program when it's destroyed.
+
+    /// Return the GL Context.
+    const SDL_GLContext& GetGLContext() { return context_; }
+
+private:
+    /// SDL OpenGL context.
+    SDL_GLContext context_{};
+    /// iOS/tvOS system framebuffer handle.
+    unsigned systemFBO_{};
+    /// Active texture unit.
+    unsigned activeTexture_{};
+    /// Enabled vertex attributes bitmask.
+    unsigned enabledVertexAttributes_{};
+    /// Vertex attributes bitmask used by the current shader program.
+    unsigned usedVertexAttributes_{};
+    /// Vertex attribute instancing bitmask for keeping track of divisors.
+    unsigned instancingVertexAttributes_{};
+    /// Current mapping of vertex attribute locations by semantic. The map is owned by the shader program, so care must be taken to switch a null shader program when it's destroyed.
     const HashMap<Pair<i8, i8>, unsigned>* vertexAttributes_{};
-    /// Currently bound frame buffer object.
-    unsigned boundFBO_{};
-    /// Currently bound vertex buffer object.
-    unsigned boundVBO_{};
-    /// Currently bound uniform buffer object.
-    unsigned boundUBO_{};
-    /// Read frame buffer for multisampled texture resolves.
-    unsigned resolveSrcFBO_{};
-    /// Write frame buffer for multisampled texture resolves.
-    unsigned resolveDestFBO_{};
-    /// Current pixel format.
-    int pixelFormat_{};
-    /// Map for FBO's per resolution and format.
+    /// Currently bound frame buffer object.
+    unsigned boundFBO_{};
+    /// Currently bound vertex buffer object.
+    unsigned boundVBO_{};
+    /// Currently bound uniform buffer object.
+    unsigned boundUBO_{};
+    /// Read frame buffer for multisampled texture resolves.
+    unsigned resolveSrcFBO_{};
+    /// Write frame buffer for multisampled texture resolves.
+    unsigned resolveDestFBO_{};
+    /// Current pixel format.
+    int pixelFormat_{};
+    /// Map for FBO's per resolution and format.
     HashMap<hash64, FrameBufferObject> frameBuffers_;
-    /// OpenGL texture types in use.
-    unsigned textureTypes_[MAX_TEXTURE_UNITS]{};
-    /// Constant buffer search map.
-    ConstantBufferMap allConstantBuffers_;
-    /// Currently bound constant buffers.
-    ConstantBuffer* constantBuffers_[MAX_SHADER_PARAMETER_GROUPS * 2]{};
-    /// Dirty constant buffers.
+    /// OpenGL texture types in use.
+    unsigned textureTypes_[MAX_TEXTURE_UNITS]{};
+    /// Constant buffer search map.
+    ConstantBufferMap allConstantBuffers_;
+    /// Currently bound constant buffers.
+    ConstantBuffer* constantBuffers_[MAX_SHADER_PARAMETER_GROUPS * 2]{};
+    /// Dirty constant buffers.
     Vector<ConstantBuffer*> dirtyConstantBuffers_;
-    /// Last used instance data offset.
-    unsigned lastInstanceOffset_{};
-    /// Map for additional depth textures, to emulate Direct3D9 ability to mix render texture and backbuffer rendering.
+    /// Last used instance data offset.
+    unsigned lastInstanceOffset_{};
+    /// Map for additional depth textures, to emulate Direct3D9 ability to mix render texture and backbuffer rendering.
     HashMap<unsigned, SharedPtr<Texture2D>> depthTextures_;
-    /// Shader program in use.
+    /// Shader program in use.
     ShaderProgram_OGL* shaderProgram_{};
-    /// Linked shader programs.
+    /// Linked shader programs.
     ShaderProgramMap_OGL shaderPrograms_;
-    /// Need FBO commit flag.
-    bool fboDirty_{};
-    /// Need vertex attribute pointer update flag.
-    bool vertexBuffersDirty_{};
-    /// sRGB write mode flag.
-    bool sRGBWrite_{};
-};
-
-}
+    /// Need FBO commit flag.
+    bool fboDirty_{};
+    /// Need vertex attribute pointer update flag.
+    bool vertexBuffersDirty_{};
+    /// sRGB write mode flag.
+    bool sRGBWrite_{};
+};
+
+}

+ 103 - 102
Source/Urho3D/GraphicsAPI/OpenGL/OGLRenderSurface.cpp

@@ -1,120 +1,121 @@
 // Copyright (c) 2008-2022 the Urho3D project
 // License: MIT
-
-#include "../../Precompiled.h"
-
-#include "../../Graphics/Camera.h"
-#include "../../Graphics/Graphics.h"
-#include "../../Graphics/Renderer.h"
+
+#include "../../Precompiled.h"
+
+#include "../../Graphics/Camera.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/Renderer.h"
 #include "../../GraphicsAPI/GraphicsImpl.h"
 #include "../../GraphicsAPI/RenderSurface.h"
 #include "../../GraphicsAPI/Texture.h"
-
-#include "../../DebugNew.h"
-
-#ifdef GL_ES_VERSION_2_0
-#define GL_RENDERBUFFER_EXT GL_RENDERBUFFER
-#define glGenRenderbuffersEXT glGenRenderbuffers
-#define glBindRenderbufferEXT glBindRenderbuffer
-#define glRenderbufferStorageEXT glRenderbufferStorage
-#define glDeleteRenderbuffersEXT glDeleteRenderbuffers
-#endif
-
-namespace Urho3D
-{
-
+
+#include "../../DebugNew.h"
+
+#ifdef GL_ES_VERSION_2_0
+#define GL_RENDERBUFFER_EXT GL_RENDERBUFFER
+#define glGenRenderbuffersEXT glGenRenderbuffers
+#define glBindRenderbufferEXT glBindRenderbuffer
+#define glRenderbufferStorageEXT glRenderbufferStorage
+#define glDeleteRenderbuffersEXT glDeleteRenderbuffers
+#define glRenderbufferStorageMultisampleEXT glRenderbufferStorageMultisample
+#endif
+
+namespace Urho3D
+{
+
 void RenderSurface::Constructor_OGL(Texture* parentTexture)
-{
+{
     parentTexture_ = parentTexture;
     target_ = GL_TEXTURE_2D;
     renderBuffer_ = 0;
-}
-
+}
+
 bool RenderSurface::CreateRenderBuffer_OGL(unsigned width, unsigned height, unsigned format, int multiSample)
-{
-    Graphics* graphics = parentTexture_->GetGraphics();
-    if (!graphics)
-        return false;
-
+{
+    Graphics* graphics = parentTexture_->GetGraphics();
+    if (!graphics)
+        return false;
+
     Release_OGL();
-
-#ifndef GL_ES_VERSION_2_0
-    if (Graphics::GetGL3Support())
-    {
-        glGenRenderbuffers(1, &renderBuffer_);
-        glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer_);
-        if (multiSample > 1)
-            glRenderbufferStorageMultisample(GL_RENDERBUFFER, multiSample, format, width, height);
-        else
-            glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
-        glBindRenderbuffer(GL_RENDERBUFFER, 0);
-    }
-    else
-#endif
-    {
-        glGenRenderbuffersEXT(1, &renderBuffer_);
-        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer_);
-#ifndef GL_ES_VERSION_2_0
-        if (multiSample > 1)
-            glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, multiSample, format, width, height);
-        else
-#endif
-            glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, format, width, height);
-        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
-    }
-
-    return true;
-}
-
+
+#ifndef GL_ES_VERSION_2_0
+    if (Graphics::GetGL3Support())
+    {
+        glGenRenderbuffers(1, &renderBuffer_);
+        glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer_);
+        if (multiSample > 1)
+            glRenderbufferStorageMultisample(GL_RENDERBUFFER, multiSample, format, width, height);
+        else
+            glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
+        glBindRenderbuffer(GL_RENDERBUFFER, 0);
+    }
+    else
+#endif
+    {
+        glGenRenderbuffersEXT(1, &renderBuffer_);
+        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderBuffer_);
+#ifndef URHO3D_GLES2
+        if (multiSample > 1)
+            glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, multiSample, format, width, height);
+        else
+#endif
+            glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, format, width, height);
+        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
+    }
+
+    return true;
+}
+
 void RenderSurface::OnDeviceLost_OGL()
-{
-    Graphics* graphics = parentTexture_->GetGraphics();
-    if (!graphics)
-        return;
-
-    for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i)
-    {
-        if (graphics->GetRenderTarget(i) == this)
-            graphics->ResetRenderTarget(i);
-    }
-
-    if (graphics->GetDepthStencil() == this)
-        graphics->ResetDepthStencil();
-
-    // Clean up also from non-active FBOs
+{
+    Graphics* graphics = parentTexture_->GetGraphics();
+    if (!graphics)
+        return;
+
+    for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i)
+    {
+        if (graphics->GetRenderTarget(i) == this)
+            graphics->ResetRenderTarget(i);
+    }
+
+    if (graphics->GetDepthStencil() == this)
+        graphics->ResetDepthStencil();
+
+    // Clean up also from non-active FBOs
     graphics->CleanupRenderSurface_OGL(this);
-
+
     if (renderBuffer_ && !graphics->IsDeviceLost())
         glDeleteRenderbuffersEXT(1, &renderBuffer_);
 
-    renderBuffer_ = 0;
-}
-
+    renderBuffer_ = 0;
+}
+
 void RenderSurface::Release_OGL()
-{
-    Graphics* graphics = parentTexture_->GetGraphics();
-    if (!graphics)
-        return;
-
-    if (!graphics->IsDeviceLost())
-    {
-        for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i)
-        {
-            if (graphics->GetRenderTarget(i) == this)
-                graphics->ResetRenderTarget(i);
-        }
-
-        if (graphics->GetDepthStencil() == this)
-            graphics->ResetDepthStencil();
-
-        // Clean up also from non-active FBOs
+{
+    Graphics* graphics = parentTexture_->GetGraphics();
+    if (!graphics)
+        return;
+
+    if (!graphics->IsDeviceLost())
+    {
+        for (unsigned i = 0; i < MAX_RENDERTARGETS; ++i)
+        {
+            if (graphics->GetRenderTarget(i) == this)
+                graphics->ResetRenderTarget(i);
+        }
+
+        if (graphics->GetDepthStencil() == this)
+            graphics->ResetDepthStencil();
+
+        // Clean up also from non-active FBOs
         graphics->CleanupRenderSurface_OGL(this);
-
-        if (renderBuffer_)
-            glDeleteRenderbuffersEXT(1, &renderBuffer_);
-    }
-
-    renderBuffer_ = 0;
-}
-
-}
+
+        if (renderBuffer_)
+            glDeleteRenderbuffersEXT(1, &renderBuffer_);
+    }
+
+    renderBuffer_ = 0;
+}
+
+}

+ 368 - 368
Source/Urho3D/GraphicsAPI/OpenGL/OGLShaderProgram.cpp

@@ -1,401 +1,401 @@
 // Copyright (c) 2008-2022 the Urho3D project
 // License: MIT
-
-#include "../../Precompiled.h"
-
-#include "../../Graphics/Graphics.h"
+
+#include "../../Precompiled.h"
+
+#include "../../Graphics/Graphics.h"
 #include "../../GraphicsAPI/ConstantBuffer.h"
 #include "../../GraphicsAPI/GraphicsImpl.h"
 #include "../../GraphicsAPI/ShaderVariation.h"
-#include "../../IO/Log.h"
+#include "../../IO/Log.h"
 #include "OGLShaderProgram.h"
-
-#include "../../DebugNew.h"
-
-namespace Urho3D
-{
-
-static const char* shaderParameterGroups[] = {
-    "frame",
-    "camera",
-    "zone",
-    "light",
-    "material",
-    "object",
-    "custom"
-};
-
-static unsigned NumberPostfix(const String& str)
-{
-    for (unsigned i = 0; i < str.Length(); ++i)
-    {
-        if (IsDigit(str[i]))
+
+#include "../../DebugNew.h"
+
+namespace Urho3D
+{
+
+static const char* shaderParameterGroups[] = {
+    "frame",
+    "camera",
+    "zone",
+    "light",
+    "material",
+    "object",
+    "custom"
+};
+
+static unsigned NumberPostfix(const String& str)
+{
+    for (unsigned i = 0; i < str.Length(); ++i)
+    {
+        if (IsDigit(str[i]))
             return ToU32(str.CString() + i);
-    }
-
-    return M_MAX_UNSIGNED;
-}
-
+    }
+
+    return M_MAX_UNSIGNED;
+}
+
 i32 ShaderProgram_OGL::globalFrameNumber = 0;
 const void* ShaderProgram_OGL::globalParameterSources[MAX_SHADER_PARAMETER_GROUPS];
-
+
 ShaderProgram_OGL::ShaderProgram_OGL(Graphics* graphics, ShaderVariation* vertexShader, ShaderVariation* pixelShader) :
-    GPUObject(graphics),
-    vertexShader_(vertexShader),
-    pixelShader_(pixelShader)
-{
-    for (auto& parameterSource : parameterSources_)
-        parameterSource = (const void*)M_MAX_UNSIGNED;
-}
-
+    GPUObject(graphics),
+    vertexShader_(vertexShader),
+    pixelShader_(pixelShader)
+{
+    for (auto& parameterSource : parameterSources_)
+        parameterSource = (const void*)M_MAX_UNSIGNED;
+}
+
 ShaderProgram_OGL::~ShaderProgram_OGL()
-{
-    Release();
-}
-
+{
+    Release();
+}
+
 void ShaderProgram_OGL::OnDeviceLost()
-{
+{
     if (object_.name_ && !graphics_->IsDeviceLost())
         glDeleteProgram(object_.name_);
 
-    GPUObject::OnDeviceLost();
-
+    GPUObject::OnDeviceLost();
+
     if (graphics_ && graphics_->GetShaderProgram_OGL() == this)
-        graphics_->SetShaders(nullptr, nullptr);
-
-    linkerOutput_.Clear();
-}
-
+        graphics_->SetShaders(nullptr, nullptr);
+
+    linkerOutput_.Clear();
+}
+
 void ShaderProgram_OGL::Release()
-{
-    if (object_.name_)
-    {
-        if (!graphics_)
-            return;
-
-        if (!graphics_->IsDeviceLost())
-        {
+{
+    if (object_.name_)
+    {
+        if (!graphics_)
+            return;
+
+        if (!graphics_->IsDeviceLost())
+        {
             if (graphics_->GetShaderProgram_OGL() == this)
-                graphics_->SetShaders(nullptr, nullptr);
-
-            glDeleteProgram(object_.name_);
-        }
-
-        object_.name_ = 0;
-        linkerOutput_.Clear();
-        shaderParameters_.Clear();
-        vertexAttributes_.Clear();
-        usedVertexAttributes_ = 0;
-
-        for (bool& useTextureUnit : useTextureUnits_)
-            useTextureUnit = false;
-        for (unsigned i = 0; i < MAX_SHADER_PARAMETER_GROUPS; ++i)
-            constantBuffers_[i].Reset();
-    }
-}
-
+                graphics_->SetShaders(nullptr, nullptr);
+
+            glDeleteProgram(object_.name_);
+        }
+
+        object_.name_ = 0;
+        linkerOutput_.Clear();
+        shaderParameters_.Clear();
+        vertexAttributes_.Clear();
+        usedVertexAttributes_ = 0;
+
+        for (bool& useTextureUnit : useTextureUnits_)
+            useTextureUnit = false;
+        for (unsigned i = 0; i < MAX_SHADER_PARAMETER_GROUPS; ++i)
+            constantBuffers_[i].Reset();
+    }
+}
+
 bool ShaderProgram_OGL::Link()
-{
-    Release();
-
-    if (!vertexShader_ || !pixelShader_ || !vertexShader_->GetGPUObjectName() || !pixelShader_->GetGPUObjectName())
-        return false;
-
-    object_.name_ = glCreateProgram();
-    if (!object_.name_)
-    {
-        linkerOutput_ = "Could not create shader program";
-        return false;
-    }
-
-    glAttachShader(object_.name_, vertexShader_->GetGPUObjectName());
-    glAttachShader(object_.name_, pixelShader_->GetGPUObjectName());
-    glLinkProgram(object_.name_);
-
-    int linked, length;
-    glGetProgramiv(object_.name_, GL_LINK_STATUS, &linked);
-    if (!linked)
-    {
-        glGetProgramiv(object_.name_, GL_INFO_LOG_LENGTH, &length);
-        linkerOutput_.Resize((unsigned)length);
-        int outLength;
-        glGetProgramInfoLog(object_.name_, length, &outLength, &linkerOutput_[0]);
-        glDeleteProgram(object_.name_);
-        object_.name_ = 0;
-    }
-    else
-        linkerOutput_.Clear();
-
-    if (!object_.name_)
-        return false;
-
-    const int MAX_NAME_LENGTH = 256;
-    char nameBuffer[MAX_NAME_LENGTH];
-    int attributeCount, uniformCount, elementCount, nameLength;
-    GLenum type;
-
-    glUseProgram(object_.name_);
-
-    // Check for vertex attributes
-    glGetProgramiv(object_.name_, GL_ACTIVE_ATTRIBUTES, &attributeCount);
-    for (int i = 0; i < attributeCount; ++i)
-    {
-        glGetActiveAttrib(object_.name_, i, (GLsizei)MAX_NAME_LENGTH, &nameLength, &elementCount, &type, nameBuffer);
-
-        String name = String(nameBuffer, nameLength);
-        VertexElementSemantic semantic = MAX_VERTEX_ELEMENT_SEMANTICS;
+{
+    Release();
+
+    if (!vertexShader_ || !pixelShader_ || !vertexShader_->GetGPUObjectName() || !pixelShader_->GetGPUObjectName())
+        return false;
+
+    object_.name_ = glCreateProgram();
+    if (!object_.name_)
+    {
+        linkerOutput_ = "Could not create shader program";
+        return false;
+    }
+
+    glAttachShader(object_.name_, vertexShader_->GetGPUObjectName());
+    glAttachShader(object_.name_, pixelShader_->GetGPUObjectName());
+    glLinkProgram(object_.name_);
+
+    int linked, length;
+    glGetProgramiv(object_.name_, GL_LINK_STATUS, &linked);
+    if (!linked)
+    {
+        glGetProgramiv(object_.name_, GL_INFO_LOG_LENGTH, &length);
+        linkerOutput_.Resize((unsigned)length);
+        int outLength;
+        glGetProgramInfoLog(object_.name_, length, &outLength, &linkerOutput_[0]);
+        glDeleteProgram(object_.name_);
+        object_.name_ = 0;
+    }
+    else
+        linkerOutput_.Clear();
+
+    if (!object_.name_)
+        return false;
+
+    const int MAX_NAME_LENGTH = 256;
+    char nameBuffer[MAX_NAME_LENGTH];
+    int attributeCount, uniformCount, elementCount, nameLength;
+    GLenum type;
+
+    glUseProgram(object_.name_);
+
+    // Check for vertex attributes
+    glGetProgramiv(object_.name_, GL_ACTIVE_ATTRIBUTES, &attributeCount);
+    for (int i = 0; i < attributeCount; ++i)
+    {
+        glGetActiveAttrib(object_.name_, i, (GLsizei)MAX_NAME_LENGTH, &nameLength, &elementCount, &type, nameBuffer);
+
+        String name = String(nameBuffer, nameLength);
+        VertexElementSemantic semantic = MAX_VERTEX_ELEMENT_SEMANTICS;
         i8 semanticIndex = 0;
-
-        // Go in reverse order so that "binormal" is detected before "normal"
+
+        // Go in reverse order so that "binormal" is detected before "normal"
         for (i32 j = MAX_VERTEX_ELEMENT_SEMANTICS - 1; j >= 0; --j)
-        {
+        {
             if (name.Contains(ShaderVariation::elementSemanticNames_OGL[j], false))
-            {
-                semantic = (VertexElementSemantic)j;
-                unsigned index = NumberPostfix(name);
-                if (index != M_MAX_UNSIGNED)
+            {
+                semantic = (VertexElementSemantic)j;
+                unsigned index = NumberPostfix(name);
+                if (index != M_MAX_UNSIGNED)
                     semanticIndex = (i8)index;
-                break;
-            }
-        }
-
-        if (semantic == MAX_VERTEX_ELEMENT_SEMANTICS)
-        {
-            URHO3D_LOGWARNING("Found vertex attribute " + name + " with no known semantic in shader program " +
-                vertexShader_->GetFullName() + " " + pixelShader_->GetFullName());
-            continue;
-        }
-
-        int location = glGetAttribLocation(object_.name_, name.CString());
+                break;
+            }
+        }
+
+        if (semantic == MAX_VERTEX_ELEMENT_SEMANTICS)
+        {
+            URHO3D_LOGWARNING("Found vertex attribute " + name + " with no known semantic in shader program " +
+                vertexShader_->GetFullName() + " " + pixelShader_->GetFullName());
+            continue;
+        }
+
+        int location = glGetAttribLocation(object_.name_, name.CString());
         vertexAttributes_[{(i8)semantic, semanticIndex}] = location;
-        usedVertexAttributes_ |= (1u << location);
-    }
-
-    // Check for constant buffers
-#ifndef GL_ES_VERSION_2_0
-    HashMap<unsigned, unsigned> blockToBinding;
-
-    if (Graphics::GetGL3Support())
-    {
-        int numUniformBlocks = 0;
-
-        glGetProgramiv(object_.name_, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);
-        for (int i = 0; i < numUniformBlocks; ++i)
-        {
-            glGetActiveUniformBlockName(object_.name_, (GLuint)i, MAX_NAME_LENGTH, &nameLength, nameBuffer);
-
-            String name(nameBuffer, (unsigned)nameLength);
-
-            unsigned blockIndex = glGetUniformBlockIndex(object_.name_, name.CString());
-            unsigned group = M_MAX_UNSIGNED;
-
-            // Try to recognize the use of the buffer from its name
-            for (unsigned j = 0; j < MAX_SHADER_PARAMETER_GROUPS; ++j)
-            {
-                if (name.Contains(shaderParameterGroups[j], false))
-                {
-                    group = j;
-                    break;
-                }
-            }
-
-            // If name is not recognized, search for a digit in the name and use that as the group index
-            if (group == M_MAX_UNSIGNED)
-                group = NumberPostfix(name);
-
-            if (group >= MAX_SHADER_PARAMETER_GROUPS)
-            {
-                URHO3D_LOGWARNING("Skipping unrecognized uniform block " + name + " in shader program " + vertexShader_->GetFullName() +
-                           " " + pixelShader_->GetFullName());
-                continue;
-            }
-
-            // Find total constant buffer data size
-            int dataSize;
-            glGetActiveUniformBlockiv(object_.name_, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &dataSize);
-            if (!dataSize)
-                continue;
-
-            unsigned bindingIndex = group;
-            // Vertex shader constant buffer bindings occupy slots starting from zero to maximum supported, pixel shader bindings
-            // from that point onward
-            ShaderType shaderType = VS;
-            if (name.Contains("PS", false))
-            {
-                bindingIndex += MAX_SHADER_PARAMETER_GROUPS;
-                shaderType = PS;
-            }
-
-            glUniformBlockBinding(object_.name_, blockIndex, bindingIndex);
-            blockToBinding[blockIndex] = bindingIndex;
-
-            constantBuffers_[bindingIndex] = graphics_->GetOrCreateConstantBuffer(shaderType, bindingIndex, (unsigned)dataSize);
-        }
-    }
-#endif
-
-    // Check for shader parameters and texture units
-    glGetProgramiv(object_.name_, GL_ACTIVE_UNIFORMS, &uniformCount);
-    for (int i = 0; i < uniformCount; ++i)
-    {
-        glGetActiveUniform(object_.name_, (GLuint)i, MAX_NAME_LENGTH, nullptr, &elementCount, &type, nameBuffer);
-        int location = glGetUniformLocation(object_.name_, nameBuffer);
-
-        // Check for array index included in the name and strip it
-        String name(nameBuffer);
+        usedVertexAttributes_ |= (1u << location);
+    }
+
+    // Check for constant buffers
+#ifndef URHO3D_GLES2
+    HashMap<unsigned, unsigned> blockToBinding;
+
+    if (Graphics::GetGL3Support())
+    {
+        int numUniformBlocks = 0;
+
+        glGetProgramiv(object_.name_, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);
+        for (int i = 0; i < numUniformBlocks; ++i)
+        {
+            glGetActiveUniformBlockName(object_.name_, (GLuint)i, MAX_NAME_LENGTH, &nameLength, nameBuffer);
+
+            String name(nameBuffer, (unsigned)nameLength);
+
+            unsigned blockIndex = glGetUniformBlockIndex(object_.name_, name.CString());
+            unsigned group = M_MAX_UNSIGNED;
+
+            // Try to recognize the use of the buffer from its name
+            for (unsigned j = 0; j < MAX_SHADER_PARAMETER_GROUPS; ++j)
+            {
+                if (name.Contains(shaderParameterGroups[j], false))
+                {
+                    group = j;
+                    break;
+                }
+            }
+
+            // If name is not recognized, search for a digit in the name and use that as the group index
+            if (group == M_MAX_UNSIGNED)
+                group = NumberPostfix(name);
+
+            if (group >= MAX_SHADER_PARAMETER_GROUPS)
+            {
+                URHO3D_LOGWARNING("Skipping unrecognized uniform block " + name + " in shader program " + vertexShader_->GetFullName() +
+                           " " + pixelShader_->GetFullName());
+                continue;
+            }
+
+            // Find total constant buffer data size
+            int dataSize;
+            glGetActiveUniformBlockiv(object_.name_, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &dataSize);
+            if (!dataSize)
+                continue;
+
+            unsigned bindingIndex = group;
+            // Vertex shader constant buffer bindings occupy slots starting from zero to maximum supported, pixel shader bindings
+            // from that point onward
+            ShaderType shaderType = VS;
+            if (name.Contains("PS", false))
+            {
+                bindingIndex += MAX_SHADER_PARAMETER_GROUPS;
+                shaderType = PS;
+            }
+
+            glUniformBlockBinding(object_.name_, blockIndex, bindingIndex);
+            blockToBinding[blockIndex] = bindingIndex;
+
+            constantBuffers_[bindingIndex] = graphics_->GetOrCreateConstantBuffer(shaderType, bindingIndex, (unsigned)dataSize);
+        }
+    }
+#endif
+
+    // Check for shader parameters and texture units
+    glGetProgramiv(object_.name_, GL_ACTIVE_UNIFORMS, &uniformCount);
+    for (int i = 0; i < uniformCount; ++i)
+    {
+        glGetActiveUniform(object_.name_, (GLuint)i, MAX_NAME_LENGTH, nullptr, &elementCount, &type, nameBuffer);
+        int location = glGetUniformLocation(object_.name_, nameBuffer);
+
+        // Check for array index included in the name and strip it
+        String name(nameBuffer);
         i32 index = name.Find('[');
-        if (index != String::NPOS)
-        {
-            // If not the first index, skip
-            if (name.Find("[0]", index) == String::NPOS)
-                continue;
-
-            name = name.Substring(0, index);
-        }
-
-        if (name[0] == 'c')
-        {
-            // Store constant uniform
-            String paramName = name.Substring(1);
-            ShaderParameter parameter{paramName, type, location};
-            bool store = location >= 0;
-
-#ifndef GL_ES_VERSION_2_0
-            // If running OpenGL 3, the uniform may be inside a constant buffer
-            if (parameter.location_ < 0 && Graphics::GetGL3Support())
-            {
-                int blockIndex, blockOffset;
-                glGetActiveUniformsiv(object_.name_, 1, (const GLuint*)&i, GL_UNIFORM_BLOCK_INDEX, &blockIndex);
-                glGetActiveUniformsiv(object_.name_, 1, (const GLuint*)&i, GL_UNIFORM_OFFSET, &blockOffset);
-                if (blockIndex >= 0)
-                {
-                    parameter.offset_ = blockOffset;
-                    parameter.bufferPtr_ = constantBuffers_[blockToBinding[blockIndex]];
-                    store = true;
-                }
-            }
-#endif
-
-            if (store)
-                shaderParameters_[StringHash(paramName)] = parameter;
-        }
-        else if (location >= 0 && name[0] == 's')
-        {
-            // Set the samplers here so that they do not have to be set later
-            unsigned unit = graphics_->GetTextureUnit(name.Substring(1));
-            if (unit >= MAX_TEXTURE_UNITS)
-                unit = NumberPostfix(name);
-
-            if (unit < MAX_TEXTURE_UNITS)
-            {
-                useTextureUnits_[unit] = true;
-                glUniform1iv(location, 1, reinterpret_cast<int*>(&unit));
-            }
-        }
-    }
-
-    // Rehash the parameter & vertex attributes maps to ensure minimal load factor
-    vertexAttributes_.Rehash(NextPowerOfTwo(vertexAttributes_.Size()));
-    shaderParameters_.Rehash(NextPowerOfTwo(shaderParameters_.Size()));
-
-    return true;
-}
-
+        if (index != String::NPOS)
+        {
+            // If not the first index, skip
+            if (name.Find("[0]", index) == String::NPOS)
+                continue;
+
+            name = name.Substring(0, index);
+        }
+
+        if (name[0] == 'c')
+        {
+            // Store constant uniform
+            String paramName = name.Substring(1);
+            ShaderParameter parameter{paramName, type, location};
+            bool store = location >= 0;
+
+#ifndef URHO3D_GLES2
+            // If running OpenGL 3, the uniform may be inside a constant buffer
+            if (parameter.location_ < 0 && Graphics::GetGL3Support())
+            {
+                int blockIndex, blockOffset;
+                glGetActiveUniformsiv(object_.name_, 1, (const GLuint*)&i, GL_UNIFORM_BLOCK_INDEX, &blockIndex);
+                glGetActiveUniformsiv(object_.name_, 1, (const GLuint*)&i, GL_UNIFORM_OFFSET, &blockOffset);
+                if (blockIndex >= 0)
+                {
+                    parameter.offset_ = blockOffset;
+                    parameter.bufferPtr_ = constantBuffers_[blockToBinding[blockIndex]];
+                    store = true;
+                }
+            }
+#endif
+
+            if (store)
+                shaderParameters_[StringHash(paramName)] = parameter;
+        }
+        else if (location >= 0 && name[0] == 's')
+        {
+            // Set the samplers here so that they do not have to be set later
+            unsigned unit = graphics_->GetTextureUnit(name.Substring(1));
+            if (unit >= MAX_TEXTURE_UNITS)
+                unit = NumberPostfix(name);
+
+            if (unit < MAX_TEXTURE_UNITS)
+            {
+                useTextureUnits_[unit] = true;
+                glUniform1iv(location, 1, reinterpret_cast<int*>(&unit));
+            }
+        }
+    }
+
+    // Rehash the parameter & vertex attributes maps to ensure minimal load factor
+    vertexAttributes_.Rehash(NextPowerOfTwo(vertexAttributes_.Size()));
+    shaderParameters_.Rehash(NextPowerOfTwo(shaderParameters_.Size()));
+
+    return true;
+}
+
 ShaderVariation* ShaderProgram_OGL::GetVertexShader() const
-{
-    return vertexShader_;
-}
-
+{
+    return vertexShader_;
+}
+
 ShaderVariation* ShaderProgram_OGL::GetPixelShader() const
-{
-    return pixelShader_;
-}
-
+{
+    return pixelShader_;
+}
+
 bool ShaderProgram_OGL::HasParameter(StringHash param) const
-{
-    return shaderParameters_.Find(param) != shaderParameters_.End();
-}
-
+{
+    return shaderParameters_.Find(param) != shaderParameters_.End();
+}
+
 const ShaderParameter* ShaderProgram_OGL::GetParameter(StringHash param) const
-{
-    HashMap<StringHash, ShaderParameter>::ConstIterator i = shaderParameters_.Find(param);
-    if (i != shaderParameters_.End())
-        return &i->second_;
-    else
-        return nullptr;
-}
-
+{
+    HashMap<StringHash, ShaderParameter>::ConstIterator i = shaderParameters_.Find(param);
+    if (i != shaderParameters_.End())
+        return &i->second_;
+    else
+        return nullptr;
+}
+
 bool ShaderProgram_OGL::NeedParameterUpdate(ShaderParameterGroup group, const void* source)
-{
-    // If global framenumber has changed, invalidate all per-program parameter sources now
-    if (globalFrameNumber != frameNumber_)
-    {
-        for (auto& parameterSource : parameterSources_)
-            parameterSource = (const void*)M_MAX_UNSIGNED;
-        frameNumber_ = globalFrameNumber;
-    }
-
-    // The shader program may use a mixture of constant buffers and individual uniforms even in the same group
-#ifndef GL_ES_VERSION_2_0
-    bool useBuffer = constantBuffers_[group].Get() || constantBuffers_[group + MAX_SHADER_PARAMETER_GROUPS].Get();
-    bool useIndividual = !constantBuffers_[group].Get() || !constantBuffers_[group + MAX_SHADER_PARAMETER_GROUPS].Get();
-    bool needUpdate = false;
-
-    if (useBuffer && globalParameterSources[group] != source)
-    {
-        globalParameterSources[group] = source;
-        needUpdate = true;
-    }
-
-    if (useIndividual && parameterSources_[group] != source)
-    {
-        parameterSources_[group] = source;
-        needUpdate = true;
-    }
-
-    return needUpdate;
-#else
-    if (parameterSources_[group] != source)
-    {
-        parameterSources_[group] = source;
-        return true;
-    }
-    else
-        return false;
-#endif
-}
-
+{
+    // If global framenumber has changed, invalidate all per-program parameter sources now
+    if (globalFrameNumber != frameNumber_)
+    {
+        for (auto& parameterSource : parameterSources_)
+            parameterSource = (const void*)M_MAX_UNSIGNED;
+        frameNumber_ = globalFrameNumber;
+    }
+
+    // The shader program may use a mixture of constant buffers and individual uniforms even in the same group
+#ifndef URHO3D_GLES2
+    bool useBuffer = constantBuffers_[group].Get() || constantBuffers_[group + MAX_SHADER_PARAMETER_GROUPS].Get();
+    bool useIndividual = !constantBuffers_[group].Get() || !constantBuffers_[group + MAX_SHADER_PARAMETER_GROUPS].Get();
+    bool needUpdate = false;
+
+    if (useBuffer && globalParameterSources[group] != source)
+    {
+        globalParameterSources[group] = source;
+        needUpdate = true;
+    }
+
+    if (useIndividual && parameterSources_[group] != source)
+    {
+        parameterSources_[group] = source;
+        needUpdate = true;
+    }
+
+    return needUpdate;
+#else
+    if (parameterSources_[group] != source)
+    {
+        parameterSources_[group] = source;
+        return true;
+    }
+    else
+        return false;
+#endif
+}
+
 void ShaderProgram_OGL::ClearParameterSource(ShaderParameterGroup group)
-{
-    // The shader program may use a mixture of constant buffers and individual uniforms even in the same group
-#ifndef GL_ES_VERSION_2_0
-    bool useBuffer = constantBuffers_[group].Get() || constantBuffers_[group + MAX_SHADER_PARAMETER_GROUPS].Get();
-    bool useIndividual = !constantBuffers_[group].Get() || !constantBuffers_[group + MAX_SHADER_PARAMETER_GROUPS].Get();
-
-    if (useBuffer)
-        globalParameterSources[group] = (const void*)M_MAX_UNSIGNED;
-    if (useIndividual)
-        parameterSources_[group] = (const void*)M_MAX_UNSIGNED;
-#else
-    parameterSources_[group] = (const void*)M_MAX_UNSIGNED;
-#endif
-}
-
+{
+    // The shader program may use a mixture of constant buffers and individual uniforms even in the same group
+#ifndef URHO3D_GLES2
+    bool useBuffer = constantBuffers_[group].Get() || constantBuffers_[group + MAX_SHADER_PARAMETER_GROUPS].Get();
+    bool useIndividual = !constantBuffers_[group].Get() || !constantBuffers_[group + MAX_SHADER_PARAMETER_GROUPS].Get();
+
+    if (useBuffer)
+        globalParameterSources[group] = (const void*)M_MAX_UNSIGNED;
+    if (useIndividual)
+        parameterSources_[group] = (const void*)M_MAX_UNSIGNED;
+#else
+    parameterSources_[group] = (const void*)M_MAX_UNSIGNED;
+#endif
+}
+
 void ShaderProgram_OGL::ClearParameterSources()
-{
-    ++globalFrameNumber;
-    if (!globalFrameNumber)
-        ++globalFrameNumber;
-
-#ifndef GL_ES_VERSION_2_0
-    for (auto& globalParameterSource : globalParameterSources)
-        globalParameterSource = (const void*)M_MAX_UNSIGNED;
-#endif
-}
-
+{
+    ++globalFrameNumber;
+    if (!globalFrameNumber)
+        ++globalFrameNumber;
+
+#ifndef URHO3D_GLES2
+    for (auto& globalParameterSource : globalParameterSources)
+        globalParameterSource = (const void*)M_MAX_UNSIGNED;
+#endif
+}
+
 void ShaderProgram_OGL::ClearGlobalParameterSource(ShaderParameterGroup group)
-{
-    globalParameterSources[group] = (const void*)M_MAX_UNSIGNED;
-}
-
-}
+{
+    globalParameterSources[group] = (const void*)M_MAX_UNSIGNED;
+}
+
+}

+ 11 - 0
Source/Urho3D/GraphicsAPI/OpenGL/OGLShaderVariation.cpp

@@ -110,7 +110,18 @@ bool ShaderVariation::Create_OGL()
     }
     // Force GLSL version 150 if no version define and GL3 is being used
     if (!verEnd && Graphics::GetGL3Support())
+    {
+#if defined(MOBILE_GRAPHICS) || URHO3D_GLES3
+        shaderCode += "#version 300 es\n";
+#else
         shaderCode += "#version 150\n";
+#endif
+    }
+#if defined(DESKTOP_GRAPHICS)
+    shaderCode += "#define DESKTOP_GRAPHICS\n";
+#elif defined(MOBILE_GRAPHICS)
+    shaderCode += "#define MOBILE_GRAPHICS\n";
+#endif
 
     // Distinguish between VS and PS compile in case the shader code wants to include/omit different things
     shaderCode += type_ == VS ? "#define COMPILEVS\n" : "#define COMPILEPS\n";

+ 388 - 330
Source/Urho3D/GraphicsAPI/OpenGL/OGLTexture.cpp

@@ -1,333 +1,391 @@
-// Copyright (c) 2008-2022 the Urho3D project
-// License: MIT
-
-#include "../../Precompiled.h"
-
-#include "../../Graphics/Graphics.h"
-#include "../../Graphics/Material.h"
-#include "../../GraphicsAPI/GraphicsImpl.h"
-#include "../../GraphicsAPI/RenderSurface.h"
-#include "../../IO/Log.h"
-#include "../../Resource/ResourceCache.h"
-#include "../../Resource/XMLFile.h"
-
-#include "../../DebugNew.h"
-
-namespace Urho3D
-{
-
-static GLenum glWrapModes[] =
-{
-    GL_REPEAT,
-    GL_MIRRORED_REPEAT,
-    GL_CLAMP_TO_EDGE,
-#ifndef GL_ES_VERSION_2_0
-    GL_CLAMP
-#else
-    GL_CLAMP_TO_EDGE
-#endif
-};
-
-#ifndef GL_ES_VERSION_2_0
-static GLenum gl3WrapModes[] =
-{
-    GL_REPEAT,
-    GL_MIRRORED_REPEAT,
-    GL_CLAMP_TO_EDGE,
-    GL_CLAMP_TO_BORDER
-};
-#endif
-
-static GLenum GetWrapMode(TextureAddressMode mode)
-{
-#ifndef GL_ES_VERSION_2_0
-    return Graphics::GetGL3Support() ? gl3WrapModes[mode] : glWrapModes[mode];
-#else
-    return glWrapModes[mode];
-#endif
-}
-
-void Texture::SetSRGB_OGL(bool enable)
-{
-    if (graphics_)
-        enable &= graphics_->GetSRGBSupport();
-
-    if (enable != sRGB_)
-    {
-        sRGB_ = enable;
-        // If texture had already been created, must recreate it to set the sRGB texture format
-        if (object_.name_)
-            Create();
-
-        // If texture in use in the framebuffer, mark it dirty
-        if (graphics_ && graphics_->GetRenderTarget(0) && graphics_->GetRenderTarget(0)->GetParentTexture() == this)
-            graphics_->MarkFBODirty_OGL();
-    }
-}
-
-void Texture::UpdateParameters_OGL()
-{
-    if (!object_.name_ || !graphics_)
-        return;
-
-    // If texture is multisampled, do not attempt to set parameters as it's illegal, just return
-#ifndef GL_ES_VERSION_2_0
-    if (target_ == GL_TEXTURE_2D_MULTISAMPLE)
-    {
-        parametersDirty_ = false;
-        return;
-    }
-#endif
-
-    // Wrapping
-    glTexParameteri(target_, GL_TEXTURE_WRAP_S, GetWrapMode(addressModes_[COORD_U]));
-    glTexParameteri(target_, GL_TEXTURE_WRAP_T, GetWrapMode(addressModes_[COORD_V]));
-#ifndef GL_ES_VERSION_2_0
-    glTexParameteri(target_, GL_TEXTURE_WRAP_R, GetWrapMode(addressModes_[COORD_W]));
-#endif
-
-    TextureFilterMode filterMode = filterMode_;
-    if (filterMode == FILTER_DEFAULT)
-        filterMode = graphics_->GetDefaultTextureFilterMode();
-
-    // Filtering
-    switch (filterMode)
-    {
-    case FILTER_NEAREST:
-        if (levels_ < 2)
-            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-        else
-            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
-        glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-        break;
-
-    case FILTER_BILINEAR:
-        if (levels_ < 2)
-            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-        else
-            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
-        glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-        break;
-
-    case FILTER_ANISOTROPIC:
-    case FILTER_TRILINEAR:
-        if (levels_ < 2)
-            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-        else
-            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
-        glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-        break;
-
-    case FILTER_NEAREST_ANISOTROPIC:
-        if (levels_ < 2)
-            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-        else
-            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
-        glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-        break;
-
-    default:
-        break;
-    }
-
-#ifndef GL_ES_VERSION_2_0
-    // Anisotropy
-    if (graphics_->GetAnisotropySupport())
-    {
-        unsigned maxAnisotropy = anisotropy_ ? anisotropy_ : graphics_->GetDefaultTextureAnisotropy();
-        glTexParameterf(target_, GL_TEXTURE_MAX_ANISOTROPY_EXT,
-            (filterMode == FILTER_ANISOTROPIC || filterMode == FILTER_NEAREST_ANISOTROPIC) ? (float)maxAnisotropy : 1.0f);
-    }
-
-    // Shadow compare
-    if (shadowCompare_)
-    {
-        glTexParameteri(target_, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
-        glTexParameteri(target_, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
-    }
-    else
-        glTexParameteri(target_, GL_TEXTURE_COMPARE_MODE, GL_NONE);
-
-    glTexParameterfv(target_, GL_TEXTURE_BORDER_COLOR, borderColor_.Data());
-#endif
-
-    parametersDirty_ = false;
-}
-
-bool Texture::GetParametersDirty_OGL() const
-{
-    return parametersDirty_;
-}
-
-bool Texture::IsCompressed_OGL() const
-{
-    return format_ == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || format_ == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
-           format_ == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT || format_ == GL_ETC1_RGB8_OES ||
-           format_ == GL_ETC2_RGB8_OES || format_ == GL_ETC2_RGBA8_OES ||
-           format_ == COMPRESSED_RGB_PVRTC_4BPPV1_IMG || format_ == COMPRESSED_RGBA_PVRTC_4BPPV1_IMG ||
-           format_ == COMPRESSED_RGB_PVRTC_2BPPV1_IMG || format_ == COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
-}
-
-unsigned Texture::GetRowDataSize_OGL(int width) const
-{
-    switch (format_)
-    {
-    case GL_ALPHA:
-    case GL_LUMINANCE:
-        return (unsigned)width;
-
-    case GL_LUMINANCE_ALPHA:
-        return (unsigned)(width * 2);
-
-    case GL_RGB:
-        return (unsigned)(width * 3);
-
-    case GL_RGBA:
-#ifndef GL_ES_VERSION_2_0
-    case GL_DEPTH24_STENCIL8_EXT:
-    case GL_RG16:
-    case GL_RG16F:
-    case GL_R32F:
+// Copyright (c) 2008-2022 the Urho3D project
+// License: MIT
+
+#include "../../Precompiled.h"
+
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/Material.h"
+#include "../../GraphicsAPI/GraphicsImpl.h"
+#include "../../GraphicsAPI/RenderSurface.h"
+#include "../../IO/Log.h"
+#include "../../Resource/ResourceCache.h"
+#include "../../Resource/XMLFile.h"
+
+#include "../../DebugNew.h"
+
+#if URHO3D_GLES3
+#define GL_COMPARE_R_TO_TEXTURE GL_COMPARE_REF_TO_TEXTURE
+#endif
+
+namespace Urho3D
+{
+
+static GLenum glWrapModes[] =
+{
+    GL_REPEAT,
+    GL_MIRRORED_REPEAT,
+    GL_CLAMP_TO_EDGE,
+#ifndef GL_ES_VERSION_2_0
+    GL_CLAMP
+#else
+    GL_CLAMP_TO_EDGE
+#endif
+};
+
+#ifndef GL_ES_VERSION_2_0
+static GLenum gl3WrapModes[] =
+{
+    GL_REPEAT,
+    GL_MIRRORED_REPEAT,
+    GL_CLAMP_TO_EDGE,
+    GL_CLAMP_TO_BORDER
+};
+#endif
+
+static GLenum GetWrapMode(TextureAddressMode mode)
+{
+#ifndef GL_ES_VERSION_2_0
+    return Graphics::GetGL3Support() ? gl3WrapModes[mode] : glWrapModes[mode];
+#else
+    return glWrapModes[mode];
+#endif
+}
+
+void Texture::SetSRGB_OGL(bool enable)
+{
+    if (graphics_)
+        enable &= graphics_->GetSRGBSupport();
+
+    if (enable != sRGB_)
+    {
+        sRGB_ = enable;
+        // If texture had already been created, must recreate it to set the sRGB texture format
+        if (object_.name_)
+            Create();
+
+        // If texture in use in the framebuffer, mark it dirty
+        if (graphics_ && graphics_->GetRenderTarget(0) && graphics_->GetRenderTarget(0)->GetParentTexture() == this)
+            graphics_->MarkFBODirty_OGL();
+    }
+}
+
+void Texture::UpdateParameters_OGL()
+{
+    if (!object_.name_ || !graphics_)
+        return;
+
+    // If texture is multisampled, do not attempt to set parameters as it's illegal, just return
+#ifndef GL_ES_VERSION_2_0
+    if (target_ == GL_TEXTURE_2D_MULTISAMPLE)
+    {
+        parametersDirty_ = false;
+        return;
+    }
+#endif
+
+    // Wrapping
+    glTexParameteri(target_, GL_TEXTURE_WRAP_S, GetWrapMode(addressModes_[COORD_U]));
+    glTexParameteri(target_, GL_TEXTURE_WRAP_T, GetWrapMode(addressModes_[COORD_V]));
+#if defined(URHO3D_GLES3) || (defined(DESKTOP_GRAPHICS) && !defined(__EMSCRIPTEN__))
+    glTexParameteri(target_, GL_TEXTURE_WRAP_R, GetWrapMode(addressModes_[COORD_W]));
+#endif
+
+    TextureFilterMode filterMode = filterMode_;
+    if (filterMode == FILTER_DEFAULT)
+        filterMode = graphics_->GetDefaultTextureFilterMode();
+
+    // Filtering
+    switch (filterMode)
+    {
+    case FILTER_NEAREST:
+        if (levels_ < 2)
+            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        else
+            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
+        glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        break;
+
+    case FILTER_BILINEAR:
+        if (levels_ < 2)
+            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        else
+            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+        glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        break;
+
+    case FILTER_ANISOTROPIC:
+    case FILTER_TRILINEAR:
+        if (levels_ < 2)
+            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        else
+            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+        glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+        break;
+
+    case FILTER_NEAREST_ANISOTROPIC:
+        if (levels_ < 2)
+            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        else
+            glTexParameteri(target_, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
+        glTexParameteri(target_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        break;
+
+    default:
+        break;
+    }
+
+#ifndef GL_ES_VERSION_2_0
+    // Anisotropy
+    if (graphics_->GetAnisotropySupport())
+    {
+        unsigned maxAnisotropy = anisotropy_ ? anisotropy_ : graphics_->GetDefaultTextureAnisotropy();
+        glTexParameterf(target_, GL_TEXTURE_MAX_ANISOTROPY_EXT,
+            (filterMode == FILTER_ANISOTROPIC || filterMode == FILTER_NEAREST_ANISOTROPIC) ? (float)maxAnisotropy : 1.0f);
+    }
+    glTexParameterfv(target_, GL_TEXTURE_BORDER_COLOR, borderColor_.Data());
+#endif
+
+#ifndef URHO3D_GLES2
+    // Shadow compare
+    if (shadowCompare_)
+    {
+        glTexParameteri(target_, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
+        glTexParameteri(target_, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
+    }
+    else
+        glTexParameteri(target_, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+#endif
+
+    parametersDirty_ = false;
+}
+
+bool Texture::GetParametersDirty_OGL() const
+{
+    return parametersDirty_;
+}
+
+bool Texture::IsCompressed_OGL() const
+{
+    return format_ == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT || format_ == GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+           format_ == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT || format_ == GL_ETC1_RGB8_OES ||
+           format_ == GL_ETC2_RGB8_OES || format_ == GL_ETC2_RGBA8_OES ||
+           format_ == COMPRESSED_RGB_PVRTC_4BPPV1_IMG || format_ == COMPRESSED_RGBA_PVRTC_4BPPV1_IMG ||
+           format_ == COMPRESSED_RGB_PVRTC_2BPPV1_IMG || format_ == COMPRESSED_RGBA_PVRTC_2BPPV1_IMG ||
+           format_ == GL_ETC2_RGB8_OES || format_ == GL_ETC2_RGBA8_OES;
+}
+
+unsigned Texture::GetRowDataSize_OGL(int width) const
+{
+    switch (format_)
+    {
+    case GL_ALPHA:
+    case GL_LUMINANCE:
+        return (unsigned)width;
+
+    case GL_LUMINANCE_ALPHA:
+        return (unsigned)(width * 2);
+
+    case GL_RGB:
+        return (unsigned)(width * 3);
+
+    case GL_RGBA:
+#ifndef GL_ES_VERSION_2_0
+    case GL_DEPTH24_STENCIL8_EXT:
+    case GL_RG16:
+    case GL_RG16F:
+    case GL_R32F:
+#endif
+#ifdef GL_ES_VERSION_3_0
+    case GL_DEPTH24_STENCIL8:
+#endif
+        return (unsigned)(width * 4);
+
+#ifndef GL_ES_VERSION_2_0
+    case GL_R8:
+        return (unsigned)width;
+
+    case GL_RG8:
+    case GL_R16F:
+        return (unsigned)(width * 2);
+
+    case GL_RGBA16:
+    case GL_RGBA16F_ARB:
+        return (unsigned)(width * 8);
+
+    case GL_RGBA32F_ARB:
+        return (unsigned)(width * 16);
+#endif
+#ifdef GL_ES_VERSION_3_0
+    case GL_R8:
+        return (unsigned) width;
+
+    case GL_RG8:
+    case GL_R16F:
+        return (unsigned) (width * 2);
+    case GL_RGBA16F:
+        return (unsigned) (width * 8);
+    case GL_RGBA32F:
+        return (unsigned) (width * 16);
+#endif
+
+    case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+        return ((unsigned)(width + 3) >> 2u) * 8;
+
+    case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+    case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+        return ((unsigned)(width + 3) >> 2u) * 16;
+
+    case GL_ETC1_RGB8_OES:
+    case GL_ETC2_RGB8_OES:
+        return ((unsigned)(width + 3) >> 2u) * 8;
+
+    case GL_ETC2_RGBA8_OES:
+        return ((unsigned)(width + 3) >> 2u) * 16;
+
+    case COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
+    case COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
+        return ((unsigned)(width + 3) >> 2u) * 8;
+
+    case COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
+    case COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
+        return ((unsigned)(width + 7) >> 3u) * 8;
+
+    default:
+        return 0;
+    }
+}
+
+#ifdef GL_ES_VERSION_3_0
+#define GL_SRGB_EXT GL_SRGB
+#define GL_SRGB_ALPHA_EXT GL_SRGB8_ALPHA8
 #endif
-        return (unsigned)(width * 4);
-
-#ifndef GL_ES_VERSION_2_0
-    case GL_R8:
-        return (unsigned)width;
-
-    case GL_RG8:
-    case GL_R16F:
-        return (unsigned)(width * 2);
-
-    case GL_RGBA16:
-    case GL_RGBA16F_ARB:
-        return (unsigned)(width * 8);
-
-    case GL_RGBA32F_ARB:
-        return (unsigned)(width * 16);
-#endif
-
-    case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
-        return ((unsigned)(width + 3) >> 2u) * 8;
-
-    case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
-    case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
-        return ((unsigned)(width + 3) >> 2u) * 16;
-
-    case GL_ETC1_RGB8_OES:
-    case GL_ETC2_RGB8_OES:
-        return ((unsigned)(width + 3) >> 2u) * 8;
-
-    case GL_ETC2_RGBA8_OES:
-        return ((unsigned)(width + 3) >> 2u) * 16;
-
-    case COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
-    case COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
-        return ((unsigned)(width + 3) >> 2u) * 8;
-
-    case COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
-    case COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
-        return ((unsigned)(width + 7) >> 3u) * 8;
-
-    default:
-        return 0;
-    }
-}
 
 unsigned Texture::GetExternalFormat_OGL(unsigned format)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (format == GL_DEPTH_COMPONENT16 || format == GL_DEPTH_COMPONENT24 || format == GL_DEPTH_COMPONENT32)
-        return GL_DEPTH_COMPONENT;
-    else if (format == GL_DEPTH24_STENCIL8_EXT)
-        return GL_DEPTH_STENCIL_EXT;
-    else if (format == GL_SLUMINANCE_EXT)
-        return GL_LUMINANCE;
-    else if (format == GL_SLUMINANCE_ALPHA_EXT)
-        return GL_LUMINANCE_ALPHA;
-    else if (format == GL_R8 || format == GL_R16F || format == GL_R32F)
-        return GL_RED;
-    else if (format == GL_RG8 || format == GL_RG16 || format == GL_RG16F || format == GL_RG32F)
-        return GL_RG;
-    else if (format == GL_RGBA16 || format == GL_RGBA16F_ARB || format == GL_RGBA32F_ARB || format == GL_SRGB_ALPHA_EXT)
-        return GL_RGBA;
-    else if (format == GL_SRGB_EXT)
-        return GL_RGB;
-    else
-        return format;
-#else
-    return format;
-#endif
-}
-
-unsigned Texture::GetDataType_OGL(unsigned format)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (format == GL_DEPTH24_STENCIL8_EXT)
-        return GL_UNSIGNED_INT_24_8_EXT;
-    else if (format == GL_RG16 || format == GL_RGBA16)
-        return GL_UNSIGNED_SHORT;
-    else if (format == GL_RGBA32F_ARB || format == GL_RG32F || format == GL_R32F)
-        return GL_FLOAT;
-    else if (format == GL_RGBA16F_ARB || format == GL_RG16F || format == GL_R16F)
-        return GL_HALF_FLOAT_ARB;
-    else
-        return GL_UNSIGNED_BYTE;
-#else
-    if (format == GL_DEPTH_COMPONENT || format == GL_DEPTH_COMPONENT24_OES)
-        return GL_UNSIGNED_INT;
-    else if (format == GL_DEPTH_COMPONENT16)
-        return GL_UNSIGNED_SHORT;
-    else
-        return GL_UNSIGNED_BYTE;
-#endif
-}
-
-unsigned Texture::GetSRGBFormat_OGL(unsigned format)
-{
-#ifndef GL_ES_VERSION_2_0
-    if (!graphics_ || !graphics_->GetSRGBSupport())
-        return format;
-
-    switch (format)
-    {
-    case GL_RGB:
-        return GL_SRGB_EXT;
-    case GL_RGBA:
-        return GL_SRGB_ALPHA_EXT;
-    case GL_LUMINANCE:
-        return GL_SLUMINANCE_EXT;
-    case GL_LUMINANCE_ALPHA:
-        return GL_SLUMINANCE_ALPHA_EXT;
-    case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
-        return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
-    case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
-        return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
-    case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
-        return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
-    default:
-        return format;
-    }
-#else
-    return format;
-#endif
-}
-
-void Texture::RegenerateLevels_OGL()
-{
-    if (!object_.name_)
-        return;
-
-#ifndef GL_ES_VERSION_2_0
-    if (Graphics::GetGL3Support())
-        glGenerateMipmap(target_);
-    else
-        glGenerateMipmapEXT(target_);
-#else
-    glGenerateMipmap(target_);
-#endif
-
-    levelsDirty_ = false;
-}
-
-}
+{
+#ifndef GL_ES_VERSION_2_0
+    if (format == GL_DEPTH_COMPONENT16 || format == GL_DEPTH_COMPONENT24 || format == GL_DEPTH_COMPONENT32)
+        return GL_DEPTH_COMPONENT;
+    else if (format == GL_DEPTH24_STENCIL8_EXT)
+        return GL_DEPTH_STENCIL_EXT;
+    else if (format == GL_SLUMINANCE_EXT)
+        return GL_LUMINANCE;
+    else if (format == GL_SLUMINANCE_ALPHA_EXT)
+        return GL_LUMINANCE_ALPHA;
+    else if (format == GL_R8 || format == GL_R16F || format == GL_R32F)
+        return GL_RED;
+    else if (format == GL_RG8 || format == GL_RG16 || format == GL_RG16F || format == GL_RG32F)
+        return GL_RG;
+    else if (format == GL_RGBA16 || format == GL_RGBA16F_ARB || format == GL_RGBA32F_ARB || format == GL_SRGB_ALPHA_EXT)
+        return GL_RGBA;
+    else if (format == GL_SRGB_EXT)
+        return GL_RGB;
+    else
+        return format;
+#else
+#if defined(GL_ES_VERSION_3_0)
+    if (format == GL_DEPTH_COMPONENT16 || format == GL_DEPTH_COMPONENT24 || format == GL_DEPTH_COMPONENT32F)
+        return GL_DEPTH_COMPONENT;
+    else if (format == GL_DEPTH24_STENCIL8)
+        return GL_DEPTH_STENCIL;
+    else if (format == GL_R8 || format == GL_R16F || format == GL_R32F)
+        return GL_RED;
+    else if (format == GL_RG8 || format == GL_RG16F || format == GL_RG32F)
+        return GL_RG;
+    else if (format == GL_RGBA16F || format == GL_RGBA32F || format == GL_SRGB_ALPHA_EXT)
+        return GL_RGBA;
+    else if (format == GL_RG16UI)
+        return GL_RG_INTEGER;
+    else if (format == GL_RGBA16UI)
+        return GL_RGBA_INTEGER;
+#endif
+    return format;
+#endif
+}
+
+unsigned Texture::GetDataType_OGL(unsigned format)
+{
+#ifndef GL_ES_VERSION_2_0
+    if (format == GL_DEPTH24_STENCIL8_EXT)
+        return GL_UNSIGNED_INT_24_8_EXT;
+    else if (format == GL_RG16 || format == GL_RGBA16)
+        return GL_UNSIGNED_SHORT;
+    else if (format == GL_RGBA32F_ARB || format == GL_RG32F || format == GL_R32F)
+        return GL_FLOAT;
+    else if (format == GL_RGBA16F_ARB || format == GL_RG16F || format == GL_R16F)
+        return GL_HALF_FLOAT_ARB;
+    else
+        return GL_UNSIGNED_BYTE;
+#else
+#if defined(GL_ES_VERSION_3_0)
+    if (format == GL_DEPTH_COMPONENT24)
+        return GL_UNSIGNED_INT;
+    else if (format == GL_DEPTH24_STENCIL8)
+        return GL_UNSIGNED_INT_24_8;
+    else if (format == GL_RGBA16UI || format == GL_RG16UI)
+        return GL_UNSIGNED_SHORT;
+    else if (format == GL_RGBA16F || format == GL_RG16F)
+        return GL_HALF_FLOAT;
+    else if (format == GL_DEPTH_COMPONENT32F || format == GL_RGBA32F || format == GL_RG32F || format == GL_R32F)
+        return GL_FLOAT;
+    else if (format == GL_RGBA16F || format == GL_RG16F || format == GL_R16F)
+        return GL_HALF_FLOAT;
+#endif
+    if (format == GL_DEPTH_COMPONENT24_OES)
+        return GL_UNSIGNED_INT;
+    else if (format == GL_DEPTH_COMPONENT || format == GL_DEPTH_COMPONENT16)
+        return GL_UNSIGNED_SHORT;
+    else
+        return GL_UNSIGNED_BYTE;
+#endif
+}
+
+unsigned Texture::GetSRGBFormat_OGL(unsigned format)
+{
+#if !defined(GL_ES_VERSION_2_0) || defined(GL_ES_VERSION_3_0)
+    if (!graphics_ || !graphics_->GetSRGBSupport())
+        return format;
+
+    switch (format)
+    {
+    case GL_RGB:
+        return GL_SRGB_EXT;
+    case GL_RGBA:
+        return GL_SRGB_ALPHA_EXT;
+#ifndef GL_ES_VERSION_3_0
+    case GL_LUMINANCE:
+        return GL_SLUMINANCE_EXT;
+    case GL_LUMINANCE_ALPHA:
+        return GL_SLUMINANCE_ALPHA_EXT;
+    case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
+        return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT;
+    case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
+        return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT;
+    case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
+        return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT;
+#endif
+    default:
+        return format;
+    }
+#else
+    return format;
+#endif
+}
+
+void Texture::RegenerateLevels_OGL()
+{
+    if (!object_.name_)
+        return;
+
+#ifndef GL_ES_VERSION_2_0
+    if (Graphics::GetGL3Support())
+        glGenerateMipmap(target_);
+    else
+        glGenerateMipmapEXT(target_);
+#else
+    glGenerateMipmap(target_);
+#endif
+
+    levelsDirty_ = false;
+}
+
+}

+ 480 - 478
Source/Urho3D/GraphicsAPI/OpenGL/OGLTexture2D.cpp

@@ -1,478 +1,480 @@
-// Copyright (c) 2008-2022 the Urho3D project
-// License: MIT
-
-#include "../../Precompiled.h"
-
-#include "../../Core/Context.h"
-#include "../../Core/Profiler.h"
-#include "../../Graphics/Graphics.h"
-#include "../../Graphics/GraphicsEvents.h"
-#include "../../Graphics/Renderer.h"
-#include "../../GraphicsAPI/GraphicsImpl.h"
-#include "../../GraphicsAPI/Texture2D.h"
-#include "../../IO/FileSystem.h"
-#include "../../IO/Log.h"
-#include "../../Resource/ResourceCache.h"
-#include "../../Resource/XMLFile.h"
-
-#include "../../DebugNew.h"
-
-namespace Urho3D
-{
-
-void Texture2D::OnDeviceLost_OGL()
-{
-    if (object_.name_ && !graphics_->IsDeviceLost())
-        glDeleteTextures(1, &object_.name_);
-
-    GPUObject::OnDeviceLost();
-
-    if (renderSurface_)
-        renderSurface_->OnDeviceLost();
-}
-
-void Texture2D::OnDeviceReset_OGL()
-{
-    if (!object_.name_ || dataPending_)
-    {
-        // If has a resource file, reload through the resource cache. Otherwise just recreate.
-        auto* cache = GetSubsystem<ResourceCache>();
-        if (cache->Exists(GetName()))
-            dataLost_ = !cache->ReloadResource(this);
-
-        if (!object_.name_)
-        {
-            Create_OGL();
-            dataLost_ = true;
-        }
-    }
-
-    dataPending_ = false;
-}
-
-void Texture2D::Release_OGL()
-{
-    if (object_.name_)
-    {
-        if (!graphics_)
-            return;
-
-        if (!graphics_->IsDeviceLost())
-        {
-            for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
-            {
-                if (graphics_->GetTexture(i) == this)
-                    graphics_->SetTexture(i, nullptr);
-            }
-
-            glDeleteTextures(1, &object_.name_);
-        }
-
-        if (renderSurface_)
-            renderSurface_->Release();
-
-        object_.name_ = 0;
-    }
-    else
-    {
-        if (renderSurface_)
-            renderSurface_->Release();
-    }
-
-    resolveDirty_ = false;
-    levelsDirty_ = false;
-}
-
-bool Texture2D::SetData_OGL(unsigned level, int x, int y, int width, int height, const void* data)
-{
-    URHO3D_PROFILE(SetTextureData);
-
-    if (!object_.name_ || !graphics_)
-    {
-        URHO3D_LOGERROR("No texture created, can not set data");
-        return false;
-    }
-
-    if (!data)
-    {
-        URHO3D_LOGERROR("Null source for setting data");
-        return false;
-    }
-
-    if (level >= levels_)
-    {
-        URHO3D_LOGERROR("Illegal mip level for setting data");
-        return false;
-    }
-
-    if (graphics_->IsDeviceLost())
-    {
-        URHO3D_LOGWARNING("Texture data assignment while device is lost");
-        dataPending_ = true;
-        return true;
-    }
-
-    if (IsCompressed_OGL())
-    {
-        x &= ~3u;
-        y &= ~3u;
-    }
-
-    int levelWidth = GetLevelWidth(level);
-    int levelHeight = GetLevelHeight(level);
-    if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
-    {
-        URHO3D_LOGERROR("Illegal dimensions for setting data");
-        return false;
-    }
-
-    graphics_->SetTextureForUpdate_OGL(this);
-
-    bool wholeLevel = x == 0 && y == 0 && width == levelWidth && height == levelHeight;
-    unsigned format = GetSRGB() ? GetSRGBFormat_OGL(format_) : format_;
-
-    if (!IsCompressed_OGL())
-    {
-        if (wholeLevel)
-            glTexImage2D(target_, level, format, width, height, 0, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), data);
-        else
-            glTexSubImage2D(target_, level, x, y, width, height, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), data);
-    }
-    else
-    {
-        if (wholeLevel)
-            glCompressedTexImage2D(target_, level, format, width, height, 0, GetDataSize(width, height), data);
-        else
-            glCompressedTexSubImage2D(target_, level, x, y, width, height, format, GetDataSize(width, height), data);
-    }
-
-    graphics_->SetTexture(0, nullptr);
-    return true;
-}
-
-bool Texture2D::SetData_OGL(Image* image, bool useAlpha)
-{
-    if (!image)
-    {
-        URHO3D_LOGERROR("Null image, can not set data");
-        return false;
-    }
-
-    // Use a shared ptr for managing the temporary mip images created during this function
-    SharedPtr<Image> mipImage;
-    unsigned memoryUse = sizeof(Texture2D);
-    MaterialQuality quality = QUALITY_HIGH;
-    auto* renderer = GetSubsystem<Renderer>();
-    if (renderer)
-        quality = renderer->GetTextureQuality();
-
-    if (!image->IsCompressed())
-    {
-        // Convert unsuitable formats to RGBA
-        unsigned components = image->GetComponents();
-        if (Graphics::GetGL3Support() && ((components == 1 && !useAlpha) || components == 2))
-        {
-            mipImage = image->ConvertToRGBA(); image = mipImage;
-            if (!image)
-                return false;
-            components = image->GetComponents();
-        }
-
-        unsigned char* levelData = image->GetData();
-        int levelWidth = image->GetWidth();
-        int levelHeight = image->GetHeight();
-        unsigned format = 0;
-
-        // Discard unnecessary mip levels
-        for (unsigned i = 0; i < mipsToSkip_[quality]; ++i)
-        {
-            mipImage = image->GetNextLevel(); image = mipImage;
-            levelData = image->GetData();
-            levelWidth = image->GetWidth();
-            levelHeight = image->GetHeight();
-        }
-
-        switch (components)
-        {
-        case 1:
-            format = useAlpha ? Graphics::GetAlphaFormat() : Graphics::GetLuminanceFormat();
-            break;
-
-        case 2:
-            format = Graphics::GetLuminanceAlphaFormat();
-            break;
-
-        case 3:
-            format = Graphics::GetRGBFormat();
-            break;
-
-        case 4:
-            format = Graphics::GetRGBAFormat();
-            break;
-
-        default:
-            assert(false);  // Should not reach here
-            break;
-        }
-
-        // If image was previously compressed, reset number of requested levels to avoid error if level count is too high for new size
-        if (IsCompressed_OGL() && requestedLevels_ > 1)
-            requestedLevels_ = 0;
-        SetSize(levelWidth, levelHeight, format);
-        if (!object_.name_)
-            return false;
-
-        for (unsigned i = 0; i < levels_; ++i)
-        {
-            SetData_OGL(i, 0, 0, levelWidth, levelHeight, levelData);
-            memoryUse += levelWidth * levelHeight * components;
-
-            if (i < levels_ - 1)
-            {
-                mipImage = image->GetNextLevel(); image = mipImage;
-                levelData = image->GetData();
-                levelWidth = image->GetWidth();
-                levelHeight = image->GetHeight();
-            }
-        }
-    }
-    else
-    {
-        int width = image->GetWidth();
-        int height = image->GetHeight();
-        unsigned levels = image->GetNumCompressedLevels();
-        unsigned format = graphics_->GetFormat(image->GetCompressedFormat());
-        bool needDecompress = false;
-
-        if (!format)
-        {
-            format = Graphics::GetRGBAFormat();
-            needDecompress = true;
-        }
-
-        unsigned mipsToSkip = mipsToSkip_[quality];
-        if (mipsToSkip >= levels)
-            mipsToSkip = levels - 1;
-        while (mipsToSkip && (width / (1u << mipsToSkip) < 4 || height / (1u << mipsToSkip) < 4))
-            --mipsToSkip;
-        width /= (1u << mipsToSkip);
-        height /= (1u << mipsToSkip);
-
-        SetNumLevels(Max((levels - mipsToSkip), 1U));
-        SetSize(width, height, format);
-
-        for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
-        {
-            CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
-            if (!needDecompress)
-            {
-                SetData_OGL(i, 0, 0, level.width_, level.height_, level.data_);
-                memoryUse += level.rows_ * level.rowSize_;
-            }
-            else
-            {
-                auto* rgbaData = new unsigned char[level.width_ * level.height_ * 4];
-                level.Decompress(rgbaData);
-                SetData_OGL(i, 0, 0, level.width_, level.height_, rgbaData);
-                memoryUse += level.width_ * level.height_ * 4;
-                delete[] rgbaData;
-            }
-        }
-    }
-
-    SetMemoryUse(memoryUse);
-    return true;
-}
-
-bool Texture2D::GetData_OGL(unsigned level, void* dest) const
-{
-    if (!object_.name_ || !graphics_)
-    {
-        URHO3D_LOGERROR("No texture created, can not get data");
-        return false;
-    }
-
-#ifndef GL_ES_VERSION_2_0
-    if (!dest)
-    {
-        URHO3D_LOGERROR("Null destination for getting data");
-        return false;
-    }
-
-    if (level >= levels_)
-    {
-        URHO3D_LOGERROR("Illegal mip level for getting data");
-        return false;
-    }
-
-    if (graphics_->IsDeviceLost())
-    {
-        URHO3D_LOGWARNING("Getting texture data while device is lost");
-        return false;
-    }
-
-    if (multiSample_ > 1 && !autoResolve_)
-    {
-        URHO3D_LOGERROR("Can not get data from multisampled texture without autoresolve");
-        return false;
-    }
-
-    if (resolveDirty_)
-        graphics_->ResolveToTexture(const_cast<Texture2D*>(this));
-
-    graphics_->SetTextureForUpdate_OGL(const_cast<Texture2D*>(this));
-
-    if (!IsCompressed_OGL())
-        glGetTexImage(target_, level, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), dest);
-    else
-        glGetCompressedTexImage(target_, level, dest);
-
-    graphics_->SetTexture(0, nullptr);
-    return true;
-#else
-    // Special case on GLES: if the texture is a rendertarget, can make it current and use glReadPixels()
-    if (usage_ == TEXTURE_RENDERTARGET)
-    {
-        graphics_->SetRenderTarget(0, const_cast<Texture2D*>(this));
-        // Ensure the FBO is current; this viewport is actually never rendered to
-        graphics_->SetViewport(IntRect(0, 0, width_, height_));
-        glReadPixels(0, 0, width_, height_, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), dest);
-        return true;
-    }
-
-    URHO3D_LOGERROR("Getting texture data not supported");
-    return false;
-#endif
-}
-
-bool Texture2D::Create_OGL()
-{
-    Release_OGL();
-
-    if (!graphics_ || !width_ || !height_)
-        return false;
-
-    if (graphics_->IsDeviceLost())
-    {
-        URHO3D_LOGWARNING("Texture creation while device is lost");
-        return true;
-    }
-
-#ifdef GL_ES_VERSION_2_0
-    if (multiSample_ > 1)
-    {
-        URHO3D_LOGWARNING("Multisampled texture is not supported on OpenGL ES");
-        multiSample_ = 1;
-        autoResolve_ = false;
-    }
-#endif
-
-    unsigned format = GetSRGB() ? GetSRGBFormat_OGL(format_) : format_;
-    unsigned externalFormat = GetExternalFormat_OGL(format_);
-    unsigned dataType = GetDataType_OGL(format_);
-
-    // Create a renderbuffer instead of a texture if depth texture is not properly supported, or if this will be a packed
-    // depth stencil texture
-#ifndef GL_ES_VERSION_2_0
-    if (format == Graphics::GetDepthStencilFormat())
-#else
-    if (format == GL_DEPTH_COMPONENT16 || format == GL_DEPTH_COMPONENT24_OES || format == GL_DEPTH24_STENCIL8_OES ||
-        (format == GL_DEPTH_COMPONENT && !graphics_->GetShadowMapFormat()))
-#endif
-    {
-        if (renderSurface_)
-        {
-            renderSurface_->CreateRenderBuffer(width_, height_, format, multiSample_);
-            return true;
-        }
-        else
-            return false;
-    }
-    else
-    {
-        if (multiSample_ > 1)
-        {
-            if (autoResolve_)
-            {
-                // Multisample with autoresolve: create a renderbuffer for rendering, but also a texture
-                renderSurface_->CreateRenderBuffer(width_, height_, format, multiSample_);
-            }
-            else
-            {
-                // Multisample without autoresolve: create a texture only
-#ifndef GL_ES_VERSION_2_0
-                if (!Graphics::GetGL3Support() && !GLEW_ARB_texture_multisample)
-                {
-                    URHO3D_LOGERROR("Multisampled texture extension not available");
-                    return false;
-                }
-
-                target_ = GL_TEXTURE_2D_MULTISAMPLE;
-                if (renderSurface_)
-                    renderSurface_->target_ = GL_TEXTURE_2D_MULTISAMPLE;
-#endif
-            }
-        }
-    }
-
-    glGenTextures(1, &object_.name_);
-
-    // Ensure that our texture is bound to OpenGL texture unit 0
-    graphics_->SetTextureForUpdate_OGL(this);
-
-    // If not compressed, create the initial level 0 texture with null data
-    bool success = true;
-
-    if (!IsCompressed_OGL())
-    {
-        glGetError();
-#ifndef GL_ES_VERSION_2_0
-        if (multiSample_ > 1 && !autoResolve_)
-        {
-            glTexImage2DMultisample(target_, multiSample_, format, width_, height_, GL_TRUE);
-        }
-        else
-#endif
-        {
-            glTexImage2D(target_, 0, format, width_, height_, 0, externalFormat, dataType, nullptr);
-        }
-        if (glGetError())
-        {
-            URHO3D_LOGERROR("Failed to create texture");
-            success = false;
-        }
-    }
-
-    // Set mipmapping
-    if (usage_ == TEXTURE_DEPTHSTENCIL || usage_ == TEXTURE_DYNAMIC)
-        requestedLevels_ = 1;
-    else if (usage_ == TEXTURE_RENDERTARGET)
-    {
-#if defined(__EMSCRIPTEN__) || defined(IOS) || defined(TVOS)
-        // glGenerateMipmap appears to not be working on WebGL or iOS/tvOS, disable rendertarget mipmaps for now
-        requestedLevels_ = 1;
-#else
-        if (requestedLevels_ != 1)
-        {
-            // Generate levels for the first time now
-            RegenerateLevels_OGL();
-            // Determine max. levels automatically
-            requestedLevels_ = 0;
-        }
-#endif
-    }
-
-    levels_ = CheckMaxLevels(width_, height_, requestedLevels_);
-#ifndef GL_ES_VERSION_2_0
-    glTexParameteri(target_, GL_TEXTURE_BASE_LEVEL, 0);
-    glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, levels_ - 1);
-#endif
-
-    // Set initial parameters, then unbind the texture
-    UpdateParameters();
-    graphics_->SetTexture(0, nullptr);
-
-    return success;
-}
-
-}
+// Copyright (c) 2008-2022 the Urho3D project
+// License: MIT
+
+#include "../../Precompiled.h"
+
+#include "../../Core/Context.h"
+#include "../../Core/Profiler.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/GraphicsEvents.h"
+#include "../../Graphics/Renderer.h"
+#include "../../GraphicsAPI/GraphicsImpl.h"
+#include "../../GraphicsAPI/Texture2D.h"
+#include "../../IO/FileSystem.h"
+#include "../../IO/Log.h"
+#include "../../IO/VectorBuffer.h"
+#include "../../Resource/ResourceCache.h"
+#include "../../Resource/XMLFile.h"
+
+#include "../../DebugNew.h"
+
+namespace Urho3D
+{
+
+void Texture2D::OnDeviceLost_OGL()
+{
+    if (object_.name_ && !graphics_->IsDeviceLost())
+        glDeleteTextures(1, &object_.name_);
+
+    GPUObject::OnDeviceLost();
+
+    if (renderSurface_)
+        renderSurface_->OnDeviceLost();
+}
+
+void Texture2D::OnDeviceReset_OGL()
+{
+    if (!object_.name_ || dataPending_)
+    {
+        // If has a resource file, reload through the resource cache. Otherwise just recreate.
+        auto* cache = GetSubsystem<ResourceCache>();
+        if (cache->Exists(GetName()))
+            dataLost_ = !cache->ReloadResource(this);
+
+        if (!object_.name_)
+        {
+            Create_OGL();
+            dataLost_ = true;
+        }
+    }
+
+    dataPending_ = false;
+}
+
+void Texture2D::Release_OGL()
+{
+    if (object_.name_)
+    {
+        if (!graphics_)
+            return;
+
+        if (!graphics_->IsDeviceLost())
+        {
+            for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
+            {
+                if (graphics_->GetTexture(i) == this)
+                    graphics_->SetTexture(i, nullptr);
+            }
+
+            glDeleteTextures(1, &object_.name_);
+        }
+
+        if (renderSurface_)
+            renderSurface_->Release();
+
+        object_.name_ = 0;
+    }
+    else
+    {
+        if (renderSurface_)
+            renderSurface_->Release();
+    }
+
+    resolveDirty_ = false;
+    levelsDirty_ = false;
+}
+
+bool Texture2D::SetData_OGL(unsigned level, int x, int y, int width, int height, const void* data)
+{
+    URHO3D_PROFILE(SetTextureData);
+
+    if (!object_.name_ || !graphics_)
+    {
+        URHO3D_LOGERROR("No texture created, can not set data");
+        return false;
+    }
+
+    if (!data)
+    {
+        URHO3D_LOGERROR("Null source for setting data");
+        return false;
+    }
+
+    if (level >= levels_)
+    {
+        URHO3D_LOGERROR("Illegal mip level for setting data");
+        return false;
+    }
+
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Texture data assignment while device is lost");
+        dataPending_ = true;
+        return true;
+    }
+
+    if (IsCompressed_OGL())
+    {
+        x &= ~3u;
+        y &= ~3u;
+    }
+
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+    if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
+    {
+        URHO3D_LOGERROR("Illegal dimensions for setting data");
+        return false;
+    }
+
+    graphics_->SetTextureForUpdate_OGL(this);
+
+    bool wholeLevel = x == 0 && y == 0 && width == levelWidth && height == levelHeight;
+    unsigned format = GetSRGB() ? GetSRGBFormat_OGL(format_) : format_;
+
+    if (!IsCompressed_OGL())
+    {
+        if (wholeLevel)
+            glTexImage2D(target_, level, format, width, height, 0, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), data);
+        else
+            glTexSubImage2D(target_, level, x, y, width, height, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), data);
+    }
+    else
+    {
+        if (wholeLevel)
+            glCompressedTexImage2D(target_, level, format, width, height, 0, GetDataSize(width, height), data);
+        else
+            glCompressedTexSubImage2D(target_, level, x, y, width, height, format, GetDataSize(width, height), data);
+    }
+
+    graphics_->SetTexture(0, nullptr);
+    return true;
+}
+
+bool Texture2D::SetData_OGL(Image* image, bool useAlpha)
+{
+    if (!image)
+    {
+        URHO3D_LOGERROR("Null image, can not set data");
+        return false;
+    }
+
+    // Use a shared ptr for managing the temporary mip images created during this function
+    SharedPtr<Image> mipImage;
+    unsigned memoryUse = sizeof(Texture2D);
+    MaterialQuality quality = QUALITY_HIGH;
+    auto* renderer = GetSubsystem<Renderer>();
+    if (renderer)
+        quality = renderer->GetTextureQuality();
+
+    if (!image->IsCompressed())
+    {
+        // Convert unsuitable formats to RGBA
+        unsigned components = image->GetComponents();
+        if (Graphics::GetGL3Support() && ((components == 1 && !useAlpha) || components == 2))
+        {
+            mipImage = image->ConvertToRGBA(); image = mipImage;
+            if (!image)
+                return false;
+            components = image->GetComponents();
+        }
+
+        unsigned char* levelData = image->GetData();
+        int levelWidth = image->GetWidth();
+        int levelHeight = image->GetHeight();
+        unsigned format = 0;
+
+        // Discard unnecessary mip levels
+        for (unsigned i = 0; i < mipsToSkip_[quality]; ++i)
+        {
+            mipImage = image->GetNextLevel(); image = mipImage;
+            levelData = image->GetData();
+            levelWidth = image->GetWidth();
+            levelHeight = image->GetHeight();
+        }
+
+        switch (components)
+        {
+        case 1:
+            format = useAlpha ? Graphics::GetAlphaFormat() : Graphics::GetLuminanceFormat();
+            break;
+
+        case 2:
+            format = Graphics::GetLuminanceAlphaFormat();
+            break;
+
+        case 3:
+            format = Graphics::GetRGBFormat();
+            break;
+
+        case 4:
+            format = Graphics::GetRGBAFormat();
+            break;
+
+        default:
+            assert(false);  // Should not reach here
+            break;
+        }
+
+        // If image was previously compressed, reset number of requested levels to avoid error if level count is too high for new size
+        if (IsCompressed_OGL() && requestedLevels_ > 1)
+            requestedLevels_ = 0;
+        SetSize(levelWidth, levelHeight, format);
+        if (!object_.name_)
+            return false;
+
+        for (unsigned i = 0; i < levels_; ++i)
+        {
+            SetData_OGL(i, 0, 0, levelWidth, levelHeight, levelData);
+            memoryUse += levelWidth * levelHeight * components;
+
+            if (i < levels_ - 1)
+            {
+                mipImage = image->GetNextLevel(); image = mipImage;
+                levelData = image->GetData();
+                levelWidth = image->GetWidth();
+                levelHeight = image->GetHeight();
+            }
+        }
+    }
+    else
+    {
+        int width = image->GetWidth();
+        int height = image->GetHeight();
+        unsigned levels = image->GetNumCompressedLevels();
+        unsigned format = graphics_->GetFormat(image->GetCompressedFormat());
+        bool needDecompress = false;
+
+        if (!format)
+        {
+            format = Graphics::GetRGBAFormat();
+            needDecompress = true;
+        }
+
+        unsigned mipsToSkip = mipsToSkip_[quality];
+        if (mipsToSkip >= levels)
+            mipsToSkip = levels - 1;
+        while (mipsToSkip && (width / (1u << mipsToSkip) < 4 || height / (1u << mipsToSkip) < 4))
+            --mipsToSkip;
+        width /= (1u << mipsToSkip);
+        height /= (1u << mipsToSkip);
+
+        SetNumLevels(Max((levels - mipsToSkip), 1U));
+        SetSize(width, height, format);
+
+        for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
+        {
+            CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
+            if (!needDecompress)
+            {
+                SetData_OGL(i, 0, 0, level.width_, level.height_, level.data_);
+                memoryUse += level.rows_ * level.rowSize_;
+            }
+            else
+            {
+                auto* rgbaData = new unsigned char[level.width_ * level.height_ * 4];
+                level.Decompress(rgbaData);
+                SetData_OGL(i, 0, 0, level.width_, level.height_, rgbaData);
+                memoryUse += level.width_ * level.height_ * 4;
+                delete[] rgbaData;
+            }
+        }
+    }
+
+    SetMemoryUse(memoryUse);
+    return true;
+}
+
+bool Texture2D::GetData_OGL(unsigned level, void* dest) const
+{
+    if (!object_.name_ || !graphics_)
+    {
+        URHO3D_LOGERROR("No texture created, can not get data");
+        return false;
+    }
+
+#ifndef GL_ES_VERSION_2_0
+    if (!dest)
+    {
+        URHO3D_LOGERROR("Null destination for getting data");
+        return false;
+    }
+
+    if (level >= levels_)
+    {
+        URHO3D_LOGERROR("Illegal mip level for getting data");
+        return false;
+    }
+
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Getting texture data while device is lost");
+        return false;
+    }
+
+    if (multiSample_ > 1 && !autoResolve_)
+    {
+        URHO3D_LOGERROR("Can not get data from multisampled texture without autoresolve");
+        return false;
+    }
+
+    if (resolveDirty_)
+        graphics_->ResolveToTexture(const_cast<Texture2D*>(this));
+
+    graphics_->SetTextureForUpdate_OGL(const_cast<Texture2D*>(this));
+
+    if (!IsCompressed_OGL())
+        glGetTexImage(target_, level, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), dest);
+    else
+        glGetCompressedTexImage(target_, level, dest);
+
+    graphics_->SetTexture(0, nullptr);
+    return true;
+#else
+    // Special case on GLES: if the texture is a rendertarget, can make it current and use glReadPixels()
+    if (usage_ == TEXTURE_RENDERTARGET)
+    {
+        graphics_->SetRenderTarget(0, const_cast<Texture2D*>(this));
+        // Ensure the FBO is current; this viewport is actually never rendered to
+        graphics_->SetViewport(IntRect(0, 0, width_, height_));
+        glReadPixels(0, 0, width_, height_, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), dest);
+        return true;
+    }
+
+    URHO3D_LOGERROR("Getting texture data not supported");
+    return false;
+#endif
+}
+
+bool Texture2D::Create_OGL()
+{
+    Release_OGL();
+
+    if (!graphics_ || !width_ || !height_)
+        return false;
+
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Texture creation while device is lost");
+        return true;
+    }
+
+#ifdef GL_ES_VERSION_2_0
+    if (multiSample_ > 1)
+    {
+        URHO3D_LOGWARNING("Multisampled texture is not supported on OpenGL ES");
+        multiSample_ = 1;
+        autoResolve_ = false;
+    }
+#endif
+
+    unsigned format = GetSRGB() ? GetSRGBFormat_OGL(format_) : format_;
+    unsigned externalFormat = GetExternalFormat_OGL(format_);
+    unsigned dataType = GetDataType_OGL(format_);
+
+    // Create a renderbuffer instead of a texture if depth texture is not properly supported, or if this will be a packed
+    // depth stencil texture
+#ifdef DESKTOP_GRAPHICS_OR_GLES3
+    if (format == Graphics::GetDepthStencilFormat())
+#else
+    if (format == GL_DEPTH_COMPONENT16 || format == GL_DEPTH_COMPONENT24_OES || format == GL_DEPTH24_STENCIL8_OES ||
+        (format == GL_DEPTH_COMPONENT && !graphics_->GetShadowMapFormat()))
+#endif
+    {
+        if (renderSurface_)
+        {
+            renderSurface_->CreateRenderBuffer(width_, height_, format, multiSample_);
+            return true;
+        }
+        else
+            return false;
+    }
+    else
+    {
+        if (multiSample_ > 1)
+        {
+            if (autoResolve_)
+            {
+                // Multisample with autoresolve: create a renderbuffer for rendering, but also a texture
+                renderSurface_->CreateRenderBuffer(width_, height_, format, multiSample_);
+            }
+            else
+            {
+                // Multisample without autoresolve: create a texture only
+#ifndef GL_ES_VERSION_2_0
+                if (!Graphics::GetGL3Support() && !GLEW_ARB_texture_multisample)
+                {
+                    URHO3D_LOGERROR("Multisampled texture extension not available");
+                    return false;
+                }
+
+                target_ = GL_TEXTURE_2D_MULTISAMPLE;
+                if (renderSurface_)
+                    renderSurface_->target_ = GL_TEXTURE_2D_MULTISAMPLE;
+#endif
+            }
+        }
+    }
+
+    glGenTextures(1, &object_.name_);
+
+    // Ensure that our texture is bound to OpenGL texture unit 0
+    graphics_->SetTextureForUpdate_OGL(this);
+
+    // If not compressed, create the initial level 0 texture with null data
+    bool success = true;
+
+    if (!IsCompressed_OGL())
+    {
+        glGetError();
+#ifndef GL_ES_VERSION_2_0
+        if (multiSample_ > 1 && !autoResolve_)
+        {
+            glTexImage2DMultisample(target_, multiSample_, format, width_, height_, GL_TRUE);
+        }
+        else
+#endif
+        {
+            glTexImage2D(target_, 0, format, width_, height_, 0, externalFormat, dataType, nullptr);
+        }
+        GLenum err = glGetError();
+        if (err)
+        {
+            URHO3D_LOGERRORF("Failed to create 2D texture err=%d, target=%d, format=%d, externalFormat=%d, dataType=%d", err, target_, format, externalFormat, dataType);
+            success = false;
+        }
+    }
+
+    // Set mipmapping
+    if (usage_ == TEXTURE_DEPTHSTENCIL || usage_ == TEXTURE_DYNAMIC)
+        requestedLevels_ = 1;
+    else if (usage_ == TEXTURE_RENDERTARGET)
+    {
+#if defined(__EMSCRIPTEN__) || defined(IOS) || defined(TVOS)
+        // glGenerateMipmap appears to not be working on WebGL or iOS/tvOS, disable rendertarget mipmaps for now
+        requestedLevels_ = 1;
+#else
+        if (requestedLevels_ != 1)
+        {
+            // Generate levels for the first time now
+            RegenerateLevels_OGL();
+            // Determine max. levels automatically
+            requestedLevels_ = 0;
+        }
+#endif
+    }
+
+    levels_ = CheckMaxLevels(width_, height_, requestedLevels_);
+#ifndef GL_ES_VERSION_2_0
+    glTexParameteri(target_, GL_TEXTURE_BASE_LEVEL, 0);
+    glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, levels_ - 1);
+#endif
+
+    // Set initial parameters, then unbind the texture
+    UpdateParameters();
+    graphics_->SetTexture(0, nullptr);
+
+    return success;
+}
+
+}

+ 1 - 1
Source/Urho3D/GraphicsAPI/OpenGL/OGLTexture2DArray.cpp

@@ -401,7 +401,7 @@ bool Texture2DArray::Create_OGL()
 {
     Release_OGL();
 
-#ifdef GL_ES_VERSION_2_0
+#if defined(GL_ES_VERSION_2_0) && !defined(GL_ES_VERSION_3_0)
     URHO3D_LOGERROR("Failed to create 2D array texture, currently unsupported on OpenGL ES 2");
     return false;
 #else

+ 3 - 3
Source/Urho3D/GraphicsAPI/OpenGL/OGLTexture3D.cpp

@@ -112,7 +112,7 @@ bool Texture3D::SetData_OGL(unsigned level, int x, int y, int z, int width, int
 
     graphics_->SetTextureForUpdate_OGL(this);
 
-#ifndef GL_ES_VERSION_2_0
+#ifndef URHO3D_GLES2
     bool wholeLevel = x == 0 && y == 0 && z == 0 && width == levelWidth && height == levelHeight && depth == levelDepth;
     unsigned format = GetSRGB() ? GetSRGBFormat_OGL(format_) : format_;
 
@@ -322,7 +322,7 @@ bool Texture3D::Create_OGL()
 {
     Release_OGL();
 
-#ifdef GL_ES_VERSION_2_0
+#if defined(GL_ES_VERSION_2_0) && !defined(GL_ES_VERSION_3_0)
     URHO3D_LOGERROR("Failed to create 3D texture, currently unsupported on OpenGL ES 2");
     return false;
 #else
@@ -353,7 +353,7 @@ bool Texture3D::Create_OGL()
         glTexImage3D(target_, 0, format, width_, height_, depth_, 0, externalFormat, dataType, nullptr);
         if (glGetError())
         {
-            URHO3D_LOGERROR("Failed to create texture");
+            URHO3D_LOGERROR("Failed to create 3D texture");
             success = false;
         }
     }

+ 459 - 459
Source/Urho3D/GraphicsAPI/OpenGL/OGLTextureCube.cpp

@@ -1,499 +1,499 @@
 // Copyright (c) 2008-2022 the Urho3D project
 // License: MIT
-
-#include "../../Precompiled.h"
-
-#include "../../Core/Context.h"
-#include "../../Core/Profiler.h"
-#include "../../Graphics/Graphics.h"
-#include "../../Graphics/GraphicsEvents.h"
-#include "../../Graphics/Renderer.h"
+
+#include "../../Precompiled.h"
+
+#include "../../Core/Context.h"
+#include "../../Core/Profiler.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/GraphicsEvents.h"
+#include "../../Graphics/Renderer.h"
 #include "../../GraphicsAPI/GraphicsImpl.h"
 #include "../../GraphicsAPI/TextureCube.h"
-#include "../../IO/FileSystem.h"
-#include "../../IO/Log.h"
-#include "../../Resource/ResourceCache.h"
-#include "../../Resource/XMLFile.h"
-
-#include "../../DebugNew.h"
-
-#ifdef _MSC_VER
-#pragma warning(disable:4355)
-#endif
-
-namespace Urho3D
-{
-
+#include "../../IO/FileSystem.h"
+#include "../../IO/Log.h"
+#include "../../Resource/ResourceCache.h"
+#include "../../Resource/XMLFile.h"
+
+#include "../../DebugNew.h"
+
+#ifdef _MSC_VER
+#pragma warning(disable:4355)
+#endif
+
+namespace Urho3D
+{
+
 void TextureCube::OnDeviceLost_OGL()
-{
+{
     if (object_.name_ && !graphics_->IsDeviceLost())
         glDeleteTextures(1, &object_.name_);
 
-    GPUObject::OnDeviceLost();
-
-    for (auto& renderSurface : renderSurfaces_)
-    {
-        if (renderSurface)
-            renderSurface->OnDeviceLost();
-    }
-}
-
+    GPUObject::OnDeviceLost();
+
+    for (auto& renderSurface : renderSurfaces_)
+    {
+        if (renderSurface)
+            renderSurface->OnDeviceLost();
+    }
+}
+
 void TextureCube::OnDeviceReset_OGL()
-{
-    if (!object_.name_ || dataPending_)
-    {
-        // If has a resource file, reload through the resource cache. Otherwise just recreate.
-        auto* cache = GetSubsystem<ResourceCache>();
-        if (cache->Exists(GetName()))
-            dataLost_ = !cache->ReloadResource(this);
-
-        if (!object_.name_)
-        {
+{
+    if (!object_.name_ || dataPending_)
+    {
+        // If has a resource file, reload through the resource cache. Otherwise just recreate.
+        auto* cache = GetSubsystem<ResourceCache>();
+        if (cache->Exists(GetName()))
+            dataLost_ = !cache->ReloadResource(this);
+
+        if (!object_.name_)
+        {
             Create_OGL();
-            dataLost_ = true;
-        }
-    }
-
-    dataPending_ = false;
-}
-
+            dataLost_ = true;
+        }
+    }
+
+    dataPending_ = false;
+}
+
 void TextureCube::Release_OGL()
-{
-    if (object_.name_)
-    {
-        if (!graphics_)
-            return;
-
-        if (!graphics_->IsDeviceLost())
-        {
-            for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
-            {
-                if (graphics_->GetTexture(i) == this)
-                    graphics_->SetTexture(i, nullptr);
-            }
-
-            glDeleteTextures(1, &object_.name_);
-        }
-
-        for (auto& renderSurface : renderSurfaces_)
-        {
-            if (renderSurface)
-                renderSurface->Release();
-        }
-
-        object_.name_ = 0;
-    }
-
-    resolveDirty_ = false;
-    levelsDirty_ = false;
-}
-
+{
+    if (object_.name_)
+    {
+        if (!graphics_)
+            return;
+
+        if (!graphics_->IsDeviceLost())
+        {
+            for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
+            {
+                if (graphics_->GetTexture(i) == this)
+                    graphics_->SetTexture(i, nullptr);
+            }
+
+            glDeleteTextures(1, &object_.name_);
+        }
+
+        for (auto& renderSurface : renderSurfaces_)
+        {
+            if (renderSurface)
+                renderSurface->Release();
+        }
+
+        object_.name_ = 0;
+    }
+
+    resolveDirty_ = false;
+    levelsDirty_ = false;
+}
+
 bool TextureCube::SetData_OGL(CubeMapFace face, unsigned level, int x, int y, int width, int height, const void* data)
-{
-    URHO3D_PROFILE(SetTextureData);
-
-    if (!object_.name_ || !graphics_)
-    {
-        URHO3D_LOGERROR("No texture created, can not set data");
-        return false;
-    }
-
-    if (!data)
-    {
-        URHO3D_LOGERROR("Null source for setting data");
-        return false;
-    }
-
-    if (level >= levels_)
-    {
-        URHO3D_LOGERROR("Illegal mip level for setting data");
-        return false;
-    }
-
-    if (graphics_->IsDeviceLost())
-    {
-        URHO3D_LOGWARNING("Texture data assignment while device is lost");
-        dataPending_ = true;
-        return true;
-    }
-
+{
+    URHO3D_PROFILE(SetTextureData);
+
+    if (!object_.name_ || !graphics_)
+    {
+        URHO3D_LOGERROR("No texture created, can not set data");
+        return false;
+    }
+
+    if (!data)
+    {
+        URHO3D_LOGERROR("Null source for setting data");
+        return false;
+    }
+
+    if (level >= levels_)
+    {
+        URHO3D_LOGERROR("Illegal mip level for setting data");
+        return false;
+    }
+
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Texture data assignment while device is lost");
+        dataPending_ = true;
+        return true;
+    }
+
     if (IsCompressed_OGL())
-    {
-        x &= ~3u;
-        y &= ~3u;
-    }
-
-    int levelWidth = GetLevelWidth(level);
-    int levelHeight = GetLevelHeight(level);
-    if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
-    {
-        URHO3D_LOGERROR("Illegal dimensions for setting data");
-        return false;
-    }
-
+    {
+        x &= ~3u;
+        y &= ~3u;
+    }
+
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+    if (x < 0 || x + width > levelWidth || y < 0 || y + height > levelHeight || width <= 0 || height <= 0)
+    {
+        URHO3D_LOGERROR("Illegal dimensions for setting data");
+        return false;
+    }
+
     graphics_->SetTextureForUpdate_OGL(this);
-
-    bool wholeLevel = x == 0 && y == 0 && width == levelWidth && height == levelHeight;
+
+    bool wholeLevel = x == 0 && y == 0 && width == levelWidth && height == levelHeight;
     unsigned format = GetSRGB() ? GetSRGBFormat_OGL(format_) : format_;
-
+
     if (!IsCompressed_OGL())
-    {
-        if (wholeLevel)
+    {
+        if (wholeLevel)
             glTexImage2D((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, format, width, height, 0, GetExternalFormat_OGL(format_),
                 GetDataType_OGL(format_), data);
-        else
+        else
             glTexSubImage2D((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, x, y, width, height, GetExternalFormat_OGL(format_),
                 GetDataType_OGL(format_), data);
-    }
-    else
-    {
-        if (wholeLevel)
-            glCompressedTexImage2D((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, format, width, height, 0,
-                GetDataSize(width, height), data);
-        else
-            glCompressedTexSubImage2D((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, x, y, width, height, format,
-                GetDataSize(width, height), data);
-    }
-
-    graphics_->SetTexture(0, nullptr);
-    return true;
-}
-
+    }
+    else
+    {
+        if (wholeLevel)
+            glCompressedTexImage2D((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, format, width, height, 0,
+                GetDataSize(width, height), data);
+        else
+            glCompressedTexSubImage2D((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, x, y, width, height, format,
+                GetDataSize(width, height), data);
+    }
+
+    graphics_->SetTexture(0, nullptr);
+    return true;
+}
+
 bool TextureCube::SetData_OGL(CubeMapFace face, Deserializer& source)
-{
-    SharedPtr<Image> image(new Image(context_));
-    if (!image->Load(source))
-        return false;
-
+{
+    SharedPtr<Image> image(new Image(context_));
+    if (!image->Load(source))
+        return false;
+
     return SetData_OGL(face, image);
-}
-
+}
+
 bool TextureCube::SetData_OGL(CubeMapFace face, Image* image, bool useAlpha)
-{
-    if (!image)
-    {
-        URHO3D_LOGERROR("Null image, can not set face data");
-        return false;
-    }
-
-    // Use a shared ptr for managing the temporary mip images created during this function
-    SharedPtr<Image> mipImage;
-    unsigned memoryUse = 0;
-    MaterialQuality quality = QUALITY_HIGH;
-    auto* renderer = GetSubsystem<Renderer>();
-    if (renderer)
-        quality = renderer->GetTextureQuality();
-
-    if (!image->IsCompressed())
-    {
-        // Convert unsuitable formats to RGBA
-        unsigned components = image->GetComponents();
-        if (Graphics::GetGL3Support() && ((components == 1 && !useAlpha) || components == 2))
-        {
-            mipImage = image->ConvertToRGBA(); image = mipImage;
-            if (!image)
-                return false;
-            components = image->GetComponents();
-        }
-
-        unsigned char* levelData = image->GetData();
-        int levelWidth = image->GetWidth();
-        int levelHeight = image->GetHeight();
-        unsigned format = 0;
-
-        if (levelWidth != levelHeight)
-        {
-            URHO3D_LOGERROR("Cube texture width not equal to height");
-            return false;
-        }
-
-        // Discard unnecessary mip levels
-        for (unsigned i = 0; i < mipsToSkip_[quality]; ++i)
-        {
-            mipImage = image->GetNextLevel(); image = mipImage;
-            levelData = image->GetData();
-            levelWidth = image->GetWidth();
-            levelHeight = image->GetHeight();
-        }
-
-        switch (components)
-        {
-        case 1:
-            format = useAlpha ? Graphics::GetAlphaFormat() : Graphics::GetLuminanceFormat();
-            break;
-
-        case 2:
-            format = Graphics::GetLuminanceAlphaFormat();
-            break;
-
-        case 3:
-            format = Graphics::GetRGBFormat();
-            break;
-
-        case 4:
-            format = Graphics::GetRGBAFormat();
-            break;
-
-        default:
-            assert(false);  // Should not reach here
-            break;
-        }
-
-        // Create the texture when face 0 is being loaded, check that rest of the faces are same size & format
-        if (!face)
-        {
-            // If image was previously compressed, reset number of requested levels to avoid error if level count is too high for new size
+{
+    if (!image)
+    {
+        URHO3D_LOGERROR("Null image, can not set face data");
+        return false;
+    }
+
+    // Use a shared ptr for managing the temporary mip images created during this function
+    SharedPtr<Image> mipImage;
+    unsigned memoryUse = 0;
+    MaterialQuality quality = QUALITY_HIGH;
+    auto* renderer = GetSubsystem<Renderer>();
+    if (renderer)
+        quality = renderer->GetTextureQuality();
+
+    if (!image->IsCompressed())
+    {
+        // Convert unsuitable formats to RGBA
+        unsigned components = image->GetComponents();
+        if (Graphics::GetGL3Support() && ((components == 1 && !useAlpha) || components == 2))
+        {
+            mipImage = image->ConvertToRGBA(); image = mipImage;
+            if (!image)
+                return false;
+            components = image->GetComponents();
+        }
+
+        unsigned char* levelData = image->GetData();
+        int levelWidth = image->GetWidth();
+        int levelHeight = image->GetHeight();
+        unsigned format = 0;
+
+        if (levelWidth != levelHeight)
+        {
+            URHO3D_LOGERROR("Cube texture width not equal to height");
+            return false;
+        }
+
+        // Discard unnecessary mip levels
+        for (unsigned i = 0; i < mipsToSkip_[quality]; ++i)
+        {
+            mipImage = image->GetNextLevel(); image = mipImage;
+            levelData = image->GetData();
+            levelWidth = image->GetWidth();
+            levelHeight = image->GetHeight();
+        }
+
+        switch (components)
+        {
+        case 1:
+            format = useAlpha ? Graphics::GetAlphaFormat() : Graphics::GetLuminanceFormat();
+            break;
+
+        case 2:
+            format = Graphics::GetLuminanceAlphaFormat();
+            break;
+
+        case 3:
+            format = Graphics::GetRGBFormat();
+            break;
+
+        case 4:
+            format = Graphics::GetRGBAFormat();
+            break;
+
+        default:
+            assert(false);  // Should not reach here
+            break;
+        }
+
+        // Create the texture when face 0 is being loaded, check that rest of the faces are same size & format
+        if (!face)
+        {
+            // If image was previously compressed, reset number of requested levels to avoid error if level count is too high for new size
             if (IsCompressed_OGL() && requestedLevels_ > 1)
-                requestedLevels_ = 0;
-            SetSize(levelWidth, format);
-        }
-        else
-        {
-            if (!object_.name_)
-            {
-                URHO3D_LOGERROR("Cube texture face 0 must be loaded first");
-                return false;
-            }
-            if (levelWidth != width_ || format != format_)
-            {
-                URHO3D_LOGERROR("Cube texture face does not match size or format of face 0");
-                return false;
-            }
-        }
-
-        for (unsigned i = 0; i < levels_; ++i)
-        {
+                requestedLevels_ = 0;
+            SetSize(levelWidth, format);
+        }
+        else
+        {
+            if (!object_.name_)
+            {
+                URHO3D_LOGERROR("Cube texture face 0 must be loaded first");
+                return false;
+            }
+            if (levelWidth != width_ || format != format_)
+            {
+                URHO3D_LOGERROR("Cube texture face does not match size or format of face 0");
+                return false;
+            }
+        }
+
+        for (unsigned i = 0; i < levels_; ++i)
+        {
             SetData_OGL(face, i, 0, 0, levelWidth, levelHeight, levelData);
-            memoryUse += levelWidth * levelHeight * components;
-
-            if (i < levels_ - 1)
-            {
-                mipImage = image->GetNextLevel(); image = mipImage;
-                levelData = image->GetData();
-                levelWidth = image->GetWidth();
-                levelHeight = image->GetHeight();
-            }
-        }
-    }
-    else
-    {
-        int width = image->GetWidth();
-        int height = image->GetHeight();
-        unsigned levels = image->GetNumCompressedLevels();
-        unsigned format = graphics_->GetFormat(image->GetCompressedFormat());
-        bool needDecompress = false;
-
-        if (width != height)
-        {
-            URHO3D_LOGERROR("Cube texture width not equal to height");
-            return false;
-        }
-
-        if (!format)
-        {
-            format = Graphics::GetRGBAFormat();
-            needDecompress = true;
-        }
-
-        unsigned mipsToSkip = mipsToSkip_[quality];
-        if (mipsToSkip >= levels)
-            mipsToSkip = levels - 1;
-        while (mipsToSkip && (width / (1u << mipsToSkip) < 4 || height / (1u << mipsToSkip) < 4))
-            --mipsToSkip;
-        width /= (1u << mipsToSkip);
-        height /= (1u << mipsToSkip);
-
-        // Create the texture when face 0 is being loaded, assume rest of the faces are same size & format
-        if (!face)
-        {
-            SetNumLevels(Max((levels - mipsToSkip), 1U));
-            SetSize(width, format);
-        }
-        else
-        {
-            if (!object_.name_)
-            {
-                URHO3D_LOGERROR("Cube texture face 0 must be loaded first");
-                return false;
-            }
-            if (width != width_ || format != format_)
-            {
-                URHO3D_LOGERROR("Cube texture face does not match size or format of face 0");
-                return false;
-            }
-        }
-
-        for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
-        {
-            CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
-            if (!needDecompress)
-            {
+            memoryUse += levelWidth * levelHeight * components;
+
+            if (i < levels_ - 1)
+            {
+                mipImage = image->GetNextLevel(); image = mipImage;
+                levelData = image->GetData();
+                levelWidth = image->GetWidth();
+                levelHeight = image->GetHeight();
+            }
+        }
+    }
+    else
+    {
+        int width = image->GetWidth();
+        int height = image->GetHeight();
+        unsigned levels = image->GetNumCompressedLevels();
+        unsigned format = graphics_->GetFormat(image->GetCompressedFormat());
+        bool needDecompress = false;
+
+        if (width != height)
+        {
+            URHO3D_LOGERROR("Cube texture width not equal to height");
+            return false;
+        }
+
+        if (!format)
+        {
+            format = Graphics::GetRGBAFormat();
+            needDecompress = true;
+        }
+
+        unsigned mipsToSkip = mipsToSkip_[quality];
+        if (mipsToSkip >= levels)
+            mipsToSkip = levels - 1;
+        while (mipsToSkip && (width / (1u << mipsToSkip) < 4 || height / (1u << mipsToSkip) < 4))
+            --mipsToSkip;
+        width /= (1u << mipsToSkip);
+        height /= (1u << mipsToSkip);
+
+        // Create the texture when face 0 is being loaded, assume rest of the faces are same size & format
+        if (!face)
+        {
+            SetNumLevels(Max((levels - mipsToSkip), 1U));
+            SetSize(width, format);
+        }
+        else
+        {
+            if (!object_.name_)
+            {
+                URHO3D_LOGERROR("Cube texture face 0 must be loaded first");
+                return false;
+            }
+            if (width != width_ || format != format_)
+            {
+                URHO3D_LOGERROR("Cube texture face does not match size or format of face 0");
+                return false;
+            }
+        }
+
+        for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
+        {
+            CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
+            if (!needDecompress)
+            {
                 SetData_OGL(face, i, 0, 0, level.width_, level.height_, level.data_);
-                memoryUse += level.rows_ * level.rowSize_;
-            }
-            else
-            {
-                auto* rgbaData = new unsigned char[level.width_ * level.height_ * 4];
-                level.Decompress(rgbaData);
+                memoryUse += level.rows_ * level.rowSize_;
+            }
+            else
+            {
+                auto* rgbaData = new unsigned char[level.width_ * level.height_ * 4];
+                level.Decompress(rgbaData);
                 SetData_OGL(face, i, 0, 0, level.width_, level.height_, rgbaData);
-                memoryUse += level.width_ * level.height_ * 4;
-                delete[] rgbaData;
-            }
-        }
-    }
-
-    faceMemoryUse_[face] = memoryUse;
-    unsigned totalMemoryUse = sizeof(TextureCube);
-    for (unsigned memoryUse : faceMemoryUse_)
-        totalMemoryUse += memoryUse;
-    SetMemoryUse(totalMemoryUse);
-    return true;
-}
-
+                memoryUse += level.width_ * level.height_ * 4;
+                delete[] rgbaData;
+            }
+        }
+    }
+
+    faceMemoryUse_[face] = memoryUse;
+    unsigned totalMemoryUse = sizeof(TextureCube);
+    for (unsigned memoryUse : faceMemoryUse_)
+        totalMemoryUse += memoryUse;
+    SetMemoryUse(totalMemoryUse);
+    return true;
+}
+
 bool TextureCube::GetData_OGL(CubeMapFace face, unsigned level, void* dest) const
-{
-    if (!object_.name_ || !graphics_)
-    {
-        URHO3D_LOGERROR("No texture created, can not get data");
-        return false;
-    }
-
-#ifndef GL_ES_VERSION_2_0
-    if (!dest)
-    {
-        URHO3D_LOGERROR("Null destination for getting data");
-        return false;
-    }
-
-    if (level >= levels_)
-    {
-        URHO3D_LOGERROR("Illegal mip level for getting data");
-        return false;
-    }
-
-    if (graphics_->IsDeviceLost())
-    {
-        URHO3D_LOGWARNING("Getting texture data while device is lost");
-        return false;
-    }
-
-    if (multiSample_ > 1 && !autoResolve_)
-    {
-        URHO3D_LOGERROR("Can not get data from multisampled texture without autoresolve");
-        return false;
-    }
-
-    if (resolveDirty_)
-        graphics_->ResolveToTexture(const_cast<TextureCube*>(this));
-
+{
+    if (!object_.name_ || !graphics_)
+    {
+        URHO3D_LOGERROR("No texture created, can not get data");
+        return false;
+    }
+
+#ifndef GL_ES_VERSION_2_0
+    if (!dest)
+    {
+        URHO3D_LOGERROR("Null destination for getting data");
+        return false;
+    }
+
+    if (level >= levels_)
+    {
+        URHO3D_LOGERROR("Illegal mip level for getting data");
+        return false;
+    }
+
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Getting texture data while device is lost");
+        return false;
+    }
+
+    if (multiSample_ > 1 && !autoResolve_)
+    {
+        URHO3D_LOGERROR("Can not get data from multisampled texture without autoresolve");
+        return false;
+    }
+
+    if (resolveDirty_)
+        graphics_->ResolveToTexture(const_cast<TextureCube*>(this));
+
     graphics_->SetTextureForUpdate_OGL(const_cast<TextureCube*>(this));
-
+
     if (!IsCompressed_OGL())
         glGetTexImage((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), dest);
-    else
-        glGetCompressedTexImage((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, dest);
-
-    graphics_->SetTexture(0, nullptr);
-    return true;
-#else
-    // Special case on GLES: if the texture is a rendertarget, can make it current and use glReadPixels()
-    if (usage_ == TEXTURE_RENDERTARGET)
-    {
-        graphics_->SetRenderTarget(0, renderSurfaces_[face]);
-        // Ensure the FBO is current; this viewport is actually never rendered to
-        graphics_->SetViewport(IntRect(0, 0, width_, height_));
+    else
+        glGetCompressedTexImage((GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), level, dest);
+
+    graphics_->SetTexture(0, nullptr);
+    return true;
+#else
+    // Special case on GLES: if the texture is a rendertarget, can make it current and use glReadPixels()
+    if (usage_ == TEXTURE_RENDERTARGET)
+    {
+        graphics_->SetRenderTarget(0, renderSurfaces_[face]);
+        // Ensure the FBO is current; this viewport is actually never rendered to
+        graphics_->SetViewport(IntRect(0, 0, width_, height_));
         glReadPixels(0, 0, width_, height_, GetExternalFormat_OGL(format_), GetDataType_OGL(format_), dest);
-        return true;
-    }
-
-    URHO3D_LOGERROR("Getting texture data not supported");
-    return false;
-#endif
-}
-
+        return true;
+    }
+
+    URHO3D_LOGERROR("Getting texture data not supported");
+    return false;
+#endif
+}
+
 bool TextureCube::Create_OGL()
-{
+{
     Release_OGL();
-
-    if (!graphics_ || !width_ || !height_)
-        return false;
-
-    if (graphics_->IsDeviceLost())
-    {
-        URHO3D_LOGWARNING("Texture creation while device is lost");
-        return true;
-    }
-
-#ifdef GL_ES_VERSION_2_0
-    if (multiSample_ > 1)
-    {
-        URHO3D_LOGWARNING("Multisampled texture is not supported on OpenGL ES");
-        multiSample_ = 1;
-        autoResolve_ = false;
-    }
-#endif
-
-    glGenTextures(1, &object_.name_);
-
-    // Ensure that our texture is bound to OpenGL texture unit 0
+
+    if (!graphics_ || !width_ || !height_)
+        return false;
+
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Texture creation while device is lost");
+        return true;
+    }
+
+#ifdef GL_ES_VERSION_2_0
+    if (multiSample_ > 1)
+    {
+        URHO3D_LOGWARNING("Multisampled texture is not supported on OpenGL ES");
+        multiSample_ = 1;
+        autoResolve_ = false;
+    }
+#endif
+
+    glGenTextures(1, &object_.name_);
+
+    // Ensure that our texture is bound to OpenGL texture unit 0
     graphics_->SetTextureForUpdate_OGL(this);
-
-    // If not compressed, create the initial level 0 texture with null data
+
+    // If not compressed, create the initial level 0 texture with null data
     unsigned format = GetSRGB() ? GetSRGBFormat_OGL(format_) : format_;
     unsigned externalFormat = GetExternalFormat_OGL(format_);
     unsigned dataType = GetDataType_OGL(format_);
-
-    // If multisample, create renderbuffers for each face
-    if (multiSample_ > 1)
-    {
-        for (auto& renderSurface : renderSurfaces_)
-            renderSurface->CreateRenderBuffer(width_, height_, format, multiSample_);
-    }
-
-    bool success = true;
+
+    // If multisample, create renderbuffers for each face
+    if (multiSample_ > 1)
+    {
+        for (auto& renderSurface : renderSurfaces_)
+            renderSurface->CreateRenderBuffer(width_, height_, format, multiSample_);
+    }
+
+    bool success = true;
     if (!IsCompressed_OGL())
-    {
-        glGetError();
-        for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
-        {
-            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, width_, height_, 0, externalFormat, dataType, nullptr);
-            if (glGetError())
-                success = false;
-        }
-    }
-    if (!success)
-        URHO3D_LOGERROR("Failed to create texture");
-
-    // Set mipmapping
-    if (usage_ == TEXTURE_DEPTHSTENCIL || usage_ == TEXTURE_DYNAMIC)
-        requestedLevels_ = 1;
-    else if (usage_ == TEXTURE_RENDERTARGET)
-    {
-#if defined(__EMSCRIPTEN__) || defined(IOS) || defined(TVOS)
-        // glGenerateMipmap appears to not be working on WebGL or iOS/tvOS, disable rendertarget mipmaps for now
-        requestedLevels_ = 1;
-#else
-        if (requestedLevels_ != 1)
-        {
-            // Generate levels for the first time now
+    {
+        glGetError();
+        for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
+        {
+            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, width_, height_, 0, externalFormat, dataType, nullptr);
+            if (glGetError())
+                success = false;
+        }
+    }
+    if (!success)
+        URHO3D_LOGERROR("Failed to create cube texture");
+
+    // Set mipmapping
+    if (usage_ == TEXTURE_DEPTHSTENCIL || usage_ == TEXTURE_DYNAMIC)
+        requestedLevels_ = 1;
+    else if (usage_ == TEXTURE_RENDERTARGET)
+    {
+#if defined(__EMSCRIPTEN__) || defined(IOS) || defined(TVOS)
+        // glGenerateMipmap appears to not be working on WebGL or iOS/tvOS, disable rendertarget mipmaps for now
+        requestedLevels_ = 1;
+#else
+        if (requestedLevels_ != 1)
+        {
+            // Generate levels for the first time now
             RegenerateLevels_OGL();
-            // Determine max. levels automatically
-            requestedLevels_ = 0;
-        }
-#endif
-    }
-
-    levels_ = CheckMaxLevels(width_, height_, requestedLevels_);
-#ifndef GL_ES_VERSION_2_0
-    glTexParameteri(target_, GL_TEXTURE_BASE_LEVEL, 0);
-    glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, levels_ - 1);
-#endif
-
-    // Set initial parameters, then unbind the texture
-    UpdateParameters();
-    graphics_->SetTexture(0, nullptr);
-
-    return success;
-}
-
-}
+            // Determine max. levels automatically
+            requestedLevels_ = 0;
+        }
+#endif
+    }
+
+    levels_ = CheckMaxLevels(width_, height_, requestedLevels_);
+#ifndef GL_ES_VERSION_2_0
+    glTexParameteri(target_, GL_TEXTURE_BASE_LEVEL, 0);
+    glTexParameteri(target_, GL_TEXTURE_MAX_LEVEL, levels_ - 1);
+#endif
+
+    // Set initial parameters, then unbind the texture
+    UpdateParameters();
+    graphics_->SetTexture(0, nullptr);
+
+    return success;
+}
+
+}

+ 1 - 1
Source/Urho3D/GraphicsAPI/ShaderPrecache.cpp

@@ -99,7 +99,7 @@ void ShaderPrecache::LoadShaders(Graphics* graphics, Deserializer& source)
         String psDefines = shader.GetAttribute("psdefines");
 
         // Check for illegal variations on OpenGL ES and skip them
-#ifdef GL_ES_VERSION_2_0
+#if defined(GL_ES_VERSION_2_0) && !defined(GL_ES_VERSION_3_0)
         if (
 #ifndef __EMSCRIPTEN__
             vsDefines.Contains("INSTANCED") ||

+ 1 - 1
Source/Urho3D/GraphicsAPI/Texture2DArray.cpp

@@ -28,7 +28,7 @@ Texture2DArray::Texture2DArray(Context* context) :
     Texture(context)
 {
 #ifdef URHO3D_OPENGL
-#ifndef GL_ES_VERSION_2_0
+#ifndef URHO3D_GLES2
     if (Graphics::GetGAPI() == GAPI_OPENGL)
         target_ = GL_TEXTURE_2D_ARRAY;
 #endif

+ 1 - 1
Source/Urho3D/GraphicsAPI/Texture3D.cpp

@@ -24,7 +24,7 @@ Texture3D::Texture3D(Context* context) :
     Texture(context)
 {
 #ifdef URHO3D_OPENGL
-#ifndef GL_ES_VERSION_2_0
+#ifndef URHO3D_GLES2
     if (Graphics::GetGAPI() == GAPI_OPENGL)
         target_ = GL_TEXTURE_3D;
 #endif

+ 18 - 4
Source/Urho3D/Resource/Image.cpp

@@ -34,9 +34,9 @@
 #define FOURCC_DXT5 (MAKEFOURCC('D','X','T','5'))
 #define FOURCC_DX10 (MAKEFOURCC('D','X','1','0'))
 
-#define FOURCC_ETC1 (MAKEFOURCC('E','T','C','1'))
-#define FOURCC_ETC2 (MAKEFOURCC('E','T','C','2'))
-#define FOURCC_ETC2A (MAKEFOURCC('E','T','2','A'))
+#define FOURCC_ETC1 (MAKEFOURCC('E', 'T', 'C', '1'))
+#define FOURCC_ETC2 (MAKEFOURCC('E', 'T', 'C', '2'))
+#define FOURCC_ETC2A (MAKEFOURCC('E', 'T', '2', 'A'))
 
 static const unsigned DDSCAPS_COMPLEX = 0x00000008U;
 static const unsigned DDSCAPS_TEXTURE = 0x00001000U;
@@ -218,7 +218,6 @@ bool CompressedLevel::Decompress(unsigned char* dest) const
     case CF_ETC2_RGBA:
         DecompressImageETC(dest, data_, width_, height_, true);
         return true;
-
     case CF_PVRTC_RGB_2BPP:
     case CF_PVRTC_RGBA_2BPP:
     case CF_PVRTC_RGB_4BPP:
@@ -315,6 +314,21 @@ bool Image::BeginLoad(Deserializer& source)
             components_ = 4;
             break;
 
+        case FOURCC_ETC1:
+            compressedFormat_ = CF_ETC1;
+            components_ = 3;
+            break;
+
+        case FOURCC_ETC2:
+            compressedFormat_ = CF_ETC2_RGB;
+            components_ = 3;
+            break;
+
+        case FOURCC_ETC2A:
+            compressedFormat_ = CF_ETC2_RGBA;
+            components_ = 4;
+            break;
+
         case 0:
             if (ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 32 && ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 24 &&
                 ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 16)

+ 1 - 1
bin/CoreData/RenderPaths/DeferredHWDepth.xml

@@ -4,7 +4,7 @@
     <rendertarget name="depth" sizedivisor="1 1" format="readabledepth" persistent="true" />
     <command type="clear" color="fog" depth="1.0" stencil="0" depthstencil="depth" />
     <command type="clear" color="0 0 0 0" output="albedo" depthstencil="depth" />
-    <command type="scenepass" pass="deferred" marktostencil="true" vertexlights="true" metadata="gbuffer" depthstencil="depth">
+    <command type="scenepass" pass="deferred" marktostencil="true" vertexlights="true" metadata="gbuffer" depthstencil="depth" psdefines="HWDEPTH">
         <output index="0" name="viewport" />
         <output index="1" name="albedo" />
         <output index="2" name="normal" />

+ 1 - 1
bin/CoreData/Shaders/GLSL/Basic.glsl

@@ -43,7 +43,7 @@ void PS()
         gl_FragColor = diffColor * diffInput;
     #endif
     #ifdef ALPHAMAP
-        #ifdef GL3
+        #if defined(GL3) && !defined(GL_ES)
             float alphaInput = texture2D(sDiffMap, vTexCoord).r;
         #else
             float alphaInput = texture2D(sDiffMap, vTexCoord).a;

+ 7 - 7
bin/CoreData/Shaders/GLSL/Lighting.glsl

@@ -68,7 +68,7 @@ float GetVertexLightVolumetric(int index, vec3 worldPos)
 
 #ifdef SHADOW
 
-#if defined(DIRLIGHT) && (!defined(GL_ES) || defined(WEBGL))
+#if defined(DIRLIGHT) && defined(DESKTOP_GRAPHICS)
     #define NUMCASCADES 4
 #else
     #define NUMCASCADES 1
@@ -177,7 +177,7 @@ float GetIntensity(vec3 color)
 
 #ifdef SHADOW
 
-#if defined(DIRLIGHT) && (!defined(GL_ES) || defined(WEBGL))
+#if defined(DIRLIGHT) && defined(DESKTOP_GRAPHICS)
     #define NUMCASCADES 4
 #else
     #define NUMCASCADES 1
@@ -208,7 +208,7 @@ float Chebyshev(vec2 Moments, float depth)
 }
 #endif
 
-#ifndef GL_ES
+#if !defined(GL_ES) || __VERSION__ >= 300
 float GetShadow(vec4 shadowPos)
 {
     #if defined(SIMPLE_SHADOW)
@@ -294,7 +294,7 @@ float GetDirShadowFade(float inLight, float depth)
     return min(inLight + max((depth - cShadowDepthFade.z) * cShadowDepthFade.w, 0.0), 1.0);
 }
 
-#if !defined(GL_ES) || defined(WEBGL)
+#ifdef DESKTOP_GRAPHICS
 float GetDirShadow(const vec4 iShadowPos[NUMCASCADES], float depth)
 {
     vec4 shadowPos;
@@ -317,7 +317,7 @@ float GetDirShadow(const highp vec4 iShadowPos[NUMCASCADES], float depth)
 }
 #endif
 
-#ifndef GL_ES
+#if !defined(GL_ES) || __VERSION__>=300
 float GetDirShadowDeferred(vec4 projWorldPos, vec3 normal, float depth)
 {
     vec4 shadowPos;
@@ -348,7 +348,7 @@ float GetDirShadowDeferred(vec4 projWorldPos, vec3 normal, float depth)
 #endif
 #endif
 
-#ifndef GL_ES
+#if !defined(GL_ES) || __VERSION__>=300
 float GetShadow(const vec4 iShadowPos[NUMCASCADES], float depth)
 #else
 float GetShadow(const highp vec4 iShadowPos[NUMCASCADES], float depth)
@@ -363,7 +363,7 @@ float GetShadow(const highp vec4 iShadowPos[NUMCASCADES], float depth)
     #endif
 }
 
-#ifndef GL_ES
+#if !defined(GL_ES) || __VERSION__ >= 300
 float GetShadowDeferred(vec4 projWorldPos, vec3 normal, float depth)
 {
     #ifdef DIRLIGHT

+ 3 - 1
bin/CoreData/Shaders/GLSL/LitSolid.glsl

@@ -210,7 +210,9 @@ void PS()
         gl_FragData[0] = vec4(GetFog(finalColor, fogFactor), 1.0);
         gl_FragData[1] = fogFactor * vec4(diffColor.rgb, specIntensity);
         gl_FragData[2] = vec4(normal * 0.5 + 0.5, specPower);
-        gl_FragData[3] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+        #ifndef HWDEPTH
+            gl_FragData[3] = vec4(EncodeDepth(vWorldPos.w), 0.0);
+        #endif
     #else
         // Ambient & per-vertex lighting
         vec3 finalColor = vVertexLight * diffColor.rgb;

+ 8 - 2
bin/CoreData/Shaders/GLSL/PostProcess.glsl

@@ -64,8 +64,14 @@ vec3 Uncharted2Tonemap(vec3 x)
    return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
 }
 
-#ifndef GL_ES
-vec3 ColorCorrection(vec3 color, sampler3D lut)
+#if defined(GL_ES) && __VERSION__ >= 300
+#define defprec mediump
+#else
+#define defprec
+#endif
+
+#if !defined(GL_ES) || __VERSION__ >= 300
+vec3 ColorCorrection(vec3 color, defprec sampler3D lut)
 {
     float lutSize = 16.0;
     float scale = (lutSize - 1.0) / lutSize;

+ 7 - 7
bin/CoreData/Shaders/GLSL/Samplers.glsl

@@ -9,21 +9,21 @@ uniform samplerCube sEnvCubeMap;
 uniform sampler2D sLightRampMap;
 uniform sampler2D sLightSpotMap;
 uniform samplerCube sLightCubeMap;
-#ifndef GL_ES
-    uniform sampler3D sVolumeMap;
+#if !defined(GL_ES) || __VERSION__ >= 300
+    uniform highp sampler3D sVolumeMap;
     uniform sampler2D sAlbedoBuffer;
     uniform sampler2D sNormalBuffer;
     uniform sampler2D sDepthBuffer;
     uniform sampler2D sLightBuffer;
     #ifdef VSM_SHADOW
-        uniform sampler2D sShadowMap;
+        uniform highp sampler2D sShadowMap;
     #else
-        uniform sampler2DShadow sShadowMap;
+        uniform highp sampler2DShadow sShadowMap;
     #endif
     uniform samplerCube sFaceSelectCubeMap;
     uniform samplerCube sIndirectionCubeMap;
     uniform samplerCube sZoneCubeMap;
-    uniform sampler3D sZoneVolumeMap;
+    uniform highp sampler3D sZoneVolumeMap;
 #else
     uniform highp sampler2D sShadowMap;
 #endif
@@ -51,7 +51,7 @@ vec3 DecodeNormal(vec4 normalInput)
 
 vec3 EncodeDepth(float depth)
 {
-    #ifndef GL3
+    #if !defined(GL3) || (defined(GL_ES) && __VERSION__ < 300)
         vec3 ret;
         depth *= 255.0;
         ret.x = floor(depth);
@@ -68,7 +68,7 @@ vec3 EncodeDepth(float depth)
 
 float DecodeDepth(vec3 depth)
 {
-    #ifndef GL3
+    #if !defined(GL3) || (defined(GL_ES) && __VERSION__ < 300)
         const vec3 dotValues = vec3(1.0, 1.0 / 255.0, 1.0 / (255.0 * 255.0));
         return dot(depth, dotValues);
     #else

+ 1 - 1
bin/CoreData/Shaders/GLSL/Text.glsl

@@ -113,7 +113,7 @@ void PS()
 #else
     #ifdef ALPHAMAP
         gl_FragColor.rgb = vColor.rgb;
-        #ifdef GL3
+        #if defined(GL3) && !defined(GL_ES)
             gl_FragColor.a = vColor.a * texture2D(sDiffMap, vTexCoord).r;
         #else
             gl_FragColor.a = vColor.a * texture2D(sDiffMap, vTexCoord).a;

+ 7 - 5
bin/CoreData/Shaders/GLSL/Transform.glsl

@@ -61,11 +61,13 @@ vec4 GetClipPos(vec3 worldPos)
 {
     vec4 ret = vec4(worldPos, 1.0) * cViewProj;
     // While getting the clip coordinate, also automatically set gl_ClipVertex for user clip planes
-    #if !defined(GL_ES) && !defined(GL3)
-        gl_ClipVertex = ret;
-    #elif defined(GL3)
-        gl_ClipDistance[0] = dot(cClipPlane, ret);
-    #endif
+#if !defined(GL_ES)
+#   if !defined(GL3)
+    gl_ClipVertex = ret;
+#   else
+    gl_ClipDistance[0] = dot(cClipPlane, ret);
+#   endif
+#endif
     return ret;
 }
 

+ 10 - 3
bin/CoreData/Shaders/GLSL/Uniforms.glsl

@@ -29,7 +29,7 @@ uniform mat4 cViewProj;
 uniform vec4 cUOffset;
 uniform vec4 cVOffset;
 uniform mat4 cZone;
-#if !defined(GL_ES) || defined(WEBGL)
+#if !defined(GL_ES) || defined(WEBGL) || __VERSION__>=300
     uniform mat4 cLightMatrices[4];
 #else
     uniform highp mat4 cLightMatrices[2];
@@ -48,10 +48,11 @@ uniform mat4 cZone;
 #ifdef COMPILEPS
 
 // Fragment shader uniforms
-#ifdef GL_ES
+#if defined(MOBILE_GRAPHICS)
     precision mediump float;
+#elif defined(WEBGL)
+    precision highp float;
 #endif
-
 uniform vec4 cAmbientColor;
 uniform vec3 cCameraPosPS;
 uniform float cDeltaTimePS;
@@ -157,6 +158,12 @@ uniform ObjectVS
 
 #ifdef COMPILEPS
 
+// Fragment shader uniforms
+#if defined(MOBILE_GRAPHICS)
+    precision mediump float;
+#elif defined(WEBGL)
+    precision highp float;
+#endif
 // Pixel shader uniforms
 uniform FramePS
 {

+ 38 - 1
bin/Data/Scripts/06_SkeletalAnimation.as

@@ -5,7 +5,6 @@
 //     - Enabling a cascaded shadow map on a directional light, which allows high-quality shadows
 //       over a large area (typically used in outdoor scenes for shadows cast by sunlight)
 //     - Displaying renderer debug geometry
-
 #include "Scripts/Utilities/Sample.as"
 
 void Start()
@@ -64,6 +63,11 @@ void CreateScene()
     light.shadowBias = BiasParameters(0.00025f, 0.5f);
     // Set cascade splits at 10, 50 and 200 world units, fade shadows out at 80% of maximum shadow distance
     light.shadowCascade = CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f);
+    if (graphics.apiName == "GLES3")
+    {
+    	CreateLights();
+    }
+	
 
     // Create animated models
     const uint NUM_MODELS = 30;
@@ -97,6 +101,16 @@ void CreateScene()
         // it to instantiate the object (using the script file & class name provided)
         Mover@ mover = cast<Mover>(modelNode.CreateScriptObject(scriptFile, "Mover"));
         mover.SetParameters(MODEL_MOVE_SPEED, MODEL_ROTATE_SPEED, bounds);
+
+        if (graphics.apiName == "GLES3")
+        {
+            Node@ nLight = modelNode.CreateChild("Light" + i, LOCAL);
+            nLight.position = Vector3(1, 2, 1);
+            nLight.LookAt(Vector3::ZERO, Vector3::UP, TransformSpace::Parent);
+            Light@ light = nLight.CreateComponent("Light");
+            light.lightType = LIGHT_SPOT;
+            light.color = Color(0.5f + Random(0.5f), 0.5f + Random(0.5f), 0.5f + Random(0.5f));
+        }
     }
 
     // Create the camera. Limit far clip distance to match the fog
@@ -108,6 +122,22 @@ void CreateScene()
     cameraNode.position = Vector3(0.0f, 5.0f, 0.0f);
 }
 
+void CreateLights() {
+    for (uint i = 0; i < 40; i++) {
+        Node@ nLight = scene_.CreateChild("Light" + i, LOCAL);
+        Vector3 pos = Vector3(Random(40.0f) - 20.0f, 1.0f + Random(1.0), Random(40.0f) - 20.0f);
+        nLight.position = pos;
+        pos.y = 0;
+        pos.x += Random(2.0f) - 1.0f;
+        pos.z += Random(2.0f) - 1.0f;
+        nLight.LookAt(pos);
+
+        Light@ light = nLight.CreateComponent("Light");
+        light.lightType = LIGHT_SPOT;
+        light.color = Color(0.5f + Random(0.5f), 0.5f + Random(0.5f), 0.5f + Random(0.5f));
+    }
+}
+
 void CreateInstructions()
 {
     // Construct new Text object, set string to display and font to use
@@ -129,6 +159,12 @@ void SetupViewport()
 {
     // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen
     Viewport@ viewport = Viewport(scene_, cameraNode.GetComponent("Camera"));
+    if (graphics.apiName == "GLES3")
+    {
+        RenderPath rp;
+        rp.Load(cache.GetResource("XMLFile", "RenderPaths/Deferred.xml"));
+    	viewport.renderPath = rp;
+    }
     renderer.viewports[0] = viewport;
 }
 
@@ -227,6 +263,7 @@ class Mover : ScriptObject
     }
 }
 
+
 // Create XML patch instructions for screen joystick layout specific to this sample app
 String patchInstructions =
     "<patch>"+

+ 13 - 1
cmake/Modules/UrhoCommon.cmake

@@ -137,6 +137,7 @@ cmake_dependent_option (URHO3D_NETWORK "Enable networking support" TRUE "NOT WEB
 option (URHO3D_PHYSICS "Enable physics support" TRUE)
 option (URHO3D_PHYSICS2D "Enable 2D physics support" TRUE)
 option (URHO3D_URHO2D "Enable 2D graphics support" TRUE)
+option (URHO3D_GLES3 "Enable GLES3" FALSE)
 option (URHO3D_WEBP "Enable WebP support" TRUE)
 if (ARM AND NOT ANDROID AND NOT RPI AND NOT APPLE)
     set (ARM_ABI_FLAGS "" CACHE STRING "Specify ABI compiler flags (ARM on Linux platform only); e.g. Orange-Pi Mini 2 could use '-mcpu=cortex-a7 -mfpu=neon-vfpv4'")
@@ -463,6 +464,7 @@ foreach (OPT
         URHO3D_TRACY_PROFILING
         URHO3D_THREADING
         URHO3D_URHO2D
+        URHO3D_GLES3
         URHO3D_WEBP
         URHO3D_WIN32_CONSOLE)
     if (${OPT})
@@ -681,6 +683,9 @@ else ()
                 # Since version 1.39.5 emcc disables deprecated find event target behavior by default; we revert the flag for now until the support is removed
                 # (See https://github.com/emscripten-core/emscripten/commit/948af470be12559367e7629f31cf7c841fbeb2a9#diff-291d81f9d42b322a89881b6d91f7a122 for more detail)
                 set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS=\"['Pointer_stringify']\" -s FORCE_FILESYSTEM=1 -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0")
+                if (URHO3D_GLES3)
+                    set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sFULL_ES3")
+                endif()
                 set (CMAKE_C_FLAGS_RELEASE "-Oz -DNDEBUG")
                 set (CMAKE_CXX_FLAGS_RELEASE "-Oz -DNDEBUG")
                 # Remove variables to make the -O3 regalloc easier
@@ -940,6 +945,9 @@ macro (define_dependency_libs TARGET)
 
         # Graphics
         if (URHO3D_OPENGL)
+            if (NOT (ANDROID OR WEB OR IOS OR TVOS))
+                set (URHO3D_GLES3 FALSE)
+            endif()
             if (APPLE)
                 # Do nothing
             elseif (WIN32)
@@ -951,7 +959,11 @@ macro (define_dependency_libs TARGET)
                     list (APPEND LIBS brcmGLESv2)
                 endif ()
             elseif (ANDROID OR ARM)
-                list (APPEND LIBS GLESv1_CM GLESv2)
+                if (URHO3D_GLES3)
+                    list (APPEND LIBS GLESv3)
+                else ()
+                    list (APPEND LIBS GLESv1_CM GLESv2)
+                endif ()
             else ()
                 list (APPEND LIBS GL)
             endif ()

+ 1 - 0
script/.build-options

@@ -44,6 +44,7 @@ URHO3D_THREADING
 URHO3D_TESTING
 URHO3D_TEST_TIMEOUT
 URHO3D_OPENGL
+URHO3D_GLES3
 URHO3D_D3D11
 URHO3D_STATIC_RUNTIME
 URHO3D_WIN32_CONSOLE

+ 1 - 0
script/.env-file

@@ -88,6 +88,7 @@ URHO3D_MMX
 URHO3D_NAVIGATION
 URHO3D_NETWORK
 URHO3D_OPENGL
+URHO3D_GLES3
 URHO3D_PACKAGING
 URHO3D_PCH
 URHO3D_PHYSICS