You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "GPUSamplerDescription.h"
#include "Engine/Core/Types/String.h"
void GPUSamplerDescription::Clear()
{
Filter = GPUSamplerFilter::Point;
AddressU = GPUSamplerAddressMode::Wrap;
AddressV = GPUSamplerAddressMode::Wrap;
AddressW = GPUSamplerAddressMode::Wrap;
MipBias = 0;
MaxAnisotropy = 0;
MinMipLevel = 0;
MaxMipLevel = MAX_float;
BorderColor = GPUSamplerBorderColor::TransparentBlack;
SamplerComparisonFunction = GPUSamplerCompareFunction::Never;
}
bool GPUSamplerDescription::Equals(const GPUSamplerDescription& other) const
{
return Filter == other.Filter
&& AddressU == other.AddressU
&& AddressV == other.AddressV
&& AddressW == other.AddressW
&& MipBias == other.MipBias
&& MaxAnisotropy == other.MaxAnisotropy
&& MinMipLevel == other.MinMipLevel
&& MaxMipLevel == other.MaxMipLevel
&& BorderColor == other.BorderColor
&& SamplerComparisonFunction == other.SamplerComparisonFunction;
}
String GPUSamplerDescription::ToString() const
{
return String::Format(TEXT("Filter: {}, Address: {}x{}x{}, MipBias: {}, MaxAnisotropy: {}, MinMipLevel: {}, MaxMipLevel: {}, BorderColor: {}, SamplerComparisonFunction: {}"),
(int32)Filter,
(int32)AddressU,
(int32)AddressV,
(int32)AddressW,
MipBias,
MaxAnisotropy,
MinMipLevel,
MaxMipLevel,
(int32)BorderColor,
(int32)SamplerComparisonFunction);
}
uint32 GetHash(const GPUSamplerDescription& key)
{
uint32 hashCode = (uint32)key.Filter;
hashCode = (hashCode * 397) ^ (uint32)key.AddressU;
hashCode = (hashCode * 397) ^ (uint32)key.AddressV;
hashCode = (hashCode * 397) ^ (uint32)key.AddressW;
hashCode = (hashCode * 397) ^ (uint32)key.MipBias;
hashCode = (hashCode * 397) ^ (uint32)key.MaxAnisotropy;
hashCode = (hashCode * 397) ^ (uint32)key.MinMipLevel;
hashCode = (hashCode * 397) ^ (uint32)key.MaxMipLevel;
hashCode = (hashCode * 397) ^ (uint32)key.BorderColor;
hashCode = (hashCode * 397) ^ (uint32)key.SamplerComparisonFunction;
return hashCode;
}

View File

@@ -0,0 +1,167 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Graphics/Enums.h"
class String;
/// <summary>
/// GPU sampler filter modes.
/// </summary>
enum class GPUSamplerFilter
{
Point = 0,
Bilinear = 1,
Trilinear = 2,
Anisotropic = 3,
MAX
};
/// <summary>
/// GPU sampler address modes.
/// </summary>
enum class GPUSamplerAddressMode
{
Wrap = 0,
Clamp = 1,
Mirror = 2,
Border = 3,
MAX
};
/// <summary>
/// GPU sampler comparision function types.
/// </summary>
enum class GPUSamplerCompareFunction
{
Never = 0,
Less = 1,
MAX
};
/// <summary>
/// GPU sampler border color types.
/// </summary>
enum class GPUSamplerBorderColor
{
/// <summary>
/// Indicates black, with the alpha component as fully transparent.
/// </summary>
TransparentBlack = 0,
/// <summary>
/// Indicates black, with the alpha component as fully opaque.
/// </summary>
OpaqueBlack = 1,
/// <summary>
/// Indicates white, with the alpha component as fully opaque.
/// </summary>
OpaqueWhite = 2,
Maximum
};
/// <summary>
/// A common description for all samplers.
/// </summary>
struct FLAXENGINE_API GPUSamplerDescription
{
public:
/// <summary>
/// The filtering method to use when sampling a texture.
/// </summary>
GPUSamplerFilter Filter;
/// <summary>
/// The addressing mode for outside [0..1] range for U coordinate.
/// </summary>
GPUSamplerAddressMode AddressU;
/// <summary>
/// The addressing mode for outside [0..1] range for V coordinate.
/// </summary>
GPUSamplerAddressMode AddressV;
/// <summary>
/// The addressing mode for outside [0..1] range for W coordinate.
/// </summary>
GPUSamplerAddressMode AddressW;
/// <summary>
/// The mip bias to be added to mipmap LOD calculation.
/// </summary>
float MipBias;
/// <summary>
/// The minimum mip map level that will be used, where 0 is the highest resolution mip level.
/// </summary>
float MinMipLevel;
/// <summary>
/// The maximum mip map level that will be used, where 0 is the highest resolution mip level. To have no upper limit on LOD set this to a large value such as MAX_float.
/// </summary>
float MaxMipLevel;
/// <summary>
/// The maximum anisotropy value.
/// </summary>
int32 MaxAnisotropy;
/// <summary>
/// The border color to use if Border is specified for AddressU, AddressV, or AddressW.
/// </summary>
GPUSamplerBorderColor BorderColor;
/// <summary>
/// A function that compares sampled data against existing sampled data.
/// </summary>
GPUSamplerCompareFunction SamplerComparisonFunction;
public:
/// <summary>
/// Clears description to the default values.
/// </summary>
void Clear();
public:
/// <summary>
/// Compares with other instance of SamplerDescription
/// </summary>
/// <param name="other">The other object to compare.</param>
/// <returns>True if objects are the same, otherwise false.</returns>
bool Equals(const GPUSamplerDescription& other) const;
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="other">The other description.</param>
/// <returns>The result of the operator.</returns>
FORCE_INLINE bool operator==(const GPUSamplerDescription& other) const
{
return Equals(other);
}
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="other">The other description.</param>
/// <returns>The result of the operator.</returns>
FORCE_INLINE bool operator!=(const GPUSamplerDescription& other) const
{
return !Equals(other);
}
public:
String ToString() const;
};
uint32 GetHash(const GPUSamplerDescription& key);

View File

