Browse Source

Added Texture2DArray.

reattiva 9 years ago
parent
commit
88d403b9b8

+ 2 - 0
Source/Urho3D/Graphics/Direct3D11/D3D11Graphics.cpp

@@ -52,6 +52,7 @@
 #include "../../Graphics/Terrain.h"
 #include "../../Graphics/TerrainPatch.h"
 #include "../../Graphics/Texture2D.h"
+#include "../../Graphics/Texture2DArray.h"
 #include "../../Graphics/Texture3D.h"
 #include "../../Graphics/TextureCube.h"
 #include "../../Graphics/VertexBuffer.h"
@@ -2760,6 +2761,7 @@ void RegisterGraphicsLibrary(Context* context)
     Shader::RegisterObject(context);
     Technique::RegisterObject(context);
     Texture2D::RegisterObject(context);
+    Texture2DArray::RegisterObject(context);
     Texture3D::RegisterObject(context);
     TextureCube::RegisterObject(context);
     Camera::RegisterObject(context);

+ 1 - 0
Source/Urho3D/Graphics/Direct3D11/D3D11Graphics.h

@@ -49,6 +49,7 @@ class ShaderProgram;
 class ShaderVariation;
 class Texture;
 class Texture2D;
+class Texture2DArray;
 class TextureCube;
 class Vector3;
 class Vector4;

+ 1 - 0
Source/Urho3D/Graphics/Direct3D11/D3D11RenderSurface.h