@@ -0,0 +1,596 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "GPUTexture.h"
#include "TextureData.h"
#include "Engine/Core/Log.h"
#include "../Config.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Graphics/Async/Tasks/GPUCopyResourceTask.h"
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Graphics/GPUDevice.h"
GPUTexture* GPUTexture::Spawn(const SpawnParams& params)
{
return GPUDevice::Instance->CreateTexture(String::Empty);
}
GPUTexture* GPUTexture::New()
{
return GPUDevice::Instance->CreateTexture(String::Empty);
}
GPUTexture::GPUTexture()
: GPUResource(SpawnParams(Guid::New(), GPUTexture::TypeInitializer))
, _residentMipLevels(0)
, _sRGB(false)
, _isBlockCompressed(false)
{
// Keep description data clear (we use _desc.MipLevels to check if it's has been initated)
_desc.Clear();
}
Vector2 GPUTexture::Size() const
{
return Vector2(static_cast<float>(_desc.Width), static_cast<float>(_desc.Height));
}
Vector3 GPUTexture::Size3() const
{
return Vector3(static_cast<float>(_desc.Width), static_cast<float>(_desc.Height), static_cast<float>(_desc.Depth));
}
bool GPUTexture::IsPowerOfTwo() const
{
return Math::IsPowerOfTwo(_desc.Width) && Math::IsPowerOfTwo(_desc.Height);
}
void GPUTexture::GetMipSize(int32 mipLevelIndex, int32& mipWidth, int32& mipHeight) const
{
ASSERT(mipLevelIndex >= 0 && mipLevelIndex < MipLevels());
mipWidth = Math::Max(1, Width() >> mipLevelIndex);
mipHeight = Math::Max(1, Height() >> mipLevelIndex);
}
void GPUTexture::GetMipSize(int32 mipLevelIndex, int32& mipWidth, int32& mipHeight, int32& mipDepth) const
{
ASSERT(mipLevelIndex >= 0 && mipLevelIndex < MipLevels());
mipWidth = Math::Max(1, Width() >> mipLevelIndex);
mipHeight = Math::Max(1, Height() >> mipLevelIndex);
mipDepth = Math::Max(1, Depth() >> mipLevelIndex);
}
void GPUTexture::GetResidentSize(int32& width, int32& height) const
{
// Check if texture isn't loaded
if (_residentMipLevels <= 0)
{
width = height = 0;
return;
}
const int32 mipIndex = _desc.MipLevels - _residentMipLevels;
width = Width() >> mipIndex;
height = Height() >> mipIndex;
}
void GPUTexture::GetResidentSize(int32& width, int32& height, int32& depth) const
{
// Check if texture isn't loaded
if (_residentMipLevels <= 0)
{
width = height = depth = 0;
return;
}
const int32 mipIndex = _desc.MipLevels - _residentMipLevels;
width = Width() >> mipIndex;
height = Height() >> mipIndex;
depth = Depth() >> mipIndex;
}
uint32 GPUTexture::RowPitch(int32 mipIndex) const
{
uint32 rowPitch, slicePitch;
ComputePitch(mipIndex, rowPitch, slicePitch);
return rowPitch;
}
uint32 GPUTexture::SlicePitch(int32 mipIndex) const
{
uint32 rowPitch, slicePitch;
ComputePitch(mipIndex, rowPitch, slicePitch);
return slicePitch;
}
void GPUTexture::ComputePitch(int32 mipIndex, uint32& rowPitch, uint32& slicePitch) const
{
int32 mipWidth, mipHeight;
GetMipSize(mipIndex, mipWidth, mipHeight);
RenderTools::ComputePitch(Format(), mipWidth, mipHeight, rowPitch, slicePitch);
}
int32 GPUTexture::CalculateMipSize(int32 size, int32 mipLevel) const
{
mipLevel = Math::Min(mipLevel, MipLevels());
return Math::Max(1, size >> mipLevel);
}
int32 GPUTexture::ComputeSubresourceSize(int32 subresource, int32 rowAlign, int32 sliceAlign) const
{
const int32 mipLevel = subresource % MipLevels();
const int32 slicePitch = ComputeSlicePitch(mipLevel, rowAlign);
const int32 depth = CalculateMipSize(Depth(), mipLevel);
return Math::AlignUp<int32>(slicePitch * depth, sliceAlign);
}
int32 GPUTexture::ComputeBufferOffset(int32 subresource, int32 rowAlign, int32 sliceAlign) const
{
int32 offset = 0;
for (int32 i = 0; i < subresource; i++)
{
offset += ComputeSubresourceSize(i, rowAlign, sliceAlign);
}
return offset;
}
int32 GPUTexture::ComputeBufferTotalSize(int32 rowAlign, int32 sliceAlign) const
{
int32 result = 0;
for (int32 mipLevel = 0; mipLevel < MipLevels(); mipLevel++)
{
const int32 slicePitch = ComputeSlicePitch(mipLevel, rowAlign);
const int32 depth = CalculateMipSize(Depth(), mipLevel);
result += Math::AlignUp<int32>(slicePitch * depth, sliceAlign);
}
return result * ArraySize();
}
int32 GPUTexture::ComputeSlicePitch(int32 mipLevel, int32 rowAlign) const
{
return ComputeRowPitch(mipLevel, rowAlign) * CalculateMipSize(Height(), mipLevel);
}
int32 GPUTexture::ComputeRowPitch(int32 mipLevel, int32 rowAlign) const
{
const int32 formatSize = PixelFormatExtensions::SizeInBytes(Format());
return Math::AlignUp<int32>(CalculateMipSize(Width(), mipLevel) * formatSize, rowAlign);
}
bool GPUTexture::Init(const GPUTextureDescription& desc)
{
ASSERT(Math::IsInRange(desc.Width, 1, GPU_MAX_TEXTURE_SIZE)
&& Math::IsInRange(desc.Height, 1, GPU_MAX_TEXTURE_SIZE)
&& Math::IsInRange(desc.Depth, 1, GPU_MAX_TEXTURE_SIZE)
&& Math::IsInRange(desc.ArraySize, 1, GPU_MAX_TEXTURE_ARRAY_SIZE)
&& Math::IsInRange(desc.MipLevels, 1, GPU_MAX_TEXTURE_MIP_LEVELS));
// Validate description
const auto device = GPUDevice::Instance;
if (desc.Usage == GPUResourceUsage::Dynamic)
{
LOG(Warning, "Cannot create texture. Dynamic textures are not supported. Description: {0}", desc.ToString());
return true;
}
if (desc.IsDepthStencil())
{
if (desc.MipLevels > 1)
{
LOG(Warning, "Cannot create texture. Depth Stencil texture cannot have mip maps. Description: {0}", desc.ToString());
return true;
}
if (desc.IsRenderTarget())
{
LOG(Warning, "Cannot create texture. Depth Stencil texture cannot be used as a Render Target. Description: {0}", desc.ToString());
return true;
}
if (desc.Flags & GPUTextureFlags::ReadOnlyDepthView && !device->Limits.HasReadOnlyDepth)
{
LOG(Warning, "Cannot create texture. The current graphics platform does not support read-only Depth Stencil texture. Description: {0}", desc.ToString());
return true;
}
}
else
{
if (desc.Flags & GPUTextureFlags::ReadOnlyDepthView)
{
LOG(Warning, "Cannot create texture. Cannot create read-only Depth Stencil texture that is not a Depth Stencil texture. Add DepthStencil flag. Description: {0}", desc.ToString());
return true;
}
}
if (desc.HasPerMipViews() && !(desc.IsShaderResource() || desc.IsRenderTarget()))
{
LOG(Warning, "Cannot create texture. Depth Stencil texture cannot have mip maps. Description: {0}", desc.ToString());
return true;
}
switch (desc.Dimensions)
{
case TextureDimensions::Texture:
{
if (desc.HasPerSliceViews())
{
LOG(Warning, "Cannot create texture. Texture cannot have per slice views. Description: {0}", desc.ToString());
return true;
}
if (desc.Width > device->Limits.MaximumTexture2DSize
|| desc.Height > device->Limits.MaximumTexture2DSize
|| desc.ArraySize > device->Limits.MaximumTexture2DArraySize)
{
LOG(Warning, "Cannot create texture. Invalid dimensions. Description: {0}", desc.ToString());
return true;
}
break;
}
case TextureDimensions::VolumeTexture:
{
if (desc.IsDepthStencil())
{
LOG(Warning, "Cannot create texture. Only 2D Texture can be used as a Depth Stencil. Description: {0}", desc.ToString());
return true;
}
if (desc.MipLevels != 1)
{
LOG(Warning, "Cannot create texture. Volume texture cannot have more than 1 mip level. Description: {0}", desc.ToString());
return true;
}
if (desc.ArraySize != 1)
{
LOG(Warning, "Cannot create texture. Volume texture cannot create array of volume textures. Description: {0}", desc.ToString());
return true;
}
if (desc.MultiSampleLevel != MSAALevel::None)
{
LOG(Warning, "Cannot create texture. Volume texture cannot use multi-sampling. Description: {0}", desc.ToString());
return true;
}
if (desc.HasPerMipViews())
{
LOG(Warning, "Cannot create texture. Volume texture cannot have per mip map views. Description: {0}", desc.ToString());
return true;
}
if (desc.HasPerMipViews() && desc.IsRenderTarget() == false)
{
LOG(Warning, "Cannot create texture. Volume texture cannot have per slice map views if is not a render target. Description: {0}", desc.ToString());
return true;
}
if (desc.Width > device->Limits.MaximumTexture3DSize
|| desc.Height > device->Limits.MaximumTexture3DSize
|| desc.Depth > device->Limits.MaximumTexture3DSize)
{
LOG(Warning, "Cannot create texture. Invalid dimensions. Description: {0}", desc.ToString());
return true;
}
break;
}
case TextureDimensions::CubeTexture:
{
if (desc.HasPerSliceViews())
{
LOG(Warning, "Cannot create texture. Cube texture cannot have per slice views. Description: {0}", desc.ToString());
return true;
}
if (desc.Width > device->Limits.MaximumTextureCubeSize
|| desc.Height > device->Limits.MaximumTextureCubeSize
|| desc.ArraySize * 6 > device->Limits.MaximumTexture2DArraySize
|| desc.Width != desc.Height)
{
LOG(Warning, "Cannot create texture. Invalid dimensions. Description: {0}", desc.ToString());
return true;
}
break;
}
}
// Release previous data
ReleaseGPU();
// Initialize
_desc = desc;
_sRGB = PixelFormatExtensions::IsSRGB(desc.Format);
_isBlockCompressed = PixelFormatExtensions::IsCompressed(desc.Format);
if (OnInit())
{
ReleaseGPU();
LOG(Warning, "Cannot initialize texture. Description: {0}", desc.ToString());
return true;
}
// Render targets and depth buffers doesn't support normal textures streaming and are considered to be always resident
if (IsRegularTexture() == false)
{
_residentMipLevels = MipLevels();
}
return false;
}
GPUTexture* GPUTexture::ToStagingReadback() const
{
const auto desc = _desc.ToStagingReadback();
ScopeLock gpuLock(GPUDevice::Instance->Locker);
auto* staging = GPUDevice::Instance->CreateTexture(TEXT("Staging.Readback"));
if (staging->Init(desc))
{
Delete(staging);
return nullptr;
}
return staging;
}
GPUTexture* GPUTexture::ToStagingUpload() const
{
const auto desc = _desc.ToStagingUpload();
ScopeLock gpuLock(GPUDevice::Instance->Locker);
auto* staging = GPUDevice::Instance->CreateTexture(TEXT("Staging.Upload"));
if (staging->Init(desc))
{
Delete(staging);
return nullptr;
}
return staging;
}
bool GPUTexture::Resize(int32 width, int32 height, int32 depth)
{
// Validate texture is created
if (!IsAllocated())
{
LOG(Warning, "Cannot resize not created textures.");
return true;
}
auto desc = GetDescription();
// Skip if size won't change
if (desc.Width == width && desc.Height == height && desc.Depth == depth)
return false;
desc.Width = width;
desc.Height = height;
desc.Depth = depth;
// Recreate
return Init(desc);
}
uint64 GPUTexture::calculateMemoryUsage() const
{
return CalculateTextureMemoryUsage(Format(), Width(), Height(), Depth(), MipLevels()) * ArraySize();
}
String GPUTexture::ToString() const
{
#if GPU_ENABLE_RESOURCE_NAMING
return String::Format(TEXT("Texture {0}, Residency: {1}, Name: {2}"), _desc.ToString(), _residentMipLevels, GetName());
#else
return TEXT("Texture");
#endif
}
GPUResource::ResourceType GPUTexture::GetResourceType() const
{
if (IsVolume())
return ResourceType::VolumeTexture;
if (IsCubeMap())
return ResourceType::CubeTexture;
return IsRegularTexture() ? ResourceType::Texture : ResourceType::RenderTarget;
}
GPUResource::ObjectType GPUTexture::GetObjectType() const
{
return ObjectType::Texture;
}
void GPUTexture::OnReleaseGPU()
{
_desc.Clear();
_residentMipLevels = 0;
}
GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex)
{
ASSERT(IsRegularTexture() && IsAllocated());
//ASSERT(Math::IsInRange(mipIndex, HighestResidentMipIndex() - 1, MipLevels() - 1) && mipIndex < MipLevels() && data.IsValid());
ASSERT(mipIndex < MipLevels() && data.IsValid());
ASSERT(data.Length() == SlicePitch(mipIndex) * ArraySize());
// Create task
auto task = ::New<GPUUploadTextureMipTask>(this, mipIndex, data, false);
ASSERT(task && task->HasReference(this));
return task;
}
class TextureDownloadDataTask : public ThreadPoolTask
{
private:
GPUTextureReference _texture;
GPUTexture* _staging;
TextureData* _data;
bool _deleteStaging;
public:
/// <summary>
/// Initializes a new instance of the <see cref="TextureDownloadDataTask"/> class.
/// </summary>
/// <param name="texture">The target texture.</param>
/// <param name="staging">The staging texture.</param>
/// <param name="data">The output texture data.</param>
TextureDownloadDataTask(GPUTexture* texture, GPUTexture* staging, TextureData& data)
: _texture(texture)
, _staging(staging)
, _data(&data)
{
_deleteStaging = texture != staging;
}
/// <summary>
/// Finalizes an instance of the <see cref="TextureDownloadDataTask"/> class.
/// </summary>
~TextureDownloadDataTask()
{
if (_deleteStaging && _staging)
_staging->DeleteObjectNow();
}
public:
// [ThreadPoolTask]
bool HasReference(Object* resource) const override
{
return _texture == resource || _staging == resource;
}
protected:
// [ThreadPoolTask]
bool Run() override
{
// Check resources
auto texture = _texture.Get();
if (texture == nullptr || _staging == nullptr || _data == nullptr)
{
LOG(Warning, "Cannot download texture data. Missing objects.");
return true;
}
const auto arraySize = texture->ArraySize();
const auto mipLevels = texture->MipLevels();
// Get all mip maps for each array slice
auto& rawResultData = _data->Items;
rawResultData.Resize(arraySize, false);
for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)
{
auto& arraySlice = rawResultData[arrayIndex];
arraySlice.Mips.Resize(mipLevels);
for (int32 mipMapIndex = 0; mipMapIndex < mipLevels; mipMapIndex++)
{
auto& mip = arraySlice.Mips[mipMapIndex];
const int32 mipWidth = _data->Width >> mipMapIndex;
const int32 mipHeight = _data->Height >> mipMapIndex;
uint32 mipRowPitch, mipSlicePitch;
RenderTools::ComputePitch(_data->Format, mipWidth, mipHeight, mipRowPitch, mipSlicePitch);
// Gather data
if (_staging->GetData(arrayIndex, mipMapIndex, mip, mipRowPitch))
{
LOG(Warning, "Staging resource of \'{0}\' get data failed.", texture->ToString());
return true;
}
}
}
return false;
}
void OnEnd() override
{
_texture.Unlink();
// Base
ThreadPoolTask::OnEnd();
}
};
bool GPUTexture::DownloadData(TextureData& result)
{
const auto name = ToString();
// Ensure not running on main thread - we support DownloadData from textures only on a worker threads (Thread Pool Workers or Content Loaders)
if (IsInMainThread())
{
LOG(Warning, "Downloading GPU texture data from the main thread is not supported.");
return true;
}
// Create async task
auto task = DownloadDataAsync(result);
if (task == nullptr)
{
LOG(Warning, "Cannot create async download task for resource {0}.", name);
return true;
}
// Wait for work to be done
task->Start();
if (task->Wait())
{
LOG(Warning, "Resource \'{0}\' copy failed.", name);
return true;
}
return false;
}
Task* GPUTexture::DownloadDataAsync(TextureData& result)
{
if (!IsAllocated())
{
LOG(Warning, "Cannot download texture data. It has not ben created yet.");
return nullptr;
}
if (Depth() != 1)
{
MISSING_CODE("support volume texture data downloading.");
}
// Set texture info
result.Width = Width();
result.Height = Height();
result.Depth = Depth();
result.Format = Format();
// Quicker path if texture is already readback
if (_desc.Usage == GPUResourceUsage::StagingReadback)
{
// Create task to copy downloaded data to TextureData container
auto getDataTask = ::New<TextureDownloadDataTask>(this, this, result);
ASSERT(getDataTask->HasReference(this));
return getDataTask;
}
// Create the staging resource
const auto staging = ToStagingReadback();
if (staging == nullptr)
{
LOG(Error, "Cannot create staging resource from {0}.", ToString());
return nullptr;
}
// Create async resource copy task
auto copyTask = ::New<GPUCopyResourceTask>(this, staging);
ASSERT(copyTask->HasReference(this) && copyTask->HasReference(staging));
// Create task to copy downloaded data to TextureData container
auto getDataTask = ::New<TextureDownloadDataTask>(this, staging, result);
ASSERT(getDataTask->HasReference(this) && getDataTask->HasReference(staging));
// Set continuation
copyTask->ContinueWith(getDataTask);
return copyTask;
}
void GPUTexture::SetResidentMipLevels(int32 count)
{
ASSERT(IsRegularTexture() && IsAllocated());
ASSERT(Math::IsInRange(count, 0, MipLevels()));
_residentMipLevels = count;
onResidentMipsChanged();
}

View File

@@ -0,0 +1,577 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Graphics/GPUResource.h"
#include "GPUTextureDescription.h"
class GPUContext;
class GPUTask;
class TextureMipData;
class TextureData;
template<typename T>
class DataContainer;
typedef DataContainer<byte> BytesContainer;
class Task;
/// <summary>
/// Defines a view for the <see cref="GPUTexture"/> surface, full resource or any of the sub-parts. Can be used to define a single subresource of the texture, volume texture or texture array. Used to render to the texture and/or use textures in the shaders.
/// </summary>
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUTextureView : public GPUResourceView
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUTextureView);
protected:
GPUResource* _parent = nullptr;
PixelFormat _format = PixelFormat::Unknown;
MSAALevel _msaa = MSAALevel::None;
GPUTextureView()
: GPUResourceView(SpawnParams(Guid::New(), TypeInitializer))
{
}
void Init(GPUResource* parent, PixelFormat format, MSAALevel msaa)
{
_parent = parent;
_format = format;
_msaa = msaa;
}
public:
/// <summary>
/// Gets parent GPU resource owning that view.
/// </summary>
API_PROPERTY() FORCE_INLINE GPUResource* GetParent() const
{
return _parent;
}
/// <summary>
/// Gets the view format.
/// </summary>
API_PROPERTY() FORCE_INLINE PixelFormat GetFormat() const
{
return _format;
}
/// <summary>
/// Gets view MSAA level.
/// </summary>
API_PROPERTY() FORCE_INLINE MSAALevel GetMSAA() const
{
return _msaa;
}
};
/// <summary>
/// The GPU texture resource object. This class is able to create 2D/3D textures, volume textures and render targets.
/// </summary>
API_CLASS(Sealed) class FLAXENGINE_API GPUTexture : public GPUResource
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUTexture);
static GPUTexture* Spawn(const SpawnParams& params);
static GPUTexture* New();
protected:
int32 _residentMipLevels;
bool _sRGB, _isBlockCompressed;
GPUTextureDescription _desc;
/// <summary>
/// Initializes a new instance of the <see cref="GPUTexture"/> class.
/// </summary>
GPUTexture();
public:
/// <summary>
/// Gets a value indicating whether this texture has any resided mip (data already uploaded to the GPU).
/// </summary>
API_PROPERTY() FORCE_INLINE bool HasResidentMip() const
{
return _residentMipLevels != 0;
}
/// <summary>
/// Gets a value indicating whether this texture has been allocated.
/// </summary>
API_PROPERTY() FORCE_INLINE bool IsAllocated() const
{
return _desc.MipLevels > 0;
}
/// <summary>
/// Gets texture width (in texels).
/// </summary>
API_PROPERTY() FORCE_INLINE int32 Width() const
{
return _desc.Width;
}
/// <summary>
/// Gets texture height (in texels).
/// </summary>
API_PROPERTY() FORCE_INLINE int32 Height() const
{
return _desc.Height;
}
/// <summary>
/// Gets texture depth (in texels).
/// </summary>
API_PROPERTY() FORCE_INLINE int32 Depth() const
{
return _desc.Depth;
}
/// <summary>
/// Gets number of textures in the array.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 ArraySize() const
{
return _desc.ArraySize;
}
/// <summary>
/// Gets multi-sampling parameters for the texture.
/// </summary>
API_PROPERTY() FORCE_INLINE MSAALevel MultiSampleLevel() const
{
return _desc.MultiSampleLevel;
}
/// <summary>
/// Gets number of mipmap levels in the texture.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 MipLevels() const
{
return _desc.MipLevels;
}
/// <summary>
/// Gets the number of resident mipmap levels in the texture. (already uploaded to the GPU).
/// </summary>
API_PROPERTY() FORCE_INLINE int32 ResidentMipLevels() const
{
return _residentMipLevels;
}
/// <summary>
/// Gets the index of the highest resident mip map (may be equal to MipLevels if no mip has been uploaded). Note: mip=0 is the highest (top quality).
/// </summary>
API_PROPERTY() FORCE_INLINE int32 HighestResidentMipIndex() const
{
return MipLevels() - ResidentMipLevels();
}
/// <summary>
/// Gets texture data format.
/// </summary>
API_PROPERTY() FORCE_INLINE PixelFormat Format() const
{
return _desc.Format;
}
/// <summary>
/// Gets flags of the texture.
/// </summary>
API_PROPERTY() FORCE_INLINE GPUTextureFlags Flags() const
{
return _desc.Flags;
}
/// <summary>
/// Gets texture dimensions.
/// </summary>
API_PROPERTY() FORCE_INLINE TextureDimensions Dimensions() const
{
return _desc.Dimensions;
}
/// <summary>
/// Gets texture description structure.
/// </summary>
API_PROPERTY() FORCE_INLINE const GPUTextureDescription& GetDescription() const
{
return _desc;
}
public:
/// <summary>
/// Gets a value indicating whether this texture is a render target.
/// </summary>
FORCE_INLINE bool IsRenderTarget() const
{
return _desc.IsRenderTarget();
}
/// <summary>
/// Gets a value indicating whether this texture is a shader resource.
/// </summary>
FORCE_INLINE bool IsShaderResource() const
{
return _desc.IsShaderResource();
}
/// <summary>
/// Gets a value indicating whether this texture is a depth stencil.
/// </summary>
FORCE_INLINE bool IsDepthStencil() const
{
return _desc.IsDepthStencil();
}
/// <summary>
/// Gets a value indicating whether this texture is a unordered access.
/// </summary>
FORCE_INLINE bool IsUnorderedAccess() const
{
return _desc.IsUnorderedAccess();
}
/// <summary>
/// Gets a value indicating whether this instance has per mip level views.
/// </summary>
FORCE_INLINE bool HasPerMipViews() const
{
return _desc.HasPerMipViews();
}
/// <summary>
/// Gets a value indicating whether this instance has per slice views.
/// </summary>
FORCE_INLINE bool HasPerSliceViews() const
{
return _desc.HasPerSliceViews();
}
/// <summary>
/// Gets a value indicating whether this instance is a multi sample texture.
/// </summary>
FORCE_INLINE bool IsMultiSample() const
{
return _desc.IsMultiSample();
}
/// <summary>
/// Gets a value indicating whether this instance is a cubemap texture.
/// </summary>
FORCE_INLINE bool IsCubeMap() const
{
return _desc.Dimensions == TextureDimensions::CubeTexture;
}
/// <summary>
/// Gets a value indicating whether this instance is a volume texture.
/// </summary>
FORCE_INLINE bool IsVolume() const
{
return _desc.Dimensions == TextureDimensions::VolumeTexture;
}
/// <summary>
/// Gets a value indicating whether this instance is an array texture.
/// </summary>
FORCE_INLINE bool IsArray() const
{
return _desc.ArraySize != 1;
}
/// <summary>
/// Checks if texture contains sRGB colors data
/// </summary>
/// <returns>True if texture contains sRGB colors data, otherwise false</returns>
FORCE_INLINE bool IsSRGB() const
{
return _sRGB;
}
/// <summary>
/// Checks if texture is normal texture asset (not render target or unordered access or depth buffer or sth else)
/// </summary>
/// <returns>True if it is a regular texture, otherwise false</returns>
FORCE_INLINE bool IsRegularTexture() const
{
return _desc.Flags == GPUTextureFlags::ShaderResource;
}
/// <summary>
/// Checks if texture is a staging buffer (supports direct CPU access)
/// </summary>
/// <returns>True if texture is a staging buffer (supports direct CPU access), otherwise false</returns>
FORCE_INLINE bool IsStaging() const
{
return _desc.Usage == GPUResourceUsage::StagingUpload || _desc.Usage == GPUResourceUsage::StagingReadback;
}
/// <summary>
/// Gets a boolean indicating whether this <see cref="GPUTexture"/> is a using a block compress format (BC1, BC2, BC3, BC4, BC5, BC6H, BC7, etc.).
/// </summary>
FORCE_INLINE bool IsBlockCompressed() const
{
return _isBlockCompressed;
}
public:
/// <summary>
/// Gets the texture total size in pixels.
/// </summary>
API_PROPERTY() Vector2 Size() const;
/// <summary>
/// Gets the texture total size in pixels (with depth).
/// </summary>
API_PROPERTY() Vector3 Size3() const;
/// <summary>
/// Returns true if texture has size that is power of two.
/// </summary>
/// <returns>True if texture has size that is power of two.</returns>
API_PROPERTY() bool IsPowerOfTwo() const;
/// <summary>
/// Gets the texture mip map dimensions.
/// </summary>
/// <param name="mipLevelIndex">Mip level index (zero-based where 0 is top texture surface).</param>
/// <param name="mipWidth">The calculated mip level width (in pixels).</param>
/// <param name="mipHeight">The calculated mip level height (in pixels).</param>
API_FUNCTION() void GetMipSize(int32 mipLevelIndex, API_PARAM(Out) int32& mipWidth, API_PARAM(Out) int32& mipHeight) const;
/// <summary>
/// Gets the texture mip map dimensions.
/// </summary>
/// <param name="mipLevelIndex">Mip level index (zero-based where 0 is top texture surface).</param>
/// <param name="mipWidth">The calculated mip level width (in pixels).</param>
/// <param name="mipHeight">The calculated mip level height (in pixels).</param>
/// <param name="mipDepth">The calculated mip level depth (in pixels).</param>
API_FUNCTION() void GetMipSize(int32 mipLevelIndex, API_PARAM(Out) int32& mipWidth, API_PARAM(Out) int32& mipHeight, API_PARAM(Out) int32& mipDepth) const;
/// <summary>
/// Gets current texture size (uploaded to the GPU and in use).
/// </summary>
/// <param name="width">The current width (in pixels).</param>
/// <param name="height">The current height (in pixels).</param>
void GetResidentSize(int32& width, int32& height) const;
/// <summary>
/// Gets current texture size (uploaded to the GPU and in use).
/// </summary>
/// <param name="width">The current width (in pixels).</param>
/// <param name="height">The current height (in pixels).</param>
/// <param name="depth">The current depth (in pixels).</param>
void GetResidentSize(int32& width, int32& height, int32& depth) const;
public:
/// <summary>
/// Calculates mip map row pitch (in bytes).
/// </summary>
/// <param name="mipIndex">Index of the mip.</param>
/// <returns>Row pitch.</returns>
uint32 RowPitch(int32 mipIndex = 0) const;
/// <summary>
/// Calculates mip map slice pitch (in bytes).
/// </summary>
/// <param name="mipIndex">Index of the mip.</param>
/// <returns>Slice pitch.</returns>
uint32 SlicePitch(int32 mipIndex = 0) const;
/// <summary>
/// Computes row and slice pitch of the mip map.
/// </summary>
/// <param name="mipIndex">Index of the mip.</param>
/// <param name="rowPitch">The row pitch.</param>
/// <param name="slicePitch">The slice pitch.</param>
void ComputePitch(int32 mipIndex, uint32& rowPitch, uint32& slicePitch) const;
/// <summary>
/// Calculates the size of a particular mip.
/// </summary>
/// <param name="size">The size.</param>
/// <param name="mipLevel">The mip level.</param>
/// <returns>Mip size.</returns>
int32 CalculateMipSize(int32 size, int32 mipLevel) const;
public:
int32 ComputeSubresourceSize(int32 subresource, int32 rowAlign, int32 sliceAlign) const;
int32 ComputeBufferOffset(int32 subresource, int32 rowAlign, int32 sliceAlign) const;
int32 ComputeBufferTotalSize(int32 rowAlign, int32 sliceAlign) const;
int32 ComputeSlicePitch(int32 mipLevel, int32 rowAlign) const;
int32 ComputeRowPitch(int32 mipLevel, int32 rowAlign) const;
public:
/// <summary>
/// Gets the view to the first surface (only for 2D textures).
/// </summary>
/// <returns>The view to the main texture surface.</returns>
API_FUNCTION() FORCE_INLINE GPUTextureView* View() const
{
return View(0);
}
/// <summary>
/// Gets the view to the surface at index in an array.
/// </summary>
/// <remarks>
/// To use per depth/array slice view you need to specify the <see cref="GPUTextureFlags.PerSliceViews"/> when creating the resource.
/// </remarks>
/// <param name="arrayOrDepthIndex">The index of the surface in an array (or depth slice index).</param>
/// <returns>The view to the surface at index in an array.</returns>
API_FUNCTION() virtual GPUTextureView* View(int32 arrayOrDepthIndex) const = 0;
/// <summary>
/// Gets the view to the mip map surface at index in an array.
/// </summary>
/// <remarks>
/// To use per mip map view you need to specify the <see cref="GPUTextureFlags.PerMipViews"/> when creating the resource.
/// </remarks>
/// <param name="arrayOrDepthIndex">The index of the surface in an array (or depth slice index).</param>
/// <param name="mipMapIndex">Index of the mip level.</param>
/// <returns>The view to the surface at index in an array.</returns>
API_FUNCTION() virtual GPUTextureView* View(int32 arrayOrDepthIndex, int32 mipMapIndex) const = 0;
/// <summary>
/// Gets the view to the array of surfaces
/// </summary>
/// <remarks>
/// To use array texture view you need to create render target as an array.
/// </remarks>
/// <returns>The view to the array of surfaces.</returns>
API_FUNCTION() virtual GPUTextureView* ViewArray() const = 0;
/// <summary>
/// Gets the view to the volume texture (3D).
/// </summary>
/// <remarks>
/// To use volume texture view you need to create render target as a volume resource (3D texture with Depth > 1).
/// </remarks>
/// <returns>The view to the volume texture.</returns>
API_FUNCTION() virtual GPUTextureView* ViewVolume() const = 0;
/// <summary>
/// Gets the view to the texture as read-only depth/stencil buffer. Valid only if graphics device supports it and the texture uses depth/stencil.
/// </summary>
/// <returns>The view to the depth-stencil resource descriptor as read-only depth.</returns>
API_FUNCTION() virtual GPUTextureView* ViewReadOnlyDepth() const = 0;
/// <summary>
/// Implicit conversion to the first surface (only for 2D textures).
/// </summary>
/// <returns>The view to the main texture surface.</returns>
FORCE_INLINE operator GPUTextureView*() const
{
return View(0);
}
public:
/// <summary>
/// Initializes a texture resource (allocates the GPU memory and performs the resource setup).
/// </summary>
/// <param name="desc">The texture description.</param>
/// <returns>True if cannot create texture, otherwise false.</returns>
API_FUNCTION() bool Init(API_PARAM(Ref) const GPUTextureDescription& desc);
/// <summary>
/// Creates new staging readback texture with the same dimensions and properties as a source texture (but without a data transferred; warning: caller must delete object).
/// </summary>
/// <returns>Thr staging readback texture.</returns>
GPUTexture* ToStagingReadback() const;
/// <summary>
/// Creates new staging upload texture with the same dimensions and properties as a source texture (but without a data transferred; warning: caller must delete object).
/// </summary>
/// <returns>The staging upload texture.</returns>
GPUTexture* ToStagingUpload() const;
/// <summary>
/// Resizes the texture. It must be created first.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <returns>True if fails, otherwise false.</returns>
API_FUNCTION() bool Resize(int32 width, int32 height)
{
const auto depth = IsAllocated() ? Depth() : 1;
return Resize(width, height, depth);
}
/// <summary>
/// Resizes the texture. It must be created first.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="depth">The depth.</param>
/// <returns>True if fails, otherwise false.</returns>
API_FUNCTION() bool Resize(int32 width, int32 height, int32 depth);
public:
/// <summary>
/// Gets the native pointer to the underlying resource. It's a low-level platform-specific handle.
/// </summary>
/// <returns>The pointer.</returns>
API_PROPERTY() virtual void* GetNativePtr() const = 0;
/// <summary>
/// Uploads mip map data to the GPU. Creates async GPU task.
/// </summary>
/// <param name="data">Data to upload (it must be valid for the next a few frames due to GPU latency and async works executing)</param>
/// <param name="mipIndex">Mip level index.</param>
/// <returns>Created async task or null if cannot.</returns>
GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex);
/// <summary>
/// Stops current thread execution to gather texture data from the GPU.
/// </summary>
/// <param name="result">The result data.</param>
/// <returns>True if cannot download data, otherwise false.</returns>
bool DownloadData(TextureData& result);
/// <summary>
/// Creates GPU async task that will gather texture data from the GPU.
/// </summary>
/// <param name="result">The result data.</param>
/// <returns>Download data task (not started yet).</returns>
Task* DownloadDataAsync(TextureData& result);
/// <summary>
/// Gets texture mipmap data (raw bytes). Can be used only with textures created with Staging flag.
/// </summary>
/// <param name="arrayOrDepthSliceIndex">Array or depth slice index.</param>
/// <param name="mipMapIndex">Mip map index.</param>
/// <param name="data">Output mip data.</param>
/// <param name="mipRowPitch">Output mip data row pitch to use. Use 0 to use the pitch from the internal GPU storage.</param>
/// <returns>True if failed, otherwise false.</returns>
virtual bool GetData(int32 arrayOrDepthSliceIndex, int32 mipMapIndex, TextureMipData& data, uint32 mipRowPitch = 0) = 0;
public:
void SetResidentMipLevels(int32 count);
protected:
virtual bool OnInit() = 0;
uint64 calculateMemoryUsage() const;
virtual void onResidentMipsChanged() = 0;
public:
// [GPUResource]
String ToString() const override;
ResourceType GetResourceType() const final override;
ObjectType GetObjectType() const final override;
protected:
// [GPUResource]
void OnReleaseGPU() override;
};