@@ -34,6 +34,7 @@ class Texture;
 class URHO3D_API RenderSurface : public RefCounted
 {
     friend class Texture2D;
+    friend class Texture2DArray;
     friend class TextureCube;
 
 public:

+ 669 - 0
Source/Urho3D/Graphics/Direct3D11/D3D11Texture2DArray.cpp

@@ -0,0 +1,669 @@
+//
+// Copyright (c) 2008-2016 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../../Precompiled.h"
+
+#include "../../Core/Context.h"
+#include "../../Core/Profiler.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/GraphicsEvents.h"
+#include "../../Graphics/GraphicsImpl.h"
+#include "../../Graphics/Renderer.h"
+#include "../../Graphics/Texture2DArray.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
+{
+
+Texture2DArray::Texture2DArray(Context* context) :
+    Texture(context),
+    layers_(0),
+    lockedLevel_(-1)
+{
+}
+
+Texture2DArray::~Texture2DArray()
+{
+    Release();
+}
+
+void Texture2DArray::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Texture2DArray>();
+}
+
+bool Texture2DArray::BeginLoad(Deserializer& source)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    // In headless mode, do not actually load the texture, just return success
+    if (!graphics_)
+        return true;
+
+    cache->ResetDependencies(this);
+
+    String texPath, texName, texExt;
+    SplitPath(GetName(), texPath, texName, texExt);
+
+    loadParameters_ = (new XMLFile(context_));
+    if (!loadParameters_->Load(source))
+    {
+        loadParameters_.Reset();
+        return false;
+    }
+
+    loadImages_.Clear();
+
+    XMLElement textureElem = loadParameters_->GetRoot();
+    XMLElement layerElem = textureElem.GetChild("layer");
+    while (layerElem)
+    {
+        String name = layerElem.GetAttribute("name");
+
+        // If path is empty, add the XML file path
+        if (GetPath(name).Empty())
+            name = texPath + name;
+
+        loadImages_.Push(cache->GetTempResource<Image>(name));
+        cache->StoreResourceDependency(this, name);
+
+        layerElem = layerElem.GetNext("layer");
+    }
+
+    // Precalculate mip levels if async loading
+    if (GetAsyncLoadState() == ASYNC_LOADING)
+    {
+        for (unsigned i = 0; i < loadImages_.Size(); ++i)
+        {
+            if (loadImages_[i])
+                loadImages_[i]->PrecalculateLevels();
+        }
+    }
+
+    return true;
+}
+
+bool Texture2DArray::EndLoad()
+{
+    // In headless mode, do not actually load the texture, just return success
+    if (!graphics_)
+        return true;
+
+    // If over the texture budget, see if materials can be freed to allow textures to be freed
+    CheckTextureBudget(GetTypeStatic());
+
+    SetParameters(loadParameters_);
+    SetLayers(loadImages_.Size());
+
+    for (unsigned i = 0; i < loadImages_.Size(); ++i)
+        SetData(i, loadImages_[i]);
+
+    loadImages_.Clear();
+    loadParameters_.Reset();
+
+    return true;
+}
+
+void Texture2DArray::Release()
+{
+    if (graphics_)
+    {
+        for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
+        {
+            if (graphics_->GetTexture(i) == this)
+                graphics_->SetTexture(i, 0);
+        }
+    }
+
+    if (renderSurface_)
+        renderSurface_->Release();
+
+    URHO3D_SAFE_RELEASE(object_);
+    URHO3D_SAFE_RELEASE(shaderResourceView_);
+    URHO3D_SAFE_RELEASE(sampler_);
+}
+
+void Texture2DArray::SetLayers(unsigned layers)
+{
+    Release();
+
+    layers_ = layers;
+}
+
+bool Texture2DArray::SetSize(unsigned layers, int width, int height, unsigned format, TextureUsage usage)
+{
+    if (width <= 0 || height <= 0)
+    {
+        URHO3D_LOGERROR("Zero or negative texture array size");
+        return false;
+    }
+    if (usage == TEXTURE_DEPTHSTENCIL)
+    {
+        URHO3D_LOGERROR("Depth-stencil usage not supported for texture arrays");
+        return false;
+    }
+
+    // Delete the old rendersurfaces if any
+    renderSurface_.Reset();
+
+    usage_ = usage;
+
+    if (usage_ == TEXTURE_RENDERTARGET)
+    {
+        renderSurface_ = new RenderSurface(this);
+
+        // Nearest filtering and mipmaps disabled by default
+        filterMode_ = FILTER_NEAREST;
+        requestedLevels_ = 1;
+    }
+    else if (usage_ == TEXTURE_DYNAMIC)
+        requestedLevels_ = 1;
+
+    if (usage_ == TEXTURE_RENDERTARGET)
+        SubscribeToEvent(E_RENDERSURFACEUPDATE, URHO3D_HANDLER(Texture2DArray, HandleRenderSurfaceUpdate));
+    else
+        UnsubscribeFromEvent(E_RENDERSURFACEUPDATE);
+
+    width_ = width;
+    height_ = height;
+    format_ = format;
+    if (layers)
+        layers_ = layers;
+
+    layerMemoryUse_.Resize(layers_);
+    for (unsigned i = 0; i < layers_; ++i)
+        layerMemoryUse_[i] = 0;
+
+    return Create();
+}
+
+bool Texture2DArray::SetData(unsigned layer, unsigned level, int x, int y, int width, int height, const void* data)
+{
+    URHO3D_PROFILE(SetTextureData);
+
+    if (!object_)
+    {
+        URHO3D_LOGERROR("Texture array not created, can not set data");
+        return false;
+    }
+
+    if (!data)
+    {
+        URHO3D_LOGERROR("Null source for setting data");
+        return false;
+    }
+
+    if (layer >= layers_)
+    {
+        URHO3D_LOGERROR("Illegal layer for setting data");
+        return false;
+    }
+
+    if (level >= levels_)
+    {
+        URHO3D_LOGERROR("Illegal mip level for setting data");
+        return false;
+    }
+
+    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;
+    }
+
+    // If compressed, align the update region on a block
+    if (IsCompressed())
+    {
+        x &= ~3;
+        y &= ~3;
+        width += 3;
+        width &= 0xfffffffc;
+        height += 3;
+        height &= 0xfffffffc;
+    }
+
+    unsigned char* src = (unsigned char*)data;
+    unsigned rowSize = GetRowDataSize(width);
+    unsigned rowStart = GetRowDataSize(x);
+    unsigned subResource = D3D11CalcSubresource(level, layer, levels_);
+
+    if (usage_ == TEXTURE_DYNAMIC)
+    {
+        if (IsCompressed())
+        {
+            height = (height + 3) >> 2;
+            y >>= 2;
+        }
+
+        D3D11_MAPPED_SUBRESOURCE mappedData;
+        mappedData.pData = 0;
+
+        HRESULT hr = graphics_->GetImpl()->GetDeviceContext()->Map((ID3D11Resource*)object_, subResource, D3D11_MAP_WRITE_DISCARD, 0,
+            &mappedData);
+        if (FAILED(hr) || !mappedData.pData)
+        {
+            URHO3D_LOGD3DERROR("Failed to map texture for update", hr);
+            return false;
+        }
+        else
+        {
+            for (int row = 0; row < height; ++row)
+                memcpy((unsigned char*)mappedData.pData + (row + y) * mappedData.RowPitch + rowStart, src + row * rowSize, rowSize);
+            graphics_->GetImpl()->GetDeviceContext()->Unmap((ID3D11Resource*)object_, subResource);
+        }
+    }
+    else
+    {
+        D3D11_BOX destBox;
+        destBox.left = (UINT)x;
+        destBox.right = (UINT)(x + width);
+        destBox.top = (UINT)y;
+        destBox.bottom = (UINT)(y + height);
+        destBox.front = 0;
+        destBox.back = 1;
+
+        graphics_->GetImpl()->GetDeviceContext()->UpdateSubresource((ID3D11Resource*)object_, subResource, &destBox, data,
+            rowSize, 0);
+    }
+
+    return true;
+}
+
+bool Texture2DArray::SetData(unsigned layer, Deserializer& source)
+{
+    SharedPtr<Image> image(new Image(context_));
+    if (!image->Load(source))
+        return false;
+
+    return SetData(layer, image);
+}
+
+bool Texture2DArray::SetData(unsigned layer, SharedPtr<Image> image, bool useAlpha)
+{
+    if (!image)
+    {
+        URHO3D_LOGERROR("Null image, can not set data");
+        return false;
+    }
+    if (!layers_)
+    {
+        URHO3D_LOGERROR("Number of layers in the array must be set first");
+        return false;
+    }
+    if (layer >= layers_)
+    {
+        URHO3D_LOGERROR("Illegal layer for setting data");
+        return false;
+    }
+
+    unsigned memoryUse = 0;
+
+    int quality = QUALITY_HIGH;
+    Renderer* renderer = GetSubsystem<Renderer>();
+    if (renderer)
+        quality = renderer->GetTextureQuality();
+
+    if (!image->IsCompressed())
+    {
+        // Convert unsuitable formats to RGBA
+        unsigned components = image->GetComponents();
+        if ((components == 1 && !useAlpha) || components == 2 || components == 3)
+        {
+            image = image->ConvertToRGBA();
+            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)
+        {
+            image = image->GetNextLevel();
+            levelData = image->GetData();
+            levelWidth = image->GetWidth();
+            levelHeight = image->GetHeight();
+        }
+
+        switch (components)
+        {
+        case 1:
+            format = Graphics::GetAlphaFormat();
+            break;
+
+        case 4:
+            format = Graphics::GetRGBAFormat();
+            break;
+
+        default: break;
+        }
+
+        // Create the texture array when layer 0 is being loaded, check that rest of the layers are same size & format
+        if (!layer)
+        {
+            // If image was previously compressed, reset number of requested levels to avoid error if level count is too high for new size
+            if (IsCompressed() && requestedLevels_ > 1)
+                requestedLevels_ = 0;
+            // Create the texture array (the number of layers must have been already set)
+            SetSize(0, levelWidth, levelHeight, format);
+        }
+        else
+        {
+            if (!object_)
+            {
+                URHO3D_LOGERROR("Texture array layer 0 must be loaded first");
+                return false;
+            }
+            if (levelWidth != width_ || levelHeight != height_ || format != format_)
+            {
+                URHO3D_LOGERROR("Texture array layer does not match size or format of layer 0");
+                return false;
+            }
+        }
+
+        for (unsigned i = 0; i < levels_; ++i)
+        {
+            SetData(layer, i, 0, 0, levelWidth, levelHeight, levelData);
+            memoryUse += levelWidth * levelHeight * components;
+
+            if (i < levels_ - 1)
+            {
+                image = image->GetNextLevel();
+                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 / (1 << mipsToSkip) < 4 || height / (1 << mipsToSkip) < 4))
+            --mipsToSkip;
+        width /= (1 << mipsToSkip);
+        height /= (1 << mipsToSkip);
+
+        // Create the texture array when layer 0 is being loaded, assume rest of the layers are same size & format
+        if (!layer)
+        {
+            SetNumLevels(Max((levels - mipsToSkip), 1U));
+            SetSize(0, width, height, format);
+        }
+        else
+        {
+            if (!object_)
+            {
+                URHO3D_LOGERROR("Texture array layer 0 must be loaded first");
+                return false;
+            }
+            if (width != width_ || height != height_ || format != format_)
+            {
+                URHO3D_LOGERROR("Texture array layer does not match size or format of layer 0");
+                return false;
+            }
+        }
+
+        for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
+        {
+            CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
+            if (!needDecompress)
+            {
+                SetData(layer, i, 0, 0, level.width_, level.height_, level.data_);
+                memoryUse += level.rows_ * level.rowSize_;
+            }
+            else
+            {
+                unsigned char* rgbaData = new unsigned char[level.width_ * level.height_ * 4];
+                level.Decompress(rgbaData);
+                SetData(layer, i, 0, 0, level.width_, level.height_, rgbaData);
+                memoryUse += level.width_ * level.height_ * 4;
+                delete[] rgbaData;
+            }
+        }
+    }
+
+    layerMemoryUse_[layer] = memoryUse;
+    unsigned totalMemoryUse = sizeof(Texture2DArray) + layerMemoryUse_.Capacity() * sizeof(unsigned);
+    for (unsigned i = 0; i < layers_; ++i)
+        totalMemoryUse += layerMemoryUse_[i];
+    SetMemoryUse(totalMemoryUse);
+
+    return true;
+}
+
+bool Texture2DArray::GetData(unsigned layer, unsigned level, void* dest) const
+{
+    if (!object_)
+    {
+        URHO3D_LOGERROR("Texture array not created, can not get data");
+        return false;
+    }
+
+    if (!dest)
+    {
+        URHO3D_LOGERROR("Null destination for getting data");
+        return false;
+    }
+
+    if (layer >= layers_)
+    {
+        URHO3D_LOGERROR("Illegal layer for getting data");
+        return false;
+    }
+
+    if (level >= levels_)
+    {
+        URHO3D_LOGERROR("Illegal mip level for getting data");
+        return false;
+    }
+
+    int levelWidth = GetLevelWidth(level);
+    int levelHeight = GetLevelHeight(level);
+
+    D3D11_TEXTURE2D_DESC textureDesc;
+    memset(&textureDesc, 0, sizeof textureDesc);
+    textureDesc.Width = (UINT)levelWidth;
+    textureDesc.Height = (UINT)levelHeight;
+    textureDesc.MipLevels = 1;
+    textureDesc.ArraySize = 1;
+    textureDesc.Format = (DXGI_FORMAT)format_;
+    textureDesc.SampleDesc.Count = 1;
+    textureDesc.SampleDesc.Quality = 0;
+    textureDesc.Usage = D3D11_USAGE_STAGING;
+    textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+
+    ID3D11Texture2D* stagingTexture = 0;
+    HRESULT hr = graphics_->GetImpl()->GetDevice()->CreateTexture2D(&textureDesc, 0, &stagingTexture);
+    if (FAILED(hr))
+    {
+        URHO3D_SAFE_RELEASE(stagingTexture);
+        URHO3D_LOGD3DERROR("Failed to create staging texture for GetData", hr);
+        return false;
+    }
+
+    unsigned srcSubResource = D3D11CalcSubresource(level, layer, levels_);
+    D3D11_BOX srcBox;
+    srcBox.left = 0;
+    srcBox.right = (UINT)levelWidth;
+    srcBox.top = 0;
+    srcBox.bottom = (UINT)levelHeight;
+    srcBox.front = 0;
+    srcBox.back = 1;
+    graphics_->GetImpl()->GetDeviceContext()->CopySubresourceRegion(stagingTexture, 0, 0, 0, 0, (ID3D11Resource*)object_,
+        srcSubResource, &srcBox);
+
+    D3D11_MAPPED_SUBRESOURCE mappedData;
+    mappedData.pData = 0;
+    unsigned rowSize = GetRowDataSize(levelWidth);
+    unsigned numRows = (unsigned)(IsCompressed() ? (levelHeight + 3) >> 2 : levelHeight);
+
+    hr = graphics_->GetImpl()->GetDeviceContext()->Map((ID3D11Resource*)stagingTexture, 0, D3D11_MAP_READ, 0, &mappedData);
+    if (FAILED(hr) || !mappedData.pData)
+    {
+        URHO3D_LOGD3DERROR("Failed to map staging texture for GetData", hr);
+        stagingTexture->Release();
+        return false;
+    }
+    else
+    {
+        for (unsigned row = 0; row < numRows; ++row)
+            memcpy((unsigned char*)dest + row * rowSize, (unsigned char*)mappedData.pData + row * mappedData.RowPitch, rowSize);
+        graphics_->GetImpl()->GetDeviceContext()->Unmap((ID3D11Resource*)stagingTexture, 0);
+        stagingTexture->Release();
+        return true;
+    }
+}
+
+bool Texture2DArray::Create()
+{
+    Release();
+
+    if (!graphics_ || !width_ || !height_ || !layers_)
+        return false;
+
+    levels_ = CheckMaxLevels(width_, height_, requestedLevels_);
+
+    D3D11_TEXTURE2D_DESC textureDesc;
+    memset(&textureDesc, 0, sizeof textureDesc);
+    textureDesc.Width = (UINT)width_;
+    textureDesc.Height = (UINT)height_;
+    textureDesc.MipLevels = levels_;
+    textureDesc.ArraySize = layers_;
+    textureDesc.Format = (DXGI_FORMAT)(sRGB_ ? GetSRGBFormat(format_) : format_);
+    textureDesc.SampleDesc.Count = 1;
+    textureDesc.SampleDesc.Quality = 0;
+    textureDesc.Usage = usage_ == TEXTURE_DYNAMIC ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT;
+    textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
+    if (usage_ == TEXTURE_RENDERTARGET)
+        textureDesc.BindFlags |= D3D11_BIND_RENDER_TARGET;
+    else if (usage_ == TEXTURE_DEPTHSTENCIL)
+        textureDesc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
+    textureDesc.CPUAccessFlags = usage_ == TEXTURE_DYNAMIC ? D3D11_CPU_ACCESS_WRITE : 0;
+
+    HRESULT hr = graphics_->GetImpl()->GetDevice()->CreateTexture2D(&textureDesc, 0, (ID3D11Texture2D**)&object_);
+    if (FAILED(hr))
+    {
+        URHO3D_SAFE_RELEASE(object_);
+        URHO3D_LOGD3DERROR("Failed to create texture array", hr);
+        return false;
+    }
+
+    D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
+    memset(&srvDesc, 0, sizeof srvDesc);
+    srvDesc.Format = (DXGI_FORMAT)GetSRVFormat(textureDesc.Format);
+    if (layers_ == 1)
+    {
+        srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+        srvDesc.Texture2D.MipLevels = (UINT)levels_;
+        srvDesc.Texture2D.MostDetailedMip = 0;
+    }
+    else
+    {
+        srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
+        srvDesc.Texture2DArray.MipLevels = (UINT)levels_;
+        srvDesc.Texture2DArray.ArraySize = layers_;
+        srvDesc.Texture2DArray.FirstArraySlice = 0;
+        srvDesc.Texture2DArray.MostDetailedMip = 0;
+    }
+
+    hr = graphics_->GetImpl()->GetDevice()->CreateShaderResourceView((ID3D11Resource*)object_, &srvDesc,
+        (ID3D11ShaderResourceView**)&shaderResourceView_);
+    if (FAILED(hr))
+    {
+        URHO3D_SAFE_RELEASE(shaderResourceView_);
+        URHO3D_LOGD3DERROR("Failed to create shader resource view for texture array", hr);
+        return false;
+    }
+
+    if (usage_ == TEXTURE_RENDERTARGET)
+    {
+        D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
+        memset(&renderTargetViewDesc, 0, sizeof renderTargetViewDesc);
+        renderTargetViewDesc.Format = textureDesc.Format;
+        if (layers_ == 1)
+        {
+            renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+            renderTargetViewDesc.Texture2D.MipSlice = 0;
+        }
+        else
+        {
+            renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
+            renderTargetViewDesc.Texture2DArray.MipSlice = 0;
+            renderTargetViewDesc.Texture2DArray.ArraySize = layers_;
+            renderTargetViewDesc.Texture2DArray.FirstArraySlice = 0;
+        }
+
+        hr = graphics_->GetImpl()->GetDevice()->CreateRenderTargetView((ID3D11Resource*)object_, &renderTargetViewDesc,
+            (ID3D11RenderTargetView**)&renderSurface_->renderTargetView_);
+
+        if (FAILED(hr))
+        {
+            URHO3D_SAFE_RELEASE(renderSurface_->renderTargetView_);
+            URHO3D_LOGD3DERROR("Failed to create rendertarget view for texture array", hr);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void Texture2DArray::HandleRenderSurfaceUpdate(StringHash eventType, VariantMap& eventData)
+{
+    if (renderSurface_ && (renderSurface_->GetUpdateMode() == SURFACE_UPDATEALWAYS || renderSurface_->IsUpdateQueued()))
+    {
+        Renderer* renderer = GetSubsystem<Renderer>();
+        if (renderer)
+            renderer->QueueRenderSurface(renderSurface_);
+        renderSurface_->ResetUpdateQueued();
+    }
+}
+
+}

+ 95 - 0
Source/Urho3D/Graphics/Direct3D11/D3D11Texture2DArray.h

@@ -0,0 +1,95 @@
+//
+// Copyright (c) 2008-2016 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../../Container/Ptr.h"
+#include "../../Graphics/RenderSurface.h"
+#include "../../Graphics/Texture.h"
+
+namespace Urho3D
+{
+
+class Deserializer;
+class Image;
+
+/// 2D texture array resource.
+class URHO3D_API Texture2DArray : public Texture
+{
+    URHO3D_OBJECT(Texture2DArray, Texture)
+
+public:
+    /// Construct.
+    Texture2DArray(Context* context);
+    /// Destruct.
+    virtual ~Texture2DArray();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Load resource from stream. May be called from a worker thread. Return true if successful.
+    virtual bool BeginLoad(Deserializer& source);
+    /// Finish resource loading. Always called from the main thread. Return true if successful.
+    virtual bool EndLoad();
+    /// Release texture.
+    virtual void Release();
+
+    /// Set the number of layers in the texture. To be used before SetData.
+    void SetLayers(unsigned layers);
+    /// Set layers, size, format and usage. Set layers to zero to leave them unchanged. Return true if successful.
+    bool SetSize(unsigned layers, int width, int height, unsigned format, TextureUsage usage = TEXTURE_STATIC);
+    /// Set data either partially or fully on a face's mip level. Return true if successful.
+    bool SetData(unsigned layer, unsigned level, int x, int y, int width, int height, const void* data);
+    /// Set data of one face from a stream. Return true if successful.
+    bool SetData(unsigned layer, Deserializer& source);
+    /// Set data of one face from an image. Return true if successful. Optionally make a single channel image alpha-only.
+    bool SetData(unsigned layer, SharedPtr<Image> image, bool useAlpha = false);
+
+    /// Return number of layers in the texture.
+    unsigned GetLayers() const { return layers_; }
+    /// Get data from a layer's mip level. The destination buffer must be big enough. Return true if successful.
+    bool GetData(unsigned layer, unsigned level, void* dest) const;
+    /// Return render surface.
+    RenderSurface* GetRenderSurface() const { return renderSurface_; }
+
+private:
+    /// Create texture array.
+    bool Create();
+    /// Handle render surface update event.
+    void HandleRenderSurfaceUpdate(StringHash eventType, VariantMap& eventData);
+
+    /// Texture array layers number.
+    unsigned layers_;
+    /// Render surfaces.
+    SharedPtr<RenderSurface> renderSurface_;
+    /// Memory use per layer.
+    PODVector<unsigned> layerMemoryUse_;
+    /// Currently locked mip level.
+    int lockedLevel_;
+    /// Currently locked layer.
+    int lockedLayer_;
+    /// Layer image files acquired during BeginLoad.
+    Vector<SharedPtr<Image> > loadImages_;
+    /// Parameter file acquired during BeginLoad.
+    SharedPtr<XMLFile> loadParameters_;
+};
+
+}

+ 75 - 20
Source/Urho3D/Graphics/Material.cpp

@@ -29,6 +29,7 @@
 #include "../Graphics/Material.h"
 #include "../Graphics/Technique.h"
 #include "../Graphics/Texture2D.h"
+#include "../Graphics/Texture2DArray.h"
 #include "../Graphics/Texture3D.h"
 #include "../Graphics/TextureCube.h"
 #include "../IO/FileSystem.h"
@@ -121,6 +122,38 @@ TextureUnit ParseTextureUnitName(String name)
     return unit;
 }
 
+StringHash ParseTextureTypeName(String name)
+{
+    name = name.ToLower().Trimmed();
+
+    if (name == "texture")
+        return Texture2D::GetTypeStatic();
+    else if (name == "cubemap")
+        return TextureCube::GetTypeStatic();
+    else if (name == "texture3d")
+        return Texture3D::GetTypeStatic();
+    else if (name == "texturearray")
+        return Texture2DArray::GetTypeStatic();
+
+    return 0;
+}
+
+StringHash ParseTextureTypeXml(ResourceCache* cache, String filename)
+{
+    StringHash type = 0;
+    if (!cache)
+        return type;
+
+    SharedPtr<File> texXmlFile = cache->GetFile(filename, false);
+    if (texXmlFile.NotNull())
+    {
+        SharedPtr<XMLFile> texXml(new XMLFile(cache->GetContext()));
+        if (texXml->Load(*texXmlFile))
+            type = ParseTextureTypeName(texXml->GetRoot().GetName());
+    }
+    return type;
+}
+
 static TechniqueEntry noEntry;
 
 bool CompareTechniqueEntries(const TechniqueEntry& lhs, const TechniqueEntry& rhs)
@@ -275,16 +308,22 @@ bool Material::BeginLoadXML(Deserializer& source)
             while (textureElem)
             {
                 String name = textureElem.GetAttribute("name");
-                // Detect cube maps by file extension: they are defined by an XML file
-                /// \todo Differentiate with 3D textures by actually reading the XML content
+                // Detect cube maps and arrays by file extension: they are defined by an XML file
                 if (GetExtension(name) == ".xml")
                 {
 #ifdef DESKTOP_GRAPHICS
-                    TextureUnit unit = TU_DIFFUSE;
-                    if (textureElem.HasAttribute("unit"))
-                        unit = ParseTextureUnitName(textureElem.GetAttribute("unit"));
-                    if (unit == TU_VOLUMEMAP)
+                    StringHash type = ParseTextureTypeXml(cache, name);
+                    if (!type && textureElem.HasAttribute("unit"))
+                    {
+                        TextureUnit unit = ParseTextureUnitName(textureElem.GetAttribute("unit"));
+                        if (unit == TU_VOLUMEMAP)
+                            type = Texture3D::GetTypeStatic();
+                    }
+
+                    if (type == Texture3D::GetTypeStatic())
                         cache->BackgroundLoadResource<Texture3D>(name, true, this);
+                    else if (type == Texture2DArray::GetTypeStatic())
+                        cache->BackgroundLoadResource<Texture2DArray>(name, true, this);
                     else
 #endif
                         cache->BackgroundLoadResource<TextureCube>(name, true, this);
@@ -330,18 +369,24 @@ bool Material::BeginLoadJSON(Deserializer& source)
             {
                 String unitString = it->first_;
                 String name = it->second_.GetString();
-                // Detect cube maps by file extension: they are defined by an XML file
-                /// \todo Differentiate with 3D textures by actually reading the XML content
+                // Detect cube maps and arrays by file extension: they are defined by an XML file
                 if (GetExtension(name) == ".xml")
                 {
-    #ifdef DESKTOP_GRAPHICS
-                    TextureUnit unit = TU_DIFFUSE;
-                    unit = ParseTextureUnitName(unitString);
-
-                    if (unit == TU_VOLUMEMAP)
+#ifdef DESKTOP_GRAPHICS
+                    StringHash type = ParseTextureTypeXml(cache, name);
+                    if (!type && !unitString.Empty())
+                    {
+                        TextureUnit unit = ParseTextureUnitName(unitString);
+                        if (unit == TU_VOLUMEMAP)
+                            type = Texture3D::GetTypeStatic();
+                    }
+
+                    if (type == Texture3D::GetTypeStatic())
                         cache->BackgroundLoadResource<Texture3D>(name, true, this);
+                    else if (type == Texture2DArray::GetTypeStatic())
+                        cache->BackgroundLoadResource<Texture2DArray>(name, true, this);
                     else
-    #endif
+#endif
                         cache->BackgroundLoadResource<TextureCube>(name, true, this);
                 }
                 else
@@ -408,13 +453,18 @@ bool Material::Load(const XMLElement& source)
         if (unit < MAX_TEXTURE_UNITS)
         {
             String name = textureElem.GetAttribute("name");
-            // Detect cube maps by file extension: they are defined by an XML file
-            /// \todo Differentiate with 3D textures by actually reading the XML content
+            // Detect cube maps and arrays by file extension: they are defined by an XML file
             if (GetExtension(name) == ".xml")
             {
 #ifdef DESKTOP_GRAPHICS
-                if (unit == TU_VOLUMEMAP)
+                StringHash type = ParseTextureTypeXml(cache, name);
+                if (!type && unit == TU_VOLUMEMAP)
+                    type = Texture3D::GetTypeStatic();
+
+                if (type == Texture3D::GetTypeStatic())
                     SetTexture(unit, cache->GetResource<Texture3D>(name));
+                else if (type == Texture2DArray::GetTypeStatic())
+                    SetTexture(unit, cache->GetResource<Texture2DArray>(name));
                 else
 #endif
                     SetTexture(unit, cache->GetResource<TextureCube>(name));
@@ -541,13 +591,18 @@ bool Material::Load(const JSONValue& source)
 
         if (unit < MAX_TEXTURE_UNITS)
         {
-            // Detect cube maps by file extension: they are defined by an XML file
-            /// \todo Differentiate with 3D textures by actually reading the XML content
+            // Detect cube maps and arrays by file extension: they are defined by an XML file
             if (GetExtension(textureName) == ".xml")
             {
 #ifdef DESKTOP_GRAPHICS
-                if (unit == TU_VOLUMEMAP)
+                StringHash type = ParseTextureTypeXml(cache, textureName);
+                if (!type && unit == TU_VOLUMEMAP)
+                    type = Texture3D::GetTypeStatic();
+
+                if (type == Texture3D::GetTypeStatic())
                     SetTexture(unit, cache->GetResource<Texture3D>(textureName));
+                else if (type == Texture2DArray::GetTypeStatic())
+                    SetTexture(unit, cache->GetResource<Texture2DArray>(textureName));
                 else
 #endif
                     SetTexture(unit, cache->GetResource<TextureCube>(textureName));

+ 2 - 0
Source/Urho3D/Graphics/OpenGL/OGLGraphics.cpp

@@ -54,6 +54,7 @@
 #include "../../Graphics/Terrain.h"
 #include "../../Graphics/TerrainPatch.h"
 #include "../../Graphics/Texture2D.h"
+#include "../../Graphics/Texture2DArray.h"
 #include "../../Graphics/Texture3D.h"
 #include "../../Graphics/TextureCube.h"
 #include "../../Graphics/VertexBuffer.h"
@@ -3300,6 +3301,7 @@ void RegisterGraphicsLibrary(Context* context)
     Shader::RegisterObject(context);
     Technique::RegisterObject(context);
     Texture2D::RegisterObject(context);
+    Texture2DArray::RegisterObject(context);
     Texture3D::RegisterObject(context);
     TextureCube::RegisterObject(context);
     Camera::RegisterObject(context);

+ 1 - 0
Source/Urho3D/Graphics/OpenGL/OGLGraphics.h

@@ -47,6 +47,7 @@ class ShaderProgram;
 class ShaderVariation;
 class Texture;
 class Texture2D;
+class Texture2DArray;
 class TextureCube;
 class Vector3;
 class Vector4;

+ 635 - 0
Source/Urho3D/Graphics/OpenGL/OGLTexture2DArray.cpp

@@ -0,0 +1,635 @@
+//
+// Copyright (c) 2008-2016 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../../Precompiled.h"
+
+#include "../../Core/Context.h"
+#include "../../Core/Profiler.h"
+#include "../../Graphics/Graphics.h"
+#include "../../Graphics/GraphicsEvents.h"
+#include "../../Graphics/GraphicsImpl.h"
+#include "../../Graphics/Renderer.h"
+#include "../../Graphics/Texture2DArray.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
+{
+
+Texture2DArray::Texture2DArray(Context* context) :
+    Texture(context),
+    layers_(0)
+{
+#ifndef GL_ES_VERSION_2_0
+    target_ = GL_TEXTURE_2D_ARRAY;
+#else
+    target_ = 0;
+#endif
+}
+
+Texture2DArray::~Texture2DArray()
+{
+    Release();
+}
+
+void Texture2DArray::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Texture2DArray>();
+}
+
+bool Texture2DArray::BeginLoad(Deserializer& source)
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+
+    // In headless mode, do not actually load the texture, just return success
+    if (!graphics_)
+        return true;
+
+    // If device is lost, retry later
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Texture load while device is lost");
+        dataPending_ = true;
+        return true;
+    }
+
+    cache->ResetDependencies(this);
+
+    String texPath, texName, texExt;
+    SplitPath(GetName(), texPath, texName, texExt);
+
+    loadParameters_ = (new XMLFile(context_));
+    if (!loadParameters_->Load(source))
+    {
+        loadParameters_.Reset();
+        return false;
+    }
+
+    loadImages_.Clear();
+
+    XMLElement textureElem = loadParameters_->GetRoot();
+    XMLElement layerElem = textureElem.GetChild("layer");
+    while (layerElem)
+    {
+        String name = layerElem.GetAttribute("name");
+
+        // If path is empty, add the XML file path
+        if (GetPath(name).Empty())
+            name = texPath + name;
+
+        loadImages_.Push(cache->GetTempResource<Image>(name));
+        cache->StoreResourceDependency(this, name);
+
+        layerElem = layerElem.GetNext("layer");
+    }
+
+    // Precalculate mip levels if async loading
+    if (GetAsyncLoadState() == ASYNC_LOADING)
+    {
+        for (unsigned i = 0; i < loadImages_.Size(); ++i)
+        {
+            if (loadImages_[i])
+                loadImages_[i]->PrecalculateLevels();
+        }
+    }
+
+    return true;
+}
+
+bool Texture2DArray::EndLoad()
+{
+    // In headless mode, do not actually load the texture, just return success
+    if (!graphics_ || graphics_->IsDeviceLost())
+        return true;
+
+    // If over the texture budget, see if materials can be freed to allow textures to be freed
+    CheckTextureBudget(GetTypeStatic());
+
+    SetParameters(loadParameters_);
+    SetLayers(loadImages_.Size());
+
+    for (unsigned i = 0; i < loadImages_.Size(); ++i)
+        SetData(i, loadImages_[i]);
+
+    loadImages_.Clear();
+    loadParameters_.Reset();
+
+    return true;
+}
+
+void Texture2DArray::OnDeviceLost()
+{
+    GPUObject::OnDeviceLost();
+
+    if (renderSurface_)
+        renderSurface_->OnDeviceLost();
+}
+
+void Texture2DArray::OnDeviceReset()
+{
+    if (!object_ || dataPending_)
+    {
+        // If has a resource file, reload through the resource cache. Otherwise just recreate.
+        ResourceCache* cache = GetSubsystem<ResourceCache>();
+        if (cache->Exists(GetName()))
+            dataLost_ = !cache->ReloadResource(this);
+
+        if (!object_)
+        {
+            Create();
+            dataLost_ = true;
+        }
+    }
+
+    dataPending_ = false;
+}
+
+void Texture2DArray::Release()
+{
+    if (object_)
+    {
+        if (!graphics_)
+            return;
+
+        if (!graphics_->IsDeviceLost())
+        {
+            for (unsigned i = 0; i < MAX_TEXTURE_UNITS; ++i)
+            {
+                if (graphics_->GetTexture(i) == this)
+                    graphics_->SetTexture(i, 0);
+            }
+
+            glDeleteTextures(1, &object_);
+        }
+
+        if (renderSurface_)
+            renderSurface_->Release();
+
+        object_ = 0;
+    }
+}
+
+void Texture2DArray::SetLayers(unsigned layers)
+{
+    Release();
+
+    layers_ = layers;
+}
+
+bool Texture2DArray::SetSize(unsigned layers, int width, int height, unsigned format, TextureUsage usage)
+{
+    if (width <= 0 || height <= 0)
+    {
+        URHO3D_LOGERROR("Zero or negative texture array size");
+        return false;
+    }
+    if (usage == TEXTURE_DEPTHSTENCIL)
+    {
+        URHO3D_LOGERROR("Depth-stencil usage not supported for texture arrays");
+        return false;
+    }
+
+    // Delete the old rendersurface if any
+    renderSurface_.Reset();
+
+    usage_ = usage;
+
+    if (usage == TEXTURE_RENDERTARGET)
+    {
+        renderSurface_ = new RenderSurface(this);
+
+        // Nearest filtering and mipmaps disabled by default
+        filterMode_ = FILTER_NEAREST;
+        requestedLevels_ = 1;
+    }
+
+    if (usage == TEXTURE_RENDERTARGET)
+        SubscribeToEvent(E_RENDERSURFACEUPDATE, URHO3D_HANDLER(Texture2DArray, HandleRenderSurfaceUpdate));
+    else
+        UnsubscribeFromEvent(E_RENDERSURFACEUPDATE);
+
+    width_ = width;
+    height_ = height;
+    format_ = format;
+    if (layers)
+        layers_ = layers;
+
+    layerMemoryUse_.Resize(layers_);
+    for (unsigned i = 0; i < layers_; ++i)
+        layerMemoryUse_[i] = 0;
+
+    return Create();
+}
+
+bool Texture2DArray::SetData(unsigned layer, unsigned level, int x, int y, int width, int height, const void* data)
+{
+    URHO3D_PROFILE(SetTextureData);
+
+    if (!object_ || !graphics_)
+    {
+        URHO3D_LOGERROR("Texture array not created, can not set data");
+        return false;
+    }
+
+    if (!data)
+    {
+        URHO3D_LOGERROR("Null source for setting data");
+        return false;
+    }
+
+    if (layer >= layers_)
+    {
+        URHO3D_LOGERROR("Illegal layer for setting data");
+        return false;
+    }
+
+    if (level >= levels_)
+    {
+        URHO3D_LOGERROR("Illegal mip level for setting data");
+        return false;
+    }
+
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Texture array data assignment while device is lost");
+        dataPending_ = true;
+        return true;
+    }
+
+    if (IsCompressed())
+    {
+        x &= ~3;
+        y &= ~3;
+    }
+
+    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(this);
+
+#ifndef GL_ES_VERSION_2_0
+    bool wholeLevel = x == 0 && y == 0 && width == levelWidth && height == levelHeight && layer == 0;
+    unsigned format = GetSRGB() ? GetSRGBFormat(format_) : format_;
+
+    if (!IsCompressed())
+    {
+        if (wholeLevel)
+            glTexImage3D(target_, level, format, width, height, layers_, 0, GetExternalFormat(format_),
+                GetDataType(format_), 0);
+        glTexSubImage3D(target_, level, x, y, layer, width, height, 1, GetExternalFormat(format_),
+            GetDataType(format_), data);
+    }
+    else
+    {
+        if (wholeLevel)
+            glCompressedTexImage3D(target_, level, format, width, height, layers_, 0,
+                GetDataSize(width, height, layers_), 0);
+        glCompressedTexSubImage3D(target_, level, x, y, layer, width, height, 1, format,
+            GetDataSize(width, height), data);
+    }
+#endif
+
+    graphics_->SetTexture(0, 0);
+    return true;
+}
+
+bool Texture2DArray::SetData(unsigned layer, Deserializer& source)
+{
+    SharedPtr<Image> image(new Image(context_));
+    if (!image->Load(source))
+        return false;
+
+    return SetData(layer, image);
+}
+
+bool Texture2DArray::SetData(unsigned layer, SharedPtr<Image> image, bool useAlpha)
+{
+    if (!image)
+    {
+        URHO3D_LOGERROR("Null image, can not set data");
+        return false;
+    }
+    if (!layers_)
+    {
+        URHO3D_LOGERROR("Number of layers in the array must be set first");
+        return false;
+    }
+    if (layer >= layers_)
+    {
+        URHO3D_LOGERROR("Illegal layer for setting data");
+        return false;
+    }
+
+    unsigned memoryUse = 0;
+
+    int quality = QUALITY_HIGH;
+    Renderer* 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))
+        {
+            image = image->ConvertToRGBA();
+            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)
+        {
+            image = image->GetNextLevel();
+            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 array when layer 0 is being loaded, check that rest of the layers are same size & format
+        if (!layer)
+        {
+            // If image was previously compressed, reset number of requested levels to avoid error if level count is too high for new size
+            if (IsCompressed() && requestedLevels_ > 1)
+                requestedLevels_ = 0;
+            // Create the texture array (the number of layers must have been already set)
+            SetSize(0, levelWidth, levelHeight, format);
+        }
+        else
+        {
+            if (!object_)
+            {
+                URHO3D_LOGERROR("Texture array layer 0 must be loaded first");
+                return false;
+            }
+            if (levelWidth != width_ || levelHeight != height_ || format != format_)
+            {
+                URHO3D_LOGERROR("Texture array layer does not match size or format of layer 0");
+                return false;
+            }
+        }
+
+        for (unsigned i = 0; i < levels_; ++i)
+        {
+            SetData(layer, i, 0, 0, levelWidth, levelHeight, levelData);
+            memoryUse += levelWidth * levelHeight * components;
+
+            if (i < levels_ - 1)
+            {
+                image = image->GetNextLevel();
+                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 / (1 << mipsToSkip) < 4 || height / (1 << mipsToSkip) < 4))
+            --mipsToSkip;
+        width /= (1 << mipsToSkip);
+        height /= (1 << mipsToSkip);
+
+        // Create the texture array when layer 0 is being loaded, assume rest of the layers are same size & format
+        if (!layer)
+        {
+            SetNumLevels(Max((levels - mipsToSkip), 1U));
+            SetSize(0, width, height, format);
+        }
+        else
+        {
+            if (!object_)
+            {
+                URHO3D_LOGERROR("Texture array layer 0 must be loaded first");
+                return false;
+            }
+            if (width != width_ || height != height_ || format != format_)
+            {
+                URHO3D_LOGERROR("Texture array layer does not match size or format of layer 0");
+                return false;
+            }
+        }
+
+        for (unsigned i = 0; i < levels_ && i < levels - mipsToSkip; ++i)
+        {
+            CompressedLevel level = image->GetCompressedLevel(i + mipsToSkip);
+            if (!needDecompress)
+            {
+                SetData(layer, i, 0, 0, level.width_, level.height_, level.data_);
+                memoryUse += level.rows_ * level.rowSize_;
+            }
+            else
+            {
+                unsigned char* rgbaData = new unsigned char[level.width_ * level.height_ * 4];
+                level.Decompress(rgbaData);
+                SetData(layer, i, 0, 0, level.width_, level.height_, rgbaData);
+                memoryUse += level.width_ * level.height_ * 4;
+                delete[] rgbaData;
+            }
+        }
+    }
+
+    layerMemoryUse_[layer] = memoryUse;
+    unsigned totalMemoryUse = sizeof(Texture2DArray) + layerMemoryUse_.Capacity() * sizeof(unsigned);
+    for (unsigned i = 0; i < layers_; ++i)
+        totalMemoryUse += layerMemoryUse_[i];
+    SetMemoryUse(totalMemoryUse);
+
+    return true;
+}
+
+bool Texture2DArray::GetData(unsigned layer, unsigned level, void* dest) const
+{
+#ifndef GL_ES_VERSION_2_0
+    if (!object_ || !graphics_)
+    {
+        URHO3D_LOGERROR("Texture array not created, can not get data");
+        return false;
+    }
+
+    if (!dest)
+    {
+        URHO3D_LOGERROR("Null destination for getting data");
+        return false;
+    }
+
+    if (layer != 0)
+    {
+        URHO3D_LOGERROR("Only the full download of the array is supported, set layer=0");
+        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;
+    }
+
+    graphics_->SetTextureForUpdate(const_cast<Texture2DArray*>(this));
+
+    if (!IsCompressed())
+        glGetTexImage(target_, level, GetExternalFormat(format_), GetDataType(format_), dest);
+    else
+        glGetCompressedTexImage(target_, level, dest);
+
+    graphics_->SetTexture(0, 0);
+    return true;
+#else
+    URHO3D_LOGERROR("Getting texture data not supported");
+    return false;
+#endif
+}
+
+bool Texture2DArray::Create()
+{
+    Release();
+
+    if (!graphics_ || !width_ || !height_ || !layers_)
+        return false;
+
+    if (graphics_->IsDeviceLost())
+    {
+        URHO3D_LOGWARNING("Texture array creation while device is lost");
+        return true;
+    }
+
+    glGenTextures(1, &object_);
+
+    // Ensure that our texture is bound to OpenGL texture unit 0
+    graphics_->SetTextureForUpdate(this);
+
+    unsigned format = GetSRGB() ? GetSRGBFormat(format_) : format_;
+    unsigned externalFormat = GetExternalFormat(format_);
+    unsigned dataType = GetDataType(format_);
+
+    // If not compressed, create the initial level 0 texture with null data
+    bool success = true;
+    if (!IsCompressed())
+    {
+        glGetError();
+        glTexImage3D(target_, 0, format, width_, height_, layers_, 0, externalFormat, dataType, 0);
+        if (glGetError())
+            success = false;
+    }
+    if (!success)
+        URHO3D_LOGERROR("Failed to create texture array");
+
+    // Set mipmapping
+    levels_ = requestedLevels_;
+    if (!levels_)
+    {
+        unsigned maxSize = (unsigned)Max(width_, height_);
+        while (maxSize)
+        {
+            maxSize >>= 1;
+            ++levels_;
+        }
+    }
+
+#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, 0);
+
+    return success;
+}
+
+void Texture2DArray::HandleRenderSurfaceUpdate(StringHash eventType, VariantMap& eventData)
+{
+    if (renderSurface_ && (renderSurface_->GetUpdateMode() == SURFACE_UPDATEALWAYS || renderSurface_->IsUpdateQueued()))
+    {
+        Renderer* renderer = GetSubsystem<Renderer>();
+        if (renderer)
+            renderer->QueueRenderSurface(renderSurface_);
+        renderSurface_->ResetUpdateQueued();
+    }
+}
+
+}

+ 97 - 0
Source/Urho3D/Graphics/OpenGL/OGLTexture2DArray.h

@@ -0,0 +1,97 @@
+//
+// Copyright (c) 2008-2016 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../../Container/Ptr.h"
+#include "../../Graphics/RenderSurface.h"
+#include "../../Graphics/Texture.h"
+
+namespace Urho3D
+{
+
+class Deserializer;
+class Image;
+
+/// 2D texture array resource.
+class URHO3D_API Texture2DArray : public Texture
+{
+    URHO3D_OBJECT(Texture2DArray, Texture)
+
+public:
+    /// Construct.
+    Texture2DArray(Context* context);
+    /// Destruct.
+    virtual ~Texture2DArray();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Load resource from stream. May be called from a worker thread. Return true if successful.
+    virtual bool BeginLoad(Deserializer& source);
+    /// Finish resource loading. Always called from the main thread. Return true if successful.
+    virtual bool EndLoad();
+    /// Mark the GPU resource destroyed on context destruction.
+    virtual void OnDeviceLost();
+    /// Recreate the GPU resource and restore data if applicable.
+    virtual void OnDeviceReset();
+    /// Release the texture.
+    virtual void Release();
+
+    /// Set the number of layers in the texture. To be used before SetData.
+    void SetLayers(unsigned layers);
+    /// Set layers, size, format and usage. Set layers to zero to leave them unchanged. Return true if successful.
+    bool SetSize(unsigned layers, int width, int height, unsigned format, TextureUsage usage = TEXTURE_STATIC);
+    /// Set data either partially or fully on a layer's mip level. Return true if successful.
+    bool SetData(unsigned layer, unsigned level, int x, int y, int width, int height, const void* data);
+    /// Set data of one layer from a stream. Return true if successful.
+    bool SetData(unsigned layer, Deserializer& source);
+    /// Set data of one layer from an image. Return true if successful. Optionally make a single channel image alpha-only.
+    bool SetData(unsigned layer, SharedPtr<Image> image, bool useAlpha = false);
+
+    // Return number of layers in the texture.
+    unsigned GetLayers() const { return layers_; }
+    /// Get data from a mip level. The destination buffer must be big enough. Return true if successful.
+    bool GetData(unsigned layer, unsigned level, void* dest) const;
+    /// Return render surface.
+    RenderSurface* GetRenderSurface() const { return renderSurface_; }
+
+protected:
+    /// Create texture array.
+    virtual bool Create();
+
+private:
+    /// Handle render surface update event.
+    void HandleRenderSurfaceUpdate(StringHash eventType, VariantMap& eventData);
+
+    /// Texture array layers number.
+    unsigned layers_;
+    /// Render surface.
+    SharedPtr<RenderSurface> renderSurface_;
+    /// Memory use per layer.
+    PODVector<unsigned> layerMemoryUse_;
+    /// Layer image files acquired during BeginLoad.
+    Vector<SharedPtr<Image> > loadImages_;
+    /// Parameter file acquired during BeginLoad.
+    SharedPtr<XMLFile> loadParameters_;
+};
+
+}

+ 29 - 0
Source/Urho3D/Graphics/Texture2DArray.h

@@ -0,0 +1,29 @@
+//
+// Copyright (c) 2008-2016 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#if defined(URHO3D_OPENGL)
+#include "OpenGL/OGLTexture2DArray.h"
+#elif defined(URHO3D_D3D11)
+#include "Direct3D11/D3D11Texture2DArray.h"
+#endif

+ 15 - 4
Source/Urho3D/Graphics/View.cpp

@@ -39,6 +39,7 @@
 #include "../Graphics/Skybox.h"
 #include "../Graphics/Technique.h"
 #include "../Graphics/Texture2D.h"
+#include "../Graphics/Texture2DArray.h"
 #include "../Graphics/Texture3D.h"
 #include "../Graphics/TextureCube.h"
 #include "../Graphics/VertexBuffer.h"
@@ -286,6 +287,8 @@ void SortShadowQueueWork(const WorkItem* item, unsigned threadIndex)
         start->shadowSplits_[i].shadowBatches_.SortFrontToBack();
 }
 
+StringHash ParseTextureTypeXml(ResourceCache* cache, String filename);
+
 View::View(Context* context) :
     Object(context),
     graphics_(GetSubsystem<Graphics>()),
@@ -1731,7 +1734,7 @@ void View::SetRenderTargets(RenderPathCommand& command)
         }
     }
 