View File

@@ -0,0 +1,186 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "GPUTextureDescription.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Graphics/Config.h"
namespace
{
int32 CalculateMipMapCount(int32 requestedLevel, int32 width)
{
//int32 size = width;
//int32 maxMipMap = 1 + Math::CeilToInt(Math::Log(size) / Math::Log(2.0));
//return requestedLevel == 0 ? maxMipMap : Math::Min(requestedLevel, maxMipMap);
if (requestedLevel == 0)
requestedLevel = GPU_MAX_TEXTURE_MIP_LEVELS;
int32 maxMipMap = 1;
while (width > 1)
{
width >>= 1;
maxMipMap++;
}
return Math::Min(requestedLevel, maxMipMap);
}
}
const Char* ToString(TextureDimensions value)
{
const Char* result;
switch (value)
{
case TextureDimensions::Texture:
result = TEXT("Texture");
break;
case TextureDimensions::VolumeTexture:
result = TEXT("VolumeTexture");
break;
case TextureDimensions::CubeTexture:
result = TEXT("CubeTexture");
break;
default:
result = TEXT("");
}
return result;
}
void GPUTextureDescription::Clear()
{
Platform::MemoryClear(this, sizeof(GPUTextureDescription));
MultiSampleLevel = MSAALevel::None;
}
GPUTextureDescription GPUTextureDescription::New1D(int32 width, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount, int32 arraySize)
{
GPUTextureDescription desc;
desc.Dimensions = TextureDimensions::Texture;
desc.Width = width;
desc.Height = 1;
desc.Depth = 1;
desc.ArraySize = arraySize;
desc.MipLevels = CalculateMipMapCount(mipCount, width);
desc.Format = format;
desc.MultiSampleLevel = MSAALevel::None;
desc.Flags = textureFlags;
desc.Usage = GPUResourceUsage::Default;
desc.DefaultClearColor = Color::Black;
return desc;
}
GPUTextureDescription GPUTextureDescription::New2D(int32 width, int32 height, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount, int32 arraySize, MSAALevel msaaLevel)
{
GPUTextureDescription desc;
desc.Dimensions = TextureDimensions::Texture;
desc.Width = width;
desc.Height = height;
desc.Depth = 1;
desc.ArraySize = arraySize;
desc.MipLevels = CalculateMipMapCount(mipCount, Math::Max(width, height));
desc.Format = format;
desc.MultiSampleLevel = msaaLevel;
desc.Flags = textureFlags;
desc.DefaultClearColor = Color::Black;
desc.Usage = GPUResourceUsage::Default;
return desc;
}
GPUTextureDescription GPUTextureDescription::New3D(const Vector3& size, PixelFormat format, GPUTextureFlags textureFlags)
{
return New3D((int32)size.X, (int32)size.Y, (int32)size.Z, 1, format, textureFlags);
}
GPUTextureDescription GPUTextureDescription::New3D(int32 width, int32 height, int32 depth, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount)
{
GPUTextureDescription desc;
desc.Dimensions = TextureDimensions::VolumeTexture;
desc.Width = width;
desc.Height = height;
desc.Depth = depth;
desc.ArraySize = 1;
desc.MipLevels = CalculateMipMapCount(mipCount, Math::Max(width, height, depth));
desc.Format = format;
desc.MultiSampleLevel = MSAALevel::None;
desc.Flags = textureFlags;
desc.DefaultClearColor = Color::Black;
desc.Usage = GPUResourceUsage::Default;
return desc;
}
GPUTextureDescription GPUTextureDescription::NewCube(int32 size, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount)
{
auto desc = New2D(size, size, format, textureFlags, mipCount, 6, MSAALevel::None);
desc.Dimensions = TextureDimensions::CubeTexture;
return desc;
}
bool GPUTextureDescription::Equals(const GPUTextureDescription& other) const
{
return Dimensions == other.Dimensions
&& Width == other.Width
&& Height == other.Height
&& Depth == other.Depth
&& ArraySize == other.ArraySize
&& MipLevels == other.MipLevels
&& Format == other.Format
&& MultiSampleLevel == other.MultiSampleLevel
&& Flags == other.Flags
&& Usage == other.Usage
&& Color::NearEqual(DefaultClearColor, other.DefaultClearColor);
}
String GPUTextureDescription::ToString() const
{
// TODO: add tool to Format to string
String flags;
if (Flags == GPUTextureFlags::None)
{
flags = TEXT("None");
}
else
{
// TODO: create tool to auto convert flag enums to string
#define CONVERT_FLAGS_FLAGS_2_STR(value) if(Flags & GPUTextureFlags::value) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); }
CONVERT_FLAGS_FLAGS_2_STR(ShaderResource);
CONVERT_FLAGS_FLAGS_2_STR(RenderTarget);
CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess);
CONVERT_FLAGS_FLAGS_2_STR(DepthStencil);
CONVERT_FLAGS_FLAGS_2_STR(PerMipViews);
CONVERT_FLAGS_FLAGS_2_STR(PerSliceViews);
CONVERT_FLAGS_FLAGS_2_STR(ReadOnlyDepthView);
CONVERT_FLAGS_FLAGS_2_STR(BackBuffer);
#undef CONVERT_FLAGS_FLAGS_2_STR
}
return String::Format(TEXT("Size: {0}x{1}x{2}[{3}], Type: {4}, Mips: {5}, Format: {6}, MSAA: {7}, Flags: {8}, Usage: {9}"),
Width,
Height,
Depth,
ArraySize,
::ToString(Dimensions),
MipLevels,
(int32)Format,
::ToString(MultiSampleLevel),
flags,
(int32)Usage);
}
uint32 GetHash(const GPUTextureDescription& key)
{
uint32 hashCode = key.Width;
hashCode = (hashCode * 397) ^ key.Height;
hashCode = (hashCode * 397) ^ key.Depth;
hashCode = (hashCode * 397) ^ key.ArraySize;
hashCode = (hashCode * 397) ^ (uint32)key.Dimensions;
hashCode = (hashCode * 397) ^ key.MipLevels;
hashCode = (hashCode * 397) ^ (uint32)key.Format;
hashCode = (hashCode * 397) ^ (uint32)key.MultiSampleLevel;
hashCode = (hashCode * 397) ^ (uint32)key.Flags;
hashCode = (hashCode * 397) ^ (uint32)key.Usage;
hashCode = (hashCode * 397) ^ key.DefaultClearColor.GetHashCode();
return hashCode;
}

View File

@@ -0,0 +1,384 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine
{
partial struct GPUTextureDescription : IEquatable<GPUTextureDescription>
{
/// <summary>
/// Gets a value indicating whether this instance is a render target.
/// </summary>
public bool IsRenderTarget => (Flags & GPUTextureFlags.RenderTarget) != 0;
/// <summary>
/// Gets a value indicating whether this instance is a depth stencil.
/// </summary>
public bool IsDepthStencil => (Flags & GPUTextureFlags.DepthStencil) != 0;
/// <summary>
/// Gets a value indicating whether this instance is a shader resource.
/// </summary>
public bool IsShaderResource => (Flags & GPUTextureFlags.ShaderResource) != 0;
/// <summary>
/// Gets a value indicating whether this instance is a unordered access.
/// </summary>
public bool IsUnorderedAccess => (Flags & GPUTextureFlags.UnorderedAccess) != 0;
/// <summary>
/// Gets a value indicating whether this instance has per mip level handles.
/// </summary>
public bool HasPerMipViews => (Flags & GPUTextureFlags.PerMipViews) != 0;
/// <summary>
/// Gets a value indicating whether this instance has per slice views.
/// </summary>
public bool HasPerSliceViews => (Flags & GPUTextureFlags.PerSliceViews) != 0;
/// <summary>
/// Gets a value indicating whether this instance is a multi sample texture.
/// </summary>
public bool IsMultiSample => MultiSampleLevel > MSAALevel.None;
/// <summary>
/// Gets a value indicating whether this instance is a cubemap texture.
/// </summary>
public bool IsCubeMap => Dimensions == TextureDimensions.CubeTexture;
/// <summary>
/// Gets a value indicating whether this instance is a volume texture.
/// </summary>
public bool IsVolume => Dimensions == TextureDimensions.VolumeTexture;
/// <summary>
/// Gets a value indicating whether this instance is an array texture.
/// </summary>
public bool IsArray => ArraySize != 1;
private static int CalculateMipMapCount(int requestedLevel, int width)
{
if (requestedLevel == 0)
requestedLevel = Texture.MaxMipLevels;
int maxMipMap = 1;
while (width > 1)
{
width >>= 1;
maxMipMap++;
}
return Mathf.Min(requestedLevel, maxMipMap);
}
/// <summary>
/// Clears description.
/// </summary>
public void Clear()
{
this = new GPUTextureDescription();
MultiSampleLevel = MSAALevel.None;
}
/// <summary>
/// Creates a new 1D <see cref="GPUTextureDescription" /> with a single mipmap.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <returns>A new instance of 1D <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New1D(int width, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget, int arraySize = 1)
{
return New1D(width, format, textureFlags, 1, arraySize);
}
/// <summary>
/// Creates a new 1D <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <returns>A new instance of 1D <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New1D(int width, int mipCount, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget, int arraySize = 1)
{
return New1D(width, format, textureFlags, mipCount, arraySize);
}
/// <summary>
/// Creates a new 1D <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <returns>A new instance of 1D <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New1D(int width, PixelFormat format, GPUTextureFlags textureFlags, int mipCount, int arraySize)
{
GPUTextureDescription desc;
desc.Dimensions = TextureDimensions.Texture;
desc.Width = width;
desc.Height = 1;
desc.Depth = 1;
desc.ArraySize = arraySize;
desc.MipLevels = CalculateMipMapCount(mipCount, width);
desc.Format = format;
desc.MultiSampleLevel = MSAALevel.None;
desc.Flags = textureFlags;
desc.Usage = GPUResourceUsage.Default;
desc.DefaultClearColor = Color.Black;
return desc;
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" /> with a single mipmap.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New2D(int width, int height, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget, int arraySize = 1)
{
return New2D(width, height, 1, format, textureFlags, arraySize);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <param name="msaaLevel">The MSAA Level.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New2D(int width, int height, int mipCount, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget, int arraySize = 1, MSAALevel msaaLevel = MSAALevel.None)
{
return New2D(width, height, format, textureFlags, mipCount, arraySize, msaaLevel);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <param name="msaaLevel">The MSAA Level.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New2D(int width, int height, PixelFormat format, GPUTextureFlags textureFlags, int mipCount, int arraySize, MSAALevel msaaLevel = MSAALevel.None)
{
GPUTextureDescription desc;
desc.Dimensions = TextureDimensions.Texture;
desc.Width = width;
desc.Height = height;
desc.Depth = 1;
desc.ArraySize = arraySize;
desc.MipLevels = CalculateMipMapCount(mipCount, Mathf.Max(width, height));
desc.Format = format;
desc.MultiSampleLevel = msaaLevel;
desc.Flags = textureFlags;
desc.DefaultClearColor = Color.Black;
desc.Usage = GPUResourceUsage.Default;
return desc;
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" /> with a single mipmap.
/// </summary>
/// <param name="size">The size (width, height and depth).</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New3D(Vector3 size, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget)
{
return New3D((int)size.X, (int)size.Y, (int)size.Z, 1, format, textureFlags);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" /> with a single mipmap.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="depth">The depth.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New3D(int width, int height, int depth, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget)
{
return New3D(width, height, depth, 1, format, textureFlags);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="depth">The depth.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New3D(int width, int height, int depth, int mipCount, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget)
{
return New3D(width, height, depth, format, textureFlags, mipCount);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="depth">The depth.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription New3D(int width, int height, int depth, PixelFormat format, GPUTextureFlags textureFlags, int mipCount)
{
GPUTextureDescription desc;
desc.Dimensions = TextureDimensions.VolumeTexture;
desc.Width = width;
desc.Height = height;
desc.Depth = depth;
desc.ArraySize = 1;
desc.MipLevels = CalculateMipMapCount(mipCount, Mathf.Max(width, height, depth));
desc.Format = format;
desc.MultiSampleLevel = MSAALevel.None;
desc.Flags = textureFlags;
desc.DefaultClearColor = Color.Black;
desc.Usage = GPUResourceUsage.Default;
return desc;
}
/// <summary>
/// Creates a new Cube <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="size">The size (in pixels) of the top-level faces of the cube texture.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">The texture flags.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
public static GPUTextureDescription NewCube(int size, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget)
{
return NewCube(size, 1, format, textureFlags);
}
/// <summary>
/// Creates a new Cube <see cref="GPUTextureDescription"/>.
/// </summary>
/// <param name="size">The size (in pixels) of the top-level faces of the cube texture.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">The texture flags.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription"/> class.</returns>
public static GPUTextureDescription NewCube(int size, int mipCount, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags.ShaderResource | GPUTextureFlags.RenderTarget)
{
return NewCube(size, format, textureFlags, mipCount);
}
/// <summary>
/// Creates a new Cube <see cref="GPUTextureDescription"/>.
/// </summary>
/// <param name="size">The size (in pixels) of the top-level faces of the cube texture.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">The texture flags.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription"/> class.</returns>
public static GPUTextureDescription NewCube(int size, PixelFormat format, GPUTextureFlags textureFlags, int mipCount)
{
var desc = New2D(size, size, format, textureFlags, mipCount, 6);
desc.Dimensions = TextureDimensions.CubeTexture;
return desc;
}
/// <summary>
/// Gets the staging description for this instance.
/// </summary>
/// <returns>A staging texture description</returns>
public GPUTextureDescription ToStagingUpload()
{
var desc = this;
desc.Flags = GPUTextureFlags.None;
desc.Usage = GPUResourceUsage.StagingUpload;
return desc;
}
/// <summary>
/// Gets the staging description for this instance.
/// </summary>
/// <returns>A staging texture description</returns>
public GPUTextureDescription ToStagingReadback()
{
var desc = this;
desc.Flags = GPUTextureFlags.None;
desc.Usage = GPUResourceUsage.StagingReadback;
return desc;
}
/// <inheritdoc />
public override string ToString()
{
return string.Format("Size: {0}x{1}x{2}[{3}], Type: {4}, Mips: {5}, Format: {6}, MSAA: {7}, Flags: {8}, Usage: {9}",
Width,
Height,
Depth,
ArraySize,
Dimensions,
MipLevels,
Format,
MultiSampleLevel,
Flags,
Usage);
}
/// <inheritdoc />
public override int GetHashCode()
{
unchecked
{
var hashCode = (int)Dimensions;
hashCode = (hashCode * 397) ^ Width;
hashCode = (hashCode * 397) ^ Height;
hashCode = (hashCode * 397) ^ Depth;
hashCode = (hashCode * 397) ^ ArraySize;
hashCode = (hashCode * 397) ^ MipLevels;
hashCode = (hashCode * 397) ^ (int)Format;
hashCode = (hashCode * 397) ^ (int)MultiSampleLevel;
hashCode = (hashCode * 397) ^ (int)Flags;
hashCode = (hashCode * 397) ^ (int)Usage;
hashCode = (hashCode * 397) ^ DefaultClearColor.GetHashCode();
return hashCode;
}
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is GPUTextureDescription other && Equals(other);
}
/// <inheritdoc />
public bool Equals(GPUTextureDescription other)
{
return Dimensions == other.Dimensions &&
Width == other.Width &&
Height == other.Height &&
Depth == other.Depth &&
ArraySize == other.ArraySize &&
MipLevels == other.MipLevels &&
Format == other.Format &&
MultiSampleLevel == other.MultiSampleLevel &&
Flags == other.Flags &&
Usage == other.Usage &&
DefaultClearColor.Equals(ref other.DefaultClearColor);
}
};
}

View File

@@ -0,0 +1,470 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Math/Color.h"
#include "Engine/Graphics/PixelFormat.h"
#include "Engine/Graphics/Enums.h"
/// <summary>
/// GPU texture usage flags.
/// </summary>
API_ENUM(Attributes="Flags") enum class GPUTextureFlags
{
/// <summary>
/// No texture flags.
/// </summary>
None = 0x0000,
/// <summary>
/// Create a texture that can be bound as a shader resource.
/// </summary>
ShaderResource = 0x0001,
/// <summary>
/// Create a texture that can be bound as a render target.
/// </summary>
RenderTarget = 0x0002,
/// <summary>
/// Create a texture can be bound as an unordered access buffer.
/// </summary>
UnorderedAccess = 0x0004,
/// <summary>
/// Create a texture can be bound as a depth stencil buffer.
/// </summary>
DepthStencil = 0x0008,
/// <summary>
/// Create texture views per texture mip map (valid only for Texture2D with ShaderResource or RenderTarget flag).
/// </summary>
PerMipViews = 0x0010,
/// <summary>
/// Create texture views per texture slice map (valid only for Texture3D with ShaderResource or RenderTarget flag).
/// </summary>
PerSliceViews = 0x0020,
/// <summary>
/// Create read-only view for depth-stencil buffer. Valid only if texture uses depth-stencil and the graphics device supports it.
/// </summary>
ReadOnlyDepthView = 0x0040,
/// <summary>
/// Create a texture that can be used as a native window swap chain backbuffer surface.
/// </summary>
BackBuffer = 0x0080,
};
DECLARE_ENUM_OPERATORS(GPUTextureFlags);
/// <summary>
/// Defines the dimension of a texture object.
/// </summary>
API_ENUM() enum class TextureDimensions
{
/// <summary>
/// The texture (2d).
/// </summary>
Texture,
/// <summary>
/// The volume texture (3d texture).
/// </summary>
VolumeTexture,
/// <summary>
/// The cube texture (2d texture array of 6 items).
/// </summary>
CubeTexture,
};
const Char* ToString(TextureDimensions value);
/// <summary>
/// A common description for all GPU textures.
/// </summary>
API_STRUCT() struct FLAXENGINE_API GPUTextureDescription
{
DECLARE_SCRIPTING_TYPE_MINIMAL(GPUTextureDescription);
/// <summary>
/// The dimensions of the texture.
/// </summary>
API_FIELD() TextureDimensions Dimensions;
/// <summary>
/// Texture width (in texels).
/// </summary>
API_FIELD() int32 Width;
/// <summary>
/// Texture height (in texels).
/// </summary>
API_FIELD() int32 Height;
/// <summary>
/// Texture depth (in texels) for Volume Textures.
/// </summary>
API_FIELD() int32 Depth;
/// <summary>
/// Number of textures in array for Texture Arrays.
/// </summary>
API_FIELD() int32 ArraySize;
/// <summary>
/// The maximum number of mipmap levels in the texture. Use 1 for a multisampled texture; or 0 to generate a full set of subtextures.
/// </summary>
API_FIELD() int32 MipLevels;
/// <summary>
/// Texture format (see <strong><see cref="PixelFormat"/></strong>).
/// </summary>
API_FIELD() PixelFormat Format;
/// <summary>
/// Structure that specifies multisampling parameters for the texture.
/// </summary>
API_FIELD() MSAALevel MultiSampleLevel;
/// <summary>
/// Flags (see <strong><see cref="GPUTextureFlags"/></strong>) for binding to pipeline stages. The flags can be combined by a logical OR.
/// </summary>
API_FIELD() GPUTextureFlags Flags;
/// <summary>
/// Value that identifies how the texture is to be read from and written to. The most common value is <see cref="GPUResourceUsage.Default"/>; see <strong><see cref="GPUResourceUsage"/></strong> for all possible values.
/// </summary>
API_FIELD() GPUResourceUsage Usage;
/// <summary>
/// Default clear color for render targets
/// </summary>
API_FIELD() Color DefaultClearColor;
public:
/// <summary>
/// Gets a value indicating whether this instance is a render target.
/// </summary>
FORCE_INLINE bool IsRenderTarget() const
{
return (Flags & GPUTextureFlags::RenderTarget) != 0;
}
/// <summary>
/// Gets a value indicating whether this instance is a depth stencil.
/// </summary>
FORCE_INLINE bool IsDepthStencil() const
{
return (Flags & GPUTextureFlags::DepthStencil) != 0;
}
/// <summary>
/// Gets a value indicating whether this instance is a shader resource.
/// </summary>
FORCE_INLINE bool IsShaderResource() const
{
return (Flags & GPUTextureFlags::ShaderResource) != 0;
}
/// <summary>
/// Gets a value indicating whether this instance is a unordered access.
/// </summary>
FORCE_INLINE bool IsUnorderedAccess() const
{
return (Flags & GPUTextureFlags::UnorderedAccess) != 0;
}
/// <summary>
/// Gets a value indicating whether this instance has per mip level handles.
/// </summary>
FORCE_INLINE bool HasPerMipViews() const
{
return (Flags & GPUTextureFlags::PerMipViews) != 0;
}
/// <summary>
/// Gets a value indicating whether this instance has per slice views.
/// </summary>
FORCE_INLINE bool HasPerSliceViews() const
{
return (Flags & GPUTextureFlags::PerSliceViews) != 0;
}
/// <summary>
/// Gets a value indicating whether this instance is a multi sample texture.
/// </summary>
FORCE_INLINE bool IsMultiSample() const
{
return MultiSampleLevel > MSAALevel::None;
}
/// <summary>
/// Gets a value indicating whether this instance is a cubemap texture.
/// </summary>
FORCE_INLINE bool IsCubeMap() const
{
return Dimensions == TextureDimensions::CubeTexture;
}
/// <summary>
/// Gets a value indicating whether this instance is a volume texture.
/// </summary>
FORCE_INLINE bool IsVolume() const
{
return Dimensions == TextureDimensions::VolumeTexture;
}
/// <summary>
/// Gets a value indicating whether this instance is an array texture.
/// </summary>
FORCE_INLINE bool IsArray() const
{
return ArraySize != 1;
}
public:
/// <summary>
/// Clears description.
/// </summary>
void Clear();
public:
/// <summary>
/// Creates a new 1D <see cref="GPUTextureDescription" /> with a single mipmap.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <returns>A new instance of 1D <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New1D(int32 width, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget, int32 arraySize = 1)
{
return New1D(width, format, textureFlags, 1, arraySize);
}
/// <summary>
/// Creates a new 1D <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <returns>A new instance of 1D <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New1D(int32 width, int32 mipCount, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget, int32 arraySize = 1)
{
return New1D(width, format, textureFlags, mipCount, arraySize);
}
/// <summary>
/// Creates a new 1D <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <returns>A new instance of 1D <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New1D(int32 width, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount, int32 arraySize);
public:
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" /> with a single mipmap.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New2D(int32 width, int32 height, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget, int32 arraySize = 1)
{
return New2D(width, height, 1, format, textureFlags, arraySize);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <param name="msaaLevel">The MSAA Level.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New2D(int32 width, int32 height, int32 mipCount, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget, int32 arraySize = 1, MSAALevel msaaLevel = MSAALevel::None)
{
return New2D(width, height, format, textureFlags, mipCount, arraySize, msaaLevel);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="arraySize">Size of the texture 2D array, default to 1.</param>
/// <param name="msaaLevel">The MSAA Level.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New2D(int32 width, int32 height, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount, int32 arraySize, MSAALevel msaaLevel = MSAALevel::None);
public:
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" /> with a single mipmap.
/// </summary>
/// <param name="size">The size (width, height and depth).</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New3D(const Vector3& size, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget);
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" /> with a single mipmap.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="depth">The depth.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New3D(int32 width, int32 height, int32 depth, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)
{
return New3D(width, height, depth, 1, format, textureFlags);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="depth">The depth.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New3D(int32 width, int32 height, int32 depth, int32 mipCount, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)
{
return New3D(width, height, depth, format, textureFlags, mipCount);
}
/// <summary>
/// Creates a new <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="depth">The depth.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">true if the texture needs to support unordered read write.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription New3D(int32 width, int32 height, int32 depth, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount);
public:
/// <summary>
/// Creates a new Cube <see cref="GPUTextureDescription" />.
/// </summary>
/// <param name="size">The size (in pixels) of the top-level faces of the cube texture.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">The texture flags.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription" /> class.</returns>
static GPUTextureDescription NewCube(int32 size, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)
{
return NewCube(size, 1, format, textureFlags);
}
/// <summary>
/// Creates a new Cube <see cref="GPUTextureDescription"/>.
/// </summary>
/// <param name="size">The size (in pixels) of the top-level faces of the cube texture.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">The texture flags.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription"/> class.</returns>
static GPUTextureDescription NewCube(int32 size, int32 mipCount, PixelFormat format, GPUTextureFlags textureFlags = GPUTextureFlags::ShaderResource | GPUTextureFlags::RenderTarget)
{
return NewCube(size, format, textureFlags, mipCount);
}
/// <summary>
/// Creates a new Cube <see cref="GPUTextureDescription"/>.
/// </summary>
/// <param name="size">The size (in pixels) of the top-level faces of the cube texture.</param>
/// <param name="format">Describes the format to use.</param>
/// <param name="textureFlags">The texture flags.</param>
/// <param name="mipCount">Number of mipmaps for the texture. Default is 1. Use 0 to allocate full mip chain.</param>
/// <returns>A new instance of <see cref="GPUTextureDescription"/> class.</returns>
static GPUTextureDescription NewCube(int32 size, PixelFormat format, GPUTextureFlags textureFlags, int32 mipCount);
public:
/// <summary>
/// Gets the staging description for this instance.
/// </summary>
/// <returns>A staging texture description</returns>
GPUTextureDescription ToStagingUpload() const
{
auto copy = *this;
copy.Flags = GPUTextureFlags::None;
copy.Usage = GPUResourceUsage::StagingUpload;
return copy;
}
/// <summary>
/// Gets the staging description for this instance.
/// </summary>
/// <returns>A staging texture description</returns>
GPUTextureDescription ToStagingReadback() const
{
auto copy = *this;
copy.Flags = GPUTextureFlags::None;
copy.Usage = GPUResourceUsage::StagingReadback;
return copy;
}
public:
/// <summary>
/// Compares with other instance of GPUTextureDescription
/// </summary>
/// <param name="other">The other object to compare.</param>
/// <returns>True if objects are the same, otherwise false.</returns>
bool Equals(const GPUTextureDescription& other) const;
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="other">The other description.</param>
/// <returns>The result of the operator.</returns>
FORCE_INLINE bool operator==(const GPUTextureDescription& other) const
{
return Equals(other);
}
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="other">The other description.</param>
/// <returns>The result of the operator.</returns>
FORCE_INLINE bool operator!=(const GPUTextureDescription& other) const
{
return !Equals(other);
}
public:
String ToString() const;
};
uint32 GetHash(const GPUTextureDescription& key);

View File

@@ -0,0 +1,64 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Content/Storage/FlaxStorage.h"
class GPUTexture;
class Task;
/// <summary>
/// Interface for objects that can manage streamable texture
/// </summary>
class FLAXENGINE_API ITextureOwner
{
public:
/// <summary>
/// Gets the texture owner mutex used to synchronize texture logic.
/// </summary>
/// <returns>The mutex.</returns>
virtual CriticalSection* GetOwnerLocker() const = 0;
/// <summary>
/// Get texture mip map data
/// </summary>
/// <param name="mipIndex">Mip map index</param>
/// <returns>Task that will get asset data (may be null if data already loaded).</returns>
virtual Task* RequestMipDataAsync(int32 mipIndex) = 0;
/// <summary>
/// Prepares texture data. May lock data chunks to be keep in cache for a while.
/// </summary>
virtual FlaxStorage::LockData LockData() = 0;
/// <summary>
/// Gets texture mip map data. May fail if not data requested. See RequestMipDataAsync.
/// </summary>
/// <param name="mipIndex">Mip map index</param>
/// <param name="data">Result data</param>
virtual void GetMipData(int32 mipIndex, BytesContainer& data) const = 0;
/// <summary>
/// Gets texture mip map data. Performs loading if data is not in memory (may stall the callee thread).
/// </summary>
/// <param name="mipIndex">Mip map index</param>
/// <param name="data">Result data</param>
virtual void GetMipDataWithLoading(int32 mipIndex, BytesContainer& data) const
{
GetMipData(mipIndex, data);
}
/// <summary>
/// Gets texture mip map data row and slice pitch. Cna be used to override the default values.
/// </summary>
/// <param name="mipIndex">Mip map index</param>
/// <param name="rowPitch">Data row pitch (in bytes).</param>
/// <param name="slicePitch">Data slice pitch (in bytes).</param>
/// <returns>True if has a custom row/slice pitch values, otherwise false (to use default values).</returns>
virtual bool GetMipDataCustomPitch(int32 mipIndex, uint32& rowPitch, uint32& slicePitch) const
{
return false;
}
};

View File

@@ -0,0 +1,373 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "StreamingTexture.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Streaming/StreamingGroup.h"
#include "Engine/Content/Loading/ContentLoadingManager.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
StreamingTexture::StreamingTexture(ITextureOwner* parent, const String& name)
: StreamableResource(StreamingGroups::Instance()->Textures())
, _owner(parent)
, _texture(nullptr)
, _streamingTasksCount(0)
, _isBlockCompressed(false)
{
ASSERT(_owner != nullptr);
// Always have created texture object
ASSERT(GPUDevice::Instance);
_texture = GPUDevice::Instance->CreateTexture(name);
ASSERT(_texture != nullptr);
_header.MipLevels = 0;
}
StreamingTexture::~StreamingTexture()
{
UnloadTexture();
SAFE_DELETE(_texture);
ASSERT(_streamingTasksCount == 0);
}
bool StreamingTexture::Create(const TextureHeader& header)
{
// Validate header (further validation is performed by the Texture.Init)
if (header.MipLevels > GPU_MAX_TEXTURE_MIP_LEVELS
|| Math::IsNotInRange(header.Width, 1, GPU_MAX_TEXTURE_SIZE)
|| Math::IsNotInRange(header.Height, 1, GPU_MAX_TEXTURE_SIZE)
)
{
LOG(Warning, "Invalid texture header.");
return true;
}
ASSERT(_texture);
ScopeLock lock(_owner->GetOwnerLocker());
if (IsInitialized())
{
_texture->ReleaseGPU();
}
// Cache header
// Note: by caching header we assume that streaming texture has been initialized.
// Then we can start it's streaming so it may be allocated later (using texture.Init)
// But this may not happen because resources may be created and loaded but not always allocated.
// That's one of the main advantages of the current resources streaming system.
_header = header;
_isBlockCompressed = PixelFormatExtensions::IsCompressed(_header.Format);
// Request resource streaming
#if GPU_ENABLE_TEXTURES_STREAMING
bool isDynamic = !_header.NeverStream;
#else
bool isDynamic = false;
#endif
startStreaming(isDynamic);
return false;
}
void StreamingTexture::UnloadTexture()
{
ScopeLock lock(_owner->GetOwnerLocker());
// Release
_texture->ReleaseGPU();
_header.MipLevels = 0;
ASSERT(_streamingTasksCount == 0);
}
uint64 StreamingTexture::GetTotalMemoryUsage() const
{
const uint64 arraySize = _header.IsCubeMap ? 6 : 1;
return CalculateTextureMemoryUsage(_header.Format, _header.Width, _header.Height, _header.MipLevels) * arraySize;
}
bool StreamingTexture::CanBeUpdated() const
{
// Streaming Texture cannot be updated if:
// - is not initialized
// - mip data uploading job running
// - resize texture job running
return IsInitialized() && Platform::AtomicRead(&_streamingTasksCount) == 0;
}
class StreamTextureResizeTask : public GPUTask
{
private:
StreamingTexture* _streamingTexture;
GPUTexture* _newTexture;
int32 _uploadedMipCount;
public:
StreamTextureResizeTask(StreamingTexture* texture, GPUTexture* newTexture)
: GPUTask(Type::CopyResource)
, _streamingTexture(texture)
, _newTexture(newTexture)
{
Platform::InterlockedIncrement(&_streamingTexture->_streamingTasksCount);
}
~StreamTextureResizeTask()
{
SAFE_DELETE_GPU_RESOURCE(_newTexture);
}
protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
if (_streamingTexture == nullptr)
return Result::MissingResources;
// Copy all shared mips from previous texture to the new one
GPUTexture* dstTexture = _newTexture;
const int32 dstMips = dstTexture->MipLevels();
GPUTexture* srcTexture = _streamingTexture->GetTexture();
const int32 srcMips = srcTexture->MipLevels();
const int32 mipCount = Math::Min(dstMips, srcMips);
ASSERT(mipCount > 0);
for (int32 mipIndex = 0; mipIndex < mipCount; mipIndex++)
{
context->GPU->CopySubresource(dstTexture, dstMips - mipIndex - 1, srcTexture, srcMips - mipIndex - 1);
}
_uploadedMipCount = mipCount;
return Result::Ok;
}
void OnEnd() override
{
Platform::InterlockedDecrement(&_streamingTexture->_streamingTasksCount);
// Base
GPUTask::OnEnd();
}
void OnSync() override
{
Swap(_streamingTexture->_texture, _newTexture);
_streamingTexture->GetTexture()->SetResidentMipLevels(_uploadedMipCount);
SAFE_DELETE_GPU_RESOURCE(_newTexture);
// Base
GPUTask::OnSync();
}
};
Task* StreamingTexture::UpdateAllocation(int32 residency)
{
ScopeLock lock(_owner->GetOwnerLocker());
ASSERT(_texture && IsInitialized() && Math::IsInRange(residency, 0, TotalMipLevels()));
Task* result = nullptr;
const int32 allocatedResidency = GetAllocatedResidency();
ASSERT(allocatedResidency >= 0);
if (residency == allocatedResidency)
{
// Residency won't change
}
else if (residency == 0)
{
// Release texture memory
_texture->ReleaseGPU();
}
else
{
// Use new texture object for resizing task
GPUTexture* texture = _texture;
if (allocatedResidency != 0)
{
#if GPU_ENABLE_RESOURCE_NAMING
texture = GPUDevice::Instance->CreateTexture(_texture->GetName());
#else
texture = GPUDevice::Instance->CreateTexture(String::Empty);
#endif
}
// Create texture description
const int32 mip = TotalMipLevels() - residency;
const int32 width = Math::Max(TotalWidth() >> mip, 1);
const int32 height = Math::Max(TotalHeight() >> mip, 1);
GPUTextureDescription desc;
if (IsCubeMap())
{
ASSERT(width == height);
desc = GPUTextureDescription::NewCube(width, residency, _header.Format, GPUTextureFlags::ShaderResource);
}
else
{
desc = GPUTextureDescription::New2D(width, height, residency, _header.Format, GPUTextureFlags::ShaderResource);
}
// Setup texture
if (texture->Init(desc))
{
LOG(Error, "Cannot allocate texture {0}.", ToString());
}
if (allocatedResidency != 0)
{
// Copy data from the previous texture
result = New<StreamTextureResizeTask>(this, texture);
}
else
{
// Use the new texture
_texture = texture;
}
}
return result;
}
class StreamTextureMipTask : public GPUUploadTextureMipTask
{
private:
StreamingTexture* _streamingTexture;
FlaxStorage::LockData _dataLock;
public:
StreamTextureMipTask(StreamingTexture* texture, int32 mipIndex)
: GPUUploadTextureMipTask(texture->GetTexture(), mipIndex, Span<byte>(nullptr, 0), false)
, _streamingTexture(texture)
, _dataLock(_streamingTexture->GetOwner()->LockData())
{
Platform::InterlockedIncrement(&_streamingTexture->_streamingTasksCount);
_texture.OnUnload.Bind<StreamTextureMipTask, &StreamTextureMipTask::onResourceUnload2>(this);
}
private:
void onResourceUnload2(GPUTextureReference* ref)
{
// Unlink texture
if (_streamingTexture)
{
Platform::InterlockedDecrement(&_streamingTexture->_streamingTasksCount);
_streamingTexture = nullptr;
}
}
protected:
// [GPUTask]
Result run(GPUTasksContext* context) override
{
const auto texture = _texture.Get();
if (texture == nullptr)
return Result::MissingResources;
// Ensure that texture has been allocated before this task
ASSERT(texture->IsAllocated());
// Get asset data
BytesContainer data;
const auto absoluteMipIndex = _streamingTexture->TextureMipIndexToTotalIndex(_mipIndex);
_streamingTexture->GetOwner()->GetMipData(absoluteMipIndex, data);
if (data.IsInvalid())
return Result::MissingData;
// Cache data
const int32 arraySize = texture->ArraySize();
uint32 rowPitch, slicePitch;
if (!_streamingTexture->GetOwner()->GetMipDataCustomPitch(absoluteMipIndex, rowPitch, slicePitch))
texture->ComputePitch(_mipIndex, rowPitch, slicePitch);
_data.Link(data);
// Update all array slices
const byte* dataSource = data.Get();
for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)
{
context->GPU->UpdateTexture(texture, arrayIndex, _mipIndex, dataSource, rowPitch, slicePitch);
dataSource += slicePitch;
}
return Result::Ok;
}
void OnEnd() override
{
_dataLock.Release();
if (_streamingTexture)
{
Platform::InterlockedDecrement(&_streamingTexture->_streamingTasksCount);
_streamingTexture = nullptr;
}
// Base
GPUUploadTextureMipTask::OnEnd();
}
};
Task* StreamingTexture::CreateStreamingTask(int32 residency)
{
ScopeLock lock(_owner->GetOwnerLocker());
ASSERT(_texture && IsInitialized() && Math::IsInRange(residency, 0, TotalMipLevels()));
Task* result = nullptr;
// Switch if go up or down with residency
const int32 mipsCount = residency - GetCurrentResidency();
if (mipsCount > 0)
{
// Create tasks collection
const auto startMipIndex = TotalMipLevels() - _texture->ResidentMipLevels() - 1;
const auto endMipIndex = startMipIndex - mipsCount;
for (int32 mipIndex = startMipIndex; mipIndex > endMipIndex; mipIndex--)
{
ASSERT(mipIndex >= 0 && mipIndex < _header.MipLevels);
// Request texture mip map data
auto task = _owner->RequestMipDataAsync(mipIndex);
if (task)
{
if (result)
result->ContinueWith(task);
else
result = task;
}
// Add upload data task
const int32 allocatedMipIndex = TotalIndexToTextureMipIndex(mipIndex);
task = New<StreamTextureMipTask>(this, allocatedMipIndex);
if (result)
result->ContinueWith(task);
else
result = task;
}
ASSERT(result);
}
else
{
ASSERT(IsInMainThread());
// Check if trim the mips down to 0 (full texture release)
if (residency == 0)
{
// Do the quick data release
_texture->ReleaseGPU();
}
else
{
// TODO: create task for reducing texture quality, or update SRV now (it's MainThread now so it's safe)
MISSING_CODE("add support for streaming quality down");
}
}
return result;
}

View File

@@ -0,0 +1,238 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "GPUTexture.h"
#include "ITextureOwner.h"
#include "Engine/Streaming/StreamableResource.h"
#include "Types.h"
/// <summary>
/// GPU texture object which can change it's resolution (quality) at runtime
/// </summary>
class FLAXENGINE_API StreamingTexture : public Object, public StreamableResource
{
friend class StreamTextureMipTask;
friend class StreamTextureResizeTask;
protected:
ITextureOwner* _owner;
GPUTexture* _texture;
TextureHeader _header;
volatile mutable int64 _streamingTasksCount;
bool _isBlockCompressed;
public:
/// <summary>
/// Init
/// </summary>
/// <param name="owner">Parent object</param>
/// <param name="name">Texture object name</param>
StreamingTexture(ITextureOwner* owner, const String& name);
/// <summary>
/// Destructor
/// </summary>
~StreamingTexture();
public:
/// <summary>
/// Gets the owner.
/// </summary>
/// <returns>The owner.</returns>
FORCE_INLINE ITextureOwner* GetOwner() const
{
return _owner;
}
/// <summary>
/// Gets texture object handle
/// </summary>
/// <returns>Texture object</returns>
FORCE_INLINE GPUTexture* GetTexture() const
{
return _texture;
}
/// <summary>
/// Gets texture size of Vector2::Zero if not loaded
/// </summary>
/// <returns>Texture size</returns>
FORCE_INLINE Vector2 Size() const
{
return _texture->Size();
}
public:
/// <summary>
/// Gets a value indicating whether this instance is initialized.
/// </summary>
FORCE_INLINE bool IsInitialized() const
{
return _header.MipLevels > 0;
}
/// <summary>
/// Gets total texture width (in texels)
/// </summary>
/// <returns>Texture width</returns>
FORCE_INLINE int32 TotalWidth() const
{
return _header.Width;
}
/// <summary>
/// Gets total texture height (in texels)
/// </summary>
/// <returns>Texture width</returns>
FORCE_INLINE int32 TotalHeight() const
{
return _header.Height;
}
/// <summary>
/// Gets total texture array size
/// </summary>
/// <returns>Texture array size</returns>
FORCE_INLINE int32 TotalArraySize() const
{
return IsCubeMap() ? 6 : 1;
}
/// <summary>
/// Gets total texture mip levels count
/// </summary>
/// <returns>Texture mip levels</returns>
FORCE_INLINE int32 TotalMipLevels() const
{
return _header.MipLevels;
}
/// <summary>
/// Returns texture format type
/// </summary>
/// <returns>Texture format type</returns>
FORCE_INLINE TextureFormatType GetFormatType() const
{
return _header.Type;
}
/// <summary>
/// Returns true if it's a cube map texture
/// </summary>
/// <returns>True if i's a cubemap</returns>
FORCE_INLINE bool IsCubeMap() const
{
return _header.IsCubeMap;
}
/// <summary>
/// Returns true if texture cannot be used during GPU resources streaming system
/// </summary>
/// <returns>True if texture cannot be used during GPU resources streaming system</returns>
FORCE_INLINE bool NeverStream() const
{
return _header.NeverStream;
}
/// <summary>
/// Gets the texture header.
/// </summary>
/// <returns>Header</returns>
FORCE_INLINE const TextureHeader* GetHeader() const
{
return &_header;
}
/// <summary>
/// Gets a boolean indicating whether this <see cref="StreamingTexture"/> is a using a block compress format (BC1, BC2, BC3, BC4, BC5, BC6H, BC7).
/// </summary>
FORCE_INLINE bool IsBlockCompressed() const
{
return _isBlockCompressed;
}
public:
/// <summary>
/// Converts allocated texture mip index to the absolute mip map index.
/// </summary>
/// <param name="textureMipIndex">Index of the texture mip.</param>
/// <returns>The index of the mip map.</returns>
FORCE_INLINE int32 TextureMipIndexToTotalIndex(int32 textureMipIndex) const
{
const int32 missingMips = TotalMipLevels() - _texture->MipLevels();
return textureMipIndex + missingMips;
}
/// <summary>
/// Converts absolute mip map index to the allocated texture mip index.
/// </summary>
/// <param name="mipIndex">Index of the texture mip.</param>
/// <returns>The index of the mip map.</returns>
FORCE_INLINE int32 TotalIndexToTextureMipIndex(int32 mipIndex) const
{
const int32 missingMips = TotalMipLevels() - _texture->MipLevels();
return mipIndex - missingMips;
}
public:
/// <summary>
/// Creates new texture
/// </summary>
/// <param name="header">Texture header</param>
/// <returns>True if cannot create texture, otherwise false</returns>
bool Create(const TextureHeader& header);
/// <summary>
/// Release texture
/// </summary>
void UnloadTexture();
/// <summary>
/// Gets the total memory usage that texture may have in use (if loaded to the maximum quality).
/// Exact value may differ due to memory alignment and resource allocation policy.
/// </summary>
/// <returns>The amount of bytes.</returns>
uint64 GetTotalMemoryUsage() const;
public:
FORCE_INLINE GPUTexture* operator->() const
{
return _texture;
}
public:
// [Object]
String ToString() const override
{
return _texture->ToString();
}
// [StreamableResource]
int32 GetMaxResidency() const override
{
return _header.MipLevels;
}
int32 GetCurrentResidency() const override
{
return _texture->ResidentMipLevels();
}
int32 GetAllocatedResidency() const override
{
return _texture->MipLevels();
}
bool CanBeUpdated() const override;
Task* UpdateAllocation(int32 residency) override;
Task* CreateStreamingTask(int32 residency) override;
};

View File

@@ -0,0 +1,401 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "TextureBase.h"
#include "TextureData.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
REGISTER_BINARY_ASSET_ABSTRACT(TextureBase, "FlaxEngine.TextureBase");
TextureBase::TextureBase(const SpawnParams& params, const AssetInfo* info)
: BinaryAsset(params, info)
, _texture(this, info->Path)
, _customData(nullptr)
, _parent(this)
{
}
BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch)
{
BytesContainer result;
if (IsVirtual())
{
if (_customData == nullptr)
{
LOG(Error, "Missing virtual texture init data.");
return result;
}
// Get description
rowPitch = _customData->Mips[mipIndex].RowPitch;
slicePitch = _customData->Mips[mipIndex].SlicePitch;
}
else
{
// Wait for the asset header to be loaded
if (WaitForLoaded())
return result;
// Get description
uint32 rowPitch1, slicePitch1;
const int32 mipWidth = Math::Max(1, Width() >> mipIndex);
const int32 mipHeight = Math::Max(1, Height() >> mipIndex);
RenderTools::ComputePitch(Format(), mipWidth, mipHeight, rowPitch1, slicePitch1);
rowPitch = rowPitch1;
slicePitch = slicePitch1;
// Ensure to have chunk loaded
if (LoadChunk(calculateChunkIndex(mipIndex)))
return result;
}
// Get data
GetMipData(mipIndex, result);
return result;
}
bool TextureBase::GetTextureData(TextureData& result, bool copyData)
{
if (!IsVirtual() && WaitForLoaded())
{
LOG(Error, "Asset load failed.");
return true;
}
// Setup description
result.Width = _texture.TotalWidth();
result.Height = _texture.TotalHeight();
result.Depth = 1;
result.Format = _texture.GetHeader()->Format;
result.Items.Resize(_texture.TotalArraySize());
// Setup mips data
for (int32 arraySlice = 0; arraySlice < result.Items.Count(); arraySlice++)
{
auto& slice = result.Items[arraySlice];
slice.Mips.Resize(_texture.TotalMipLevels());
for (int32 mipIndex = 0; mipIndex < slice.Mips.Count(); mipIndex++)
{
auto& mip = slice.Mips[mipIndex];
int32 rowPitch, slicePitch;
BytesContainer mipData = GetMipData(mipIndex, rowPitch, slicePitch);
if (mipData.IsInvalid())
{
LOG(Error, "Failed to get texture mip data.");
return true;
}
if (mipData.Length() != slicePitch * _texture.TotalArraySize())
{
LOG(Error, "Invalid custom texture data (slice pitch * array size is different than data bytes count).");
return true;
}
mip.RowPitch = rowPitch;
mip.DepthPitch = slicePitch;
mip.Lines = Math::Max(1, Height() >> mipIndex);
if (copyData)
mip.Data.Copy(mipData.Get() + (arraySlice * slicePitch), slicePitch);
else
mip.Data.Link(mipData.Get() + (arraySlice * slicePitch), slicePitch);
}
}
return false;
}
bool TextureBase::Init(InitData* initData)
{
// Validate state
if (!IsVirtual())
{
Log::InvalidOperationException();
return true;
}
if (initData->Format == PixelFormat::Unknown ||
Math::IsNotInRange(initData->Width, 1, GPU_MAX_TEXTURE_SIZE) ||
Math::IsNotInRange(initData->Height, 1, GPU_MAX_TEXTURE_SIZE) ||
Math::IsNotInRange(initData->ArraySize, 1, GPU_MAX_TEXTURE_ARRAY_SIZE) ||
Math::IsNotInRange(initData->Mips.Count(), 1, GPU_MAX_TEXTURE_MIP_LEVELS))
{
Log::ArgumentOutOfRangeException();
return true;
}
// Release texture
_texture.UnloadTexture();
// Prepare descriptor
if (_customData != nullptr)
Delete(_customData);
_customData = initData;
// Create texture
TextureHeader header;
header.Format = initData->Format;
header.Width = initData->Width;
header.Height = initData->Height;
header.IsCubeMap = initData->ArraySize == 6;
header.MipLevels = initData->Mips.Count();
header.IsSRGB = false;
header.Type = TextureFormatType::ColorRGBA;
header.NeverStream = true;
if (_texture.Create(header))
{
LOG(Warning, "Cannot initialize texture.");
return true;
}
return false;
}
bool TextureBase::Init(void* ptr)
{
struct InternalInitData
{
PixelFormat Format;
int32 Width;
int32 Height;
int32 ArraySize;
int32 MipLevels;
int32 DataRowPitch[14];
int32 DataSlicePitch[14];
byte* Data[14];
};
auto initDataObj = (InternalInitData*)ptr;
auto initData = New<InitData>();
initData->Format = initDataObj->Format;
initData->Width = initDataObj->Width;
initData->Height = initDataObj->Height;
initData->ArraySize = initDataObj->ArraySize;
initData->Mips.Resize(initDataObj->MipLevels);
for (int32 mipIndex = 0; mipIndex < initDataObj->MipLevels; mipIndex++)
{
auto& mip = initData->Mips[mipIndex];
mip.RowPitch = initDataObj->DataRowPitch[mipIndex];
mip.SlicePitch = initDataObj->DataSlicePitch[mipIndex];
mip.Data.Copy(initDataObj->Data[mipIndex], mip.SlicePitch * initData->ArraySize);
}
return Init(initData);
}
int32 TextureBase::calculateChunkIndex(int32 mipIndex) const
{
// Mips are in 0-13 chunks
return mipIndex;
}
CriticalSection* TextureBase::GetOwnerLocker() const
{
return &_parent->Locker;
}
void TextureBase::unload(bool isReloading)
{
if (!isReloading)
{
// Release texture
_texture.UnloadTexture();
SAFE_DELETE(_customData);
}
}
Task* TextureBase::RequestMipDataAsync(int32 mipIndex)
{
if (_customData)
return nullptr;
auto chunkIndex = calculateChunkIndex(mipIndex);
return (Task*)_parent->RequestChunkDataAsync(chunkIndex);
}
FlaxStorage::LockData TextureBase::LockData()
{
return _parent->Storage ? _parent->Storage->Lock() : FlaxStorage::LockData::Invalid;
}
void TextureBase::GetMipData(int32 mipIndex, BytesContainer& data) const
{
if (_customData)
{
data.Link(_customData->Mips[mipIndex].Data);
return;
}
auto chunkIndex = calculateChunkIndex(mipIndex);
_parent->GetChunkData(chunkIndex, data);
}
void TextureBase::GetMipDataWithLoading(int32 mipIndex, BytesContainer& data) const
{
if (_customData)
{
data.Link(_customData->Mips[mipIndex].Data);
return;
}
const auto chunkIndex = calculateChunkIndex(mipIndex);
_parent->LoadChunk(chunkIndex);
_parent->GetChunkData(chunkIndex, data);
}
bool TextureBase::GetMipDataCustomPitch(int32 mipIndex, uint32& rowPitch, uint32& slicePitch) const
{
bool result = _customData != nullptr;
if (result)
{
rowPitch = _customData->Mips[mipIndex].RowPitch;
slicePitch = _customData->Mips[mipIndex].SlicePitch;
}
return result;
}
bool TextureBase::InitData::GenerateMip(int32 mipIndex, bool linear)
{
// Validate input
if (mipIndex < 1 || mipIndex >= Mips.Count())
{
LOG(Warning, "Invalid mip map to generate.");
return true;
}
if (ArraySize < 1)
{
LOG(Warning, "Invalid array size.");
return true;
}
if (PixelFormatExtensions::IsCompressed(Format))
{
LOG(Warning, "Cannot generate mip map for compressed format data.");
return true;
}
const auto& srcMip = Mips[mipIndex - 1];
auto& dstMip = Mips[mipIndex];
if (srcMip.RowPitch == 0 || srcMip.SlicePitch == 0 || srcMip.Data.IsInvalid())
{
LOG(Warning, "Missing data for source mip map.");
return true;
}
PROFILE_CPU_NAMED("Texture.GenerateMip");
// Allocate data
const int32 dstMipWidth = Math::Max(1, Width >> mipIndex);
const int32 dstMipHeight = Math::Max(1, Height >> mipIndex);
const int32 srcMipWidth = Math::Max(1, Width >> (mipIndex - 1));
const int32 srcMipHeight = Math::Max(1, Height >> (mipIndex - 1));
const int32 pixelStride = PixelFormatExtensions::SizeInBytes(Format);
dstMip.RowPitch = dstMipWidth * pixelStride;
dstMip.SlicePitch = dstMip.RowPitch * dstMipHeight;
dstMip.Data.Allocate(dstMip.SlicePitch * ArraySize);
// Perform filtering
if (linear)
{
switch (Format)
{
// 4 component, 32 bit with 8 bits per component - use Color32 type
case PixelFormat::R8G8B8A8_SInt:
case PixelFormat::R8G8B8A8_Typeless:
case PixelFormat::R8G8B8A8_SNorm:
case PixelFormat::R8G8B8A8_UInt:
case PixelFormat::R8G8B8A8_UNorm:
case PixelFormat::R8G8B8A8_UNorm_sRGB:
case PixelFormat::R8G8_B8G8_UNorm:
case PixelFormat::B8G8R8A8_Typeless:
case PixelFormat::B8G8R8A8_UNorm:
case PixelFormat::B8G8R8A8_UNorm_sRGB:
case PixelFormat::B8G8R8X8_Typeless:
case PixelFormat::B8G8R8X8_UNorm:
case PixelFormat::B8G8R8X8_UNorm_sRGB:
{
// Linear downscale filter
for (int32 arrayIndex = 0; arrayIndex < ArraySize; arrayIndex++)
{
const byte* dstData = dstMip.Data.Get() + arrayIndex * dstMip.SlicePitch;
const byte* srcData = srcMip.Data.Get() + arrayIndex * srcMip.SlicePitch;
for (int32 y = 0; y < dstMipHeight; y++)
{
for (int32 x = 0; x < dstMipWidth; x++)
{
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
#define SAMPLE(var, dx, dy) const auto var = Color(*(Color32*)(srcData + ((y * 2 + (dy)) * srcMip.RowPitch + x * pixelStride * 2 + (pixelStride * (dx)))))
SAMPLE(v00, 0, 0);
SAMPLE(v01, 0, 1);
SAMPLE(v10, 1, 0);
SAMPLE(v11, 1, 1);
#undef SAMPLE
*(Color32*)(dstData + dstIndex) = Color32((v00 + v01 + v10 + v11) * 0.25f);
}
}
}
break;
}
default:
LOG(Error, "Unsupported texture data format {0} for mip map generation.", (int32)Format);
return true;
}
}
else
{
// Point downscale filter
for (int32 arrayIndex = 0; arrayIndex < ArraySize; arrayIndex++)
{
byte* dstData = dstMip.Data.Get() + arrayIndex * dstMip.SlicePitch;
const byte* srcData = srcMip.Data.Get() + arrayIndex * srcMip.SlicePitch;
for (int32 y = 0; y < dstMipHeight; y++)
{
for (int32 x = 0; x < dstMipWidth; x++)
{
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
const auto srcIndex = y * 2 * srcMip.RowPitch + x * pixelStride * 2;
Platform::MemoryCopy(dstData + dstIndex, srcData + srcIndex, pixelStride);
}
}
}
// Fix right and bottom edges to preserve the original values
for (int32 arrayIndex = 0; arrayIndex < ArraySize; arrayIndex++)
{
byte* dstData = dstMip.Data.Get() + arrayIndex * dstMip.SlicePitch;
const byte* srcData = srcMip.Data.Get() + arrayIndex * srcMip.SlicePitch;
for (int32 y = 0; y < dstMipHeight - 1; y++)
{
const int32 x = dstMipWidth - 1;
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
const auto srcIndex = y * 2 * srcMip.RowPitch + x * pixelStride * 2 + pixelStride;
Platform::MemoryCopy(dstData + dstIndex, srcData + srcIndex, pixelStride);
}
for (int32 x = 0; x < dstMipWidth - 1; x++)
{
const int32 y = dstMipHeight - 1;
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
const auto srcIndex = (y * 2 + 1) * srcMip.RowPitch + x * pixelStride * 2;
Platform::MemoryCopy(dstData + dstIndex, srcData + srcIndex, pixelStride);
}
{
const int32 x = dstMipWidth - 1;
const int32 y = dstMipHeight - 1;
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
const auto srcIndex = (y * 2 + 1) * srcMip.RowPitch + x * pixelStride * 2 + pixelStride;
Platform::MemoryCopy(dstData + dstIndex, srcData + srcIndex, pixelStride);
}
}
}
return false;
}

View File

@@ -0,0 +1,238 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Content/BinaryAsset.h"
#include "StreamingTexture.h"
#include "Engine/Core/Log.h"
/// <summary>
/// Base class for <see cref="Texture"/>, <see cref="SpriteAtlas"/>, <see cref="IESProfile"/> and other assets that can contain texture data.
/// </summary>
/// <seealso cref="FlaxEngine.BinaryAsset" />
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API TextureBase : public BinaryAsset, public ITextureOwner
{
DECLARE_ASSET_HEADER(TextureBase);
public:
static const uint32 TexturesSerializedVersion = 4;
public:
/// <summary>
/// The texture init data (external source).
/// </summary>
struct FLAXENGINE_API InitData
{
struct MipData
{
BytesContainer Data;
uint32 RowPitch;
uint32 SlicePitch;
};
PixelFormat Format;
int32 Width;
int32 Height;
int32 ArraySize;
Array<MipData, FixedAllocation<14>> Mips;
/// <summary>
/// Generates the mip map data.
/// </summary>
/// <remarks>
/// Compressed formats are not supported. Point filter supports all types and preserves texture edge values.
/// </remarks>
/// <param name="mipIndex">Index of the mip.</param>
/// <param name="linear">True if use linear filer, otherwise point filtering.</param>
/// <returns>True if failed, otherwise false.</returns>
bool GenerateMip(int32 mipIndex, bool linear);
};
protected:
StreamingTexture _texture;
InitData* _customData;
private:
BinaryAsset* _parent;
public:
/// <summary>
/// Gets the streaming texture object handle.
/// </summary>
FORCE_INLINE const StreamingTexture* StreamingTexture() const
{
return &_texture;
}
/// <summary>
/// Gets GPU texture object allocated by the asset.
/// </summary>
API_PROPERTY() FORCE_INLINE GPUTexture* GetTexture() const
{
return _texture.GetTexture();
}
/// <summary>
/// Gets the texture data format.
/// </summary>
API_PROPERTY() FORCE_INLINE PixelFormat Format() const
{
return _texture.GetHeader()->Format;
}
/// <summary>
/// Gets the total width of the texture. Actual resident size may be different due to dynamic content streaming. Returns 0 if texture is not loaded.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 Width() const
{
return _texture.TotalWidth();
}
/// <summary>
/// Gets the total height of the texture. Actual resident size may be different due to dynamic content streaming. Returns 0 if texture is not loaded.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 Height() const
{
return _texture.TotalHeight();
}
/// <summary>
/// Gets the total size of the texture. Actual resident size may be different due to dynamic content streaming. Returns Vector2::Zero if texture is not loaded.
/// </summary>
API_PROPERTY() FORCE_INLINE Vector2 Size() const
{
return Vector2(static_cast<float>(_texture.TotalWidth()), static_cast<float>(_texture.TotalHeight()));
}
/// <summary>
/// Gets the total array size of the texture.
/// </summary>
API_PROPERTY() int32 GetArraySize() const
{
return StreamingTexture()->TotalArraySize();
}
/// <summary>
/// Gets the total mip levels count of the texture. Actual resident mipmaps count may be different due to dynamic content streaming.
/// </summary>
API_PROPERTY() int32 GetMipLevels() const
{
return StreamingTexture()->TotalMipLevels();
}
/// <summary>
/// Gets the current mip levels count of the texture that are on GPU ready to use.
/// </summary>
API_PROPERTY() int32 GetResidentMipLevels() const
{
return GetTexture()->ResidentMipLevels();
}
/// <summary>
/// Gets the amount of the memory used by this resource. Exact value may differ due to memory alignment and resource allocation policy.
/// </summary>
API_PROPERTY() uint64 GetCurrentMemoryUsage() const
{
return GetTexture()->GetMemoryUsage();
}
/// <summary>
/// Gets the total memory usage that texture may have in use (if loaded to the maximum quality). Exact value may differ due to memory alignment and resource allocation policy.
/// </summary>
API_PROPERTY() uint64 GetTotalMemoryUsage() const
{
return StreamingTexture()->GetTotalMemoryUsage();
}
public:
/// <summary>
/// Gets the mip data.
/// </summary>
/// <param name="mipIndex">The mip index (zero-based).</param>
/// <param name="rowPitch">The data row pitch (in bytes).</param>
/// <param name="slicePitch">The data slice pitch (in bytes).</param>
/// <returns>The mip-map data or empty if failed to get it.</returns>
API_FUNCTION() BytesContainer GetMipData(int32 mipIndex, API_PARAM(out) int32& rowPitch, API_PARAM(out) int32& slicePitch);
/// <summary>
/// Loads the texture data from the asset.
/// </summary>
/// <param name="result">The result data.</param>
/// <param name="copyData">True if copy asset data to the result buffer, otherwise texture data will be linked to the internal storage (then the data is valid while asset is loaded and there is no texture data copy operations - faster).</param>
/// <returns>True if cannot load data, otherwise false.</returns>
bool GetTextureData(TextureData& result, bool copyData = true);
/// <summary>
/// Initializes the texture with specified initialize data source (asset must be virtual).
/// </summary>
/// <param name="initData">The initialize data (allocated by the called, will be used and released by the asset internal layer).</param>
/// <returns>True if failed, otherwise false.</returns>
bool Init(InitData* initData);
protected:
virtual int32 calculateChunkIndex(int32 mipIndex) const;
private:
// Internal bindings
API_FUNCTION(NoProxy) bool Init(void* ptr);
public:
// [ITextureOwner]
CriticalSection* GetOwnerLocker() const override;
Task* RequestMipDataAsync(int32 mipIndex) override;
FlaxStorage::LockData LockData() override;
void GetMipData(int32 mipIndex, BytesContainer& data) const override;
void GetMipDataWithLoading(int32 mipIndex, BytesContainer& data) const override;
bool GetMipDataCustomPitch(int32 mipIndex, uint32& rowPitch, uint32& slicePitch) const override;
protected:
// [BinaryAsset]
bool init(AssetInitData& initData) override
{
// Skip for virtual assets
if (IsVirtual())
return false;
// Validate
if (initData.SerializedVersion != 4)
{
LOG(Error, "Invalid serialized texture version.");
return true;
}
if (initData.CustomData.Length() != sizeof(TextureHeader))
{
LOG(Error, "Missing texture header.");
return true;
}
// Load header
TextureHeader header;
Platform::MemoryCopy(&header, initData.CustomData.Get(), sizeof(TextureHeader));
// Create texture
if (_texture.Create(header))
{
LOG(Error, "Cannot initialize texture.");
return true;
}
return false;
}
LoadResult load() override
{
// Loading textures is very fast xD
return LoadResult::Ok;
}
void unload(bool isReloading) override;
};

View File

@@ -0,0 +1,145 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Graphics/Config.h"
#include "Engine/Graphics/PixelFormat.h"
/// <summary>
/// Single texture mip map entry data.
/// </summary>
class FLAXENGINE_API TextureMipData
{
public:
uint32 RowPitch;
uint32 DepthPitch;
uint32 Lines;
BytesContainer Data;
template<typename T>
T& Get(int32 x, int32 y)
{
return *(T*)(Data.Get() + y * RowPitch + x * sizeof(T));
}
template<typename T>
const T& Get(int32 x, int32 y) const
{
return *(const T*)(Data.Get() + y * RowPitch + x * sizeof(T));
}
};
/// <summary>
/// Texture data container (used to keep data downloaded from the GPU).
/// </summary>
class FLAXENGINE_API TextureData
{
public:
/// <summary>
/// Single entry of the texture array. Contains collection of mip maps.
/// </summary>
struct FLAXENGINE_API ArrayEntry
{
/// <summary>
/// The mip maps collection.
/// </summary>
Array<TextureMipData, FixedAllocation<GPU_MAX_TEXTURE_MIP_LEVELS>> Mips;
};
public:
/// <summary>
/// Init
/// </summary>
TextureData()
{
}
/// <summary>
/// Destructor
/// </summary>
~TextureData()
{
}
public:
/// <summary>
/// Top level texture width (in pixels).
/// </summary>
int32 Width = 0;
/// <summary>
/// Top level texture height (in pixels).
/// </summary>
int32 Height = 0;
/// <summary>
/// Top level texture depth (in pixels).
/// </summary>
int32 Depth = 0;
/// <summary>
/// The texture data format.
/// </summary>
PixelFormat Format = PixelFormat::Unknown;
/// <summary>
/// The items collection (depth slices or array slices).
/// </summary>
Array<ArrayEntry, InlinedAllocation<6>> Items;
public:
/// <summary>
/// Gather texture data
/// </summary>
/// <param name="arrayIndex">Texture array index</param>
/// <param name="mipLevel">Mip map index</param>
/// <returns>Result data</returns>
TextureMipData* GetData(int32 arrayIndex, int32 mipLevel)
{
return &Items[arrayIndex].Mips[mipLevel];
}
/// <summary>
/// Gather texture data
/// </summary>
/// <param name="arrayIndex">Texture array index</param>
/// <param name="mipLevel">Mip map index</param>
/// <returns>Result data</returns>
const TextureMipData* GetData(int32 arrayIndex, int32 mipLevel) const
{
return &Items[arrayIndex].Mips[mipLevel];
}
/// <summary>
/// Gets amount of textures in the array
/// </summary>
/// <returns>Array size</returns>
int32 GetArraySize() const
{
return Items.Count();
}
/// <summary>
/// Gets amount of mip maps in the textures
/// </summary>
/// <returns>Amount of mip levels</returns>
int32 GetMipLevels() const
{
return Items.HasItems() ? Items[0].Mips.Count() : 0;
}
/// <summary>
/// Clear data
/// </summary>
void Clear()
{
Items.Resize(0, false);
}
};

View File

@@ -0,0 +1,57 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Types/BaseTypes.h"
#include "Types.h"
/// <summary>
/// Texture utilities class
/// </summary>
class TextureUtils
{
public:
static PixelFormat ToPixelFormat(const TextureFormatType format, int32 width, int32 height, bool canCompress)
{
const bool canUseBlockCompression = width % 4 == 0 && height % 4 == 0;
if (canCompress && canUseBlockCompression)
{
switch (format)
{
case TextureFormatType::ColorRGB:
return PixelFormat::BC1_UNorm;
case TextureFormatType::ColorRGBA:
return PixelFormat::BC3_UNorm;
case TextureFormatType::NormalMap:
return PixelFormat::BC5_UNorm;
case TextureFormatType::GrayScale:
return PixelFormat::BC4_UNorm;
case TextureFormatType::HdrRGBA:
return PixelFormat::BC7_UNorm;
case TextureFormatType::HdrRGB:
return PixelFormat::BC6H_Uf16;
default:
return PixelFormat::Unknown;
}
}
switch (format)
{
case TextureFormatType::ColorRGB:
return PixelFormat::R8G8B8A8_UNorm;
case TextureFormatType::ColorRGBA:
return PixelFormat::R8G8B8A8_UNorm;
case TextureFormatType::NormalMap:
return PixelFormat::R16G16_UNorm;
case TextureFormatType::GrayScale:
return PixelFormat::R8_UNorm;
case TextureFormatType::HdrRGBA:
return PixelFormat::R16G16B16A16_Float;
case TextureFormatType::HdrRGB:
return PixelFormat::R11G11B10_Float;
default:
return PixelFormat::Unknown;
}
}
};

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Enums.h"
#include "Engine/Core/Types/BaseTypes.h"
#include "Engine/Graphics/PixelFormat.h"
/// <summary>
/// Describes texture compression format type
/// </summary>
DECLARE_ENUM_EX_7(TextureFormatType, byte, 0, Unknown, ColorRGB, ColorRGBA, NormalMap, GrayScale, HdrRGBA, HdrRGB);
/// <summary>
/// Texture header structure
/// </summary>
struct FLAXENGINE_API TextureHeader
{
/// <summary>
/// Top mip width in pixels
/// </summary>
int32 Width;
/// <summary>
/// Top mip height in pixels
/// </summary>
int32 Height;
/// <summary>
/// Amount of mip levels
/// </summary>
int32 MipLevels;
/// <summary>
/// Texture pixels format
/// </summary>
PixelFormat Format;
/// <summary>
/// Texture compression type
/// </summary>
TextureFormatType Type;
/// <summary>
/// True if texture is a cubemap (has 6 array slices per mip).
/// </summary>
bool IsCubeMap;
/// <summary>
/// True if disable dynamic texture streaming
/// </summary>
bool NeverStream;
/// <summary>
/// True if texture contains sRGB colors data
/// </summary>
bool IsSRGB;
/// <summary>
/// The custom data to be used per texture storage layer (faster access).
/// </summary>
byte CustomData[17];
};
static_assert(sizeof(TextureHeader) == 10 * sizeof(int32), "Invalid TextureHeader size.");