-    // When rendering to the final destination rendertarget, use the actual viewport. Otherwise texture rendertargets should use 
+    // When rendering to the final destination rendertarget, use the actual viewport. Otherwise texture rendertargets should use
     // their full size as the viewport
     IntVector2 rtSizeNow = graphics_->GetRenderTargetDimensions();
     IntRect viewport = (useViewportOutput && currentRenderTarget_ == renderTarget_) ? viewRect_ : IntRect(0, 0, rtSizeNow.x_,
@@ -1937,7 +1940,7 @@ void View::AllocateScreenBuffers()
     // Due to FBO limitations, in OpenGL deferred modes need to render to texture first and then blit to the backbuffer
     // Also, if rendering to a texture with full deferred rendering, it must be RGBA to comply with the rest of the buffers,
     // unless using OpenGL 3
-    if (((deferred_ || hasScenePassToRTs) && !renderTarget_) || (!Graphics::GetGL3Support() && deferredAmbient_ && renderTarget_ 
+    if (((deferred_ || hasScenePassToRTs) && !renderTarget_) || (!Graphics::GetGL3Support() && deferredAmbient_ && renderTarget_
         && renderTarget_->GetParentTexture()->GetFormat() != Graphics::GetRGBAFormat()))
             needSubstitute = true;
     // Also need substitute if rendering to backbuffer using a custom (readable) depth buffer
@@ -2157,7 +2160,7 @@ void View::DrawOccluders(OcclusionBuffer* buffer, const PODVector<Drawable*>& oc
 {
     buffer->SetMaxTriangles((unsigned)maxOccluderTriangles_);
     buffer->Clear();
-    
+
     if (!buffer->IsThreaded())
     {
         // If not threaded, draw occluders one by one and test the next occluder against already rasterized depth
@@ -3064,6 +3067,8 @@ Texture* View::FindNamedTexture(const String& name, bool isRenderTarget, bool is
         texture = cache->GetExistingResource<TextureCube>(name);
     if (!texture)
         texture = cache->GetExistingResource<Texture3D>(name);
+    if (!texture)
+        texture = cache->GetExistingResource<Texture2DArray>(name);
     if (texture)
         return texture;
 
@@ -3075,8 +3080,14 @@ Texture* View::FindNamedTexture(const String& name, bool isRenderTarget, bool is
         {
             // Assume 3D textures are only bound to the volume map unit, otherwise it's a cube texture
 #ifdef DESKTOP_GRAPHICS
-            if (isVolumeMap)
+            StringHash type = ParseTextureTypeXml(cache, name);
+            if (!type && isVolumeMap)
+                type = Texture3D::GetTypeStatic();
+
+            if (type == Texture3D::GetTypeStatic())
                 return cache->GetResource<Texture3D>(name);
+            else if (type == Texture2DArray::GetTypeStatic())
+                return cache->GetResource<Texture2DArray>(name);
             else
 #endif
                 return cache->GetResource<TextureCube>(name);