Add support for using per-texture group sampler in Materials

This commit is contained in:
Wojtek Figat
2021-06-29 16:16:56 +02:00
parent add88a783b
commit a3dfb1c5d3
18 changed files with 252 additions and 45 deletions

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using FlaxEditor.Content.Settings;
using FlaxEditor.GUI;
using FlaxEngine;
namespace FlaxEditor.Surface.Archetypes
@@ -11,30 +13,77 @@ namespace FlaxEditor.Surface.Archetypes
[HideInEditor]
public static class Textures
{
/// <summary>
/// Common samplers types.
/// </summary>
public enum CommonSamplerType
internal enum CommonSamplerType
{
/// <summary>
/// The linear clamp
/// </summary>
LinearClamp = 0,
/// <summary>
/// The point clamp
/// </summary>
PointClamp = 1,
/// <summary>
/// The linear wrap
/// </summary>
LinearWrap = 2,
/// <summary>
/// The point wrap
/// </summary>
PointWrap = 3,
TextureGroup = 4,
}
internal class SampleTextureNode : SurfaceNode
{
private ComboBox _textureGroupPicker;
public SampleTextureNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
: base(id, context, nodeArch, groupArch)
{
}
public override void OnValuesChanged()
{
base.OnValuesChanged();
UpdateUI();
}
public override void OnLoaded()
{
base.OnLoaded();
UpdateUI();
}
private void UpdateUI()
{
if ((int)Values[0] == (int)CommonSamplerType.TextureGroup)
{
if (_textureGroupPicker == null)
{
_textureGroupPicker = new ComboBox
{
Location = new Vector2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * 5),
Width = 100,
Parent = this,
};
_textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged;
var groups = GameSettings.Load<StreamingSettings>();
if (groups?.TextureGroups != null)
{
for (int i = 0; i < groups.TextureGroups.Length; i++)
_textureGroupPicker.AddItem(groups.TextureGroups[i].Name);
}
}
else
{
_textureGroupPicker.Visible = true;
}
_textureGroupPicker.SelectedIndexChanged -= OnSelectedTextureGroupChanged;
_textureGroupPicker.SelectedIndex = (int)Values[2];
_textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged;
}
else if (_textureGroupPicker != null)
{
_textureGroupPicker.Visible = false;
}
ResizeAuto();
}
private void OnSelectedTextureGroupChanged(ComboBox comboBox)
{
SetValue(2, _textureGroupPicker.SelectedIndex);
}
}
/// <summary>
@@ -213,6 +262,7 @@ namespace FlaxEditor.Surface.Archetypes
new NodeArchetype
{
TypeID = 9,
Create = (id, context, arch, groupArch) => new SampleTextureNode(id, context, arch, groupArch),
Title = "Sample Texture",
Description = "Custom texture sampling",
Flags = NodeFlags.MaterialGraph,
@@ -222,6 +272,7 @@ namespace FlaxEditor.Surface.Archetypes
{
0,
-1.0f,
0,
},
Elements = new[]
{
@@ -230,7 +281,7 @@ namespace FlaxEditor.Surface.Archetypes
NodeElementArchetype.Factory.Input(2, "Level", true, typeof(float), 2, 1),
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Vector2), 3),
NodeElementArchetype.Factory.Output(0, "Color", typeof(Vector4), 4),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4 + 4, "Sampler"),
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4, 100, 0, typeof(CommonSamplerType))
}
},

View File

@@ -12,6 +12,14 @@ using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEngine
{
partial struct TextureGroup
{
internal bool IsAnisotropic => SamplerFilter == GPUSamplerFilter.Anisotropic;
}
}
namespace FlaxEditor.Utilities
{
/// <summary>

View File

@@ -10,6 +10,7 @@
/// Releases GPU resource memory and deletes object.
/// </summary>
#define SAFE_DELETE_GPU_RESOURCE(x) if (x) { (x)->DeleteObjectNow(); (x) = nullptr; }
#define SAFE_DELETE_GPU_RESOURCES(x) for (auto& e : (x)) if (e) { e->DeleteObjectNow(); e = nullptr; }
/// <summary>
/// The base class for all GPU resources.

View File

@@ -101,5 +101,10 @@ namespace FlaxEngine
/// The gameplay global.
/// </summary>
GameplayGlobal = 18,
/// <summary>
/// The texture sampler derived from texture group settings.
/// </summary>
TextureGroupSampler = 19,
}
}

View File

@@ -11,6 +11,7 @@
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Streaming/Streaming.h"
bool MaterialInfo8::operator==(const MaterialInfo8& other) const
{
@@ -153,6 +154,9 @@ const Char* ToString(MaterialParameterType value)
case MaterialParameterType::GameplayGlobal:
result = TEXT("GameplayGlobal");
break;
case MaterialParameterType::TextureGroupSampler:
result = TEXT("TextureGroupSampler");
break;
default:
result = TEXT("");
break;
@@ -169,6 +173,7 @@ Variant MaterialParameter::GetValue() const
case MaterialParameterType::Integer:
case MaterialParameterType::SceneTexture:
case MaterialParameterType::ChannelMask:
case MaterialParameterType::TextureGroupSampler:
return _asInteger;
case MaterialParameterType::Float:
return _asFloat;
@@ -209,6 +214,7 @@ void MaterialParameter::SetValue(const Variant& value)
case MaterialParameterType::Integer:
case MaterialParameterType::SceneTexture:
case MaterialParameterType::ChannelMask:
case MaterialParameterType::TextureGroupSampler:
_asInteger = (int32)value;
break;
case MaterialParameterType::Float:
@@ -446,6 +452,9 @@ void MaterialParameter::Bind(BindMeta& meta) const
}
}
break;
case MaterialParameterType::TextureGroupSampler:
meta.Context->BindSampler(_registerIndex, Streaming::GetTextureGroupSampler(_asInteger));
break;
default:
break;
}
@@ -475,6 +484,7 @@ void MaterialParameter::clone(const MaterialParameter* param)
break;
case MaterialParameterType::Integer:
case MaterialParameterType::SceneTexture:
case MaterialParameterType::TextureGroupSampler:
_asInteger = param->_asInteger;
break;
case MaterialParameterType::Float:
@@ -654,6 +664,7 @@ bool MaterialParams::Load(ReadStream* stream)
case MaterialParameterType::Integer:
case MaterialParameterType::SceneTexture:
case MaterialParameterType::ChannelMask:
case MaterialParameterType::TextureGroupSampler:
stream->ReadInt32(&param->_asInteger);
break;
case MaterialParameterType::Float:
@@ -727,6 +738,7 @@ bool MaterialParams::Load(ReadStream* stream)
break;
case MaterialParameterType::Integer:
case MaterialParameterType::SceneTexture:
case MaterialParameterType::TextureGroupSampler:
stream->ReadInt32(&param->_asInteger);
break;
case MaterialParameterType::Float:
@@ -801,6 +813,7 @@ bool MaterialParams::Load(ReadStream* stream)
case MaterialParameterType::Integer:
case MaterialParameterType::SceneTexture:
case MaterialParameterType::ChannelMask:
case MaterialParameterType::TextureGroupSampler:
stream->ReadInt32(&param->_asInteger);
break;
case MaterialParameterType::Float:
@@ -893,6 +906,7 @@ void MaterialParams::Save(WriteStream* stream)
case MaterialParameterType::Integer:
case MaterialParameterType::SceneTexture:
case MaterialParameterType::ChannelMask:
case MaterialParameterType::TextureGroupSampler:
stream->WriteInt32(param->_asInteger);
break;
case MaterialParameterType::Float:
@@ -967,6 +981,7 @@ void MaterialParams::Save(WriteStream* stream, const Array<SerializedMaterialPar
case MaterialParameterType::SceneTexture:
case MaterialParameterType::ChannelMask:
case MaterialParameterType::Integer:
case MaterialParameterType::TextureGroupSampler:
stream->WriteInt32(param.AsInteger);
break;
case MaterialParameterType::Float:

View File

@@ -123,6 +123,11 @@ enum class MaterialParameterType : byte
/// The gameplay global.
/// </summary>
GameplayGlobal = 18,
/// <summary>
/// The texture sampler derived from texture group settings.
/// </summary>
TextureGroupSampler = 19,
};
const Char* ToString(MaterialParameterType value);

View File

@@ -52,10 +52,7 @@ protected:
void Release()
{
for (int32 i = 0; i < ARRAY_COUNT(PS); i++)
{
SAFE_DELETE_GPU_RESOURCE(PS[i]);
}
SAFE_DELETE_GPU_RESOURCES(PS);
}
};

View File

@@ -863,11 +863,7 @@ GPUBufferVulkan* HelperResourcesVulkan::GetDummyVertexBuffer()
void HelperResourcesVulkan::Dispose()
{
for (int32 i = 0; i < ARRAY_COUNT(_dummyTextures); i++)
{
SAFE_DELETE_GPU_RESOURCE(_dummyTextures[i]);
}
SAFE_DELETE_GPU_RESOURCES(_dummyTextures);
SAFE_DELETE_GPU_RESOURCE(_dummyBuffer);
SAFE_DELETE_GPU_RESOURCE(_dummyVB);

View File

@@ -188,10 +188,8 @@ void AmbientOcclusionPass::Dispose()
// Delete pipeline states
SAFE_DELETE_GPU_RESOURCE(_psPrepareDepths);
SAFE_DELETE_GPU_RESOURCE(_psPrepareDepthsHalf);
for (int32 i = 0; i < ARRAY_COUNT(_psPrepareDepthMip); i++)
SAFE_DELETE_GPU_RESOURCE(_psPrepareDepthMip[i]);
for (int32 i = 0; i < ARRAY_COUNT(_psGenerate); i++)
SAFE_DELETE_GPU_RESOURCE(_psGenerate[i]);
SAFE_DELETE_GPU_RESOURCES(_psPrepareDepthMip);
SAFE_DELETE_GPU_RESOURCES(_psGenerate);
SAFE_DELETE_GPU_RESOURCE(_psSmartBlur);
SAFE_DELETE_GPU_RESOURCE(_psSmartBlurWide);
SAFE_DELETE_GPU_RESOURCE(_psNonSmartBlur);

View File

@@ -9,6 +9,7 @@
#include "Engine/Threading/Threading.h"
#include "Engine/Threading/Task.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Textures/GPUSampler.h"
#include "Engine/Serialization/Serialization.h"
namespace StreamingManagerImpl
@@ -17,6 +18,8 @@ namespace StreamingManagerImpl
int32 LastUpdateResourcesIndex = 0;
CriticalSection ResourcesLock;
Array<StreamableResource*> Resources;
Array<GPUSampler*, InlinedAllocation<32>> TextureGroupSamplers;
GPUSampler* FallbackSampler = nullptr;
}
using namespace StreamingManagerImpl;
@@ -30,6 +33,7 @@ public:
}
void Update() override;
void BeforeExit() override;
};
StreamingManagerService StreamingManagerServiceInstance;
@@ -39,6 +43,8 @@ Array<TextureGroup, InlinedAllocation<32>> Streaming::TextureGroups;
void StreamingSettings::Apply()
{
Streaming::TextureGroups = TextureGroups;
SAFE_DELETE_GPU_RESOURCES(TextureGroupSamplers);
TextureGroupSamplers.Resize(TextureGroups.Count(), false);
}
void StreamingSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -217,6 +223,13 @@ void StreamingManagerService::Update()
// TODO: add StreamingManager stats, update time per frame, updates per frame, etc.
}
void StreamingManagerService::BeforeExit()
{
SAFE_DELETE_GPU_RESOURCE(FallbackSampler);
SAFE_DELETE_GPU_RESOURCES(TextureGroupSamplers);
TextureGroupSamplers.Resize(0);
}
void Streaming::RequestStreamingUpdate()
{
PROFILE_CPU();
@@ -225,3 +238,35 @@ void Streaming::RequestStreamingUpdate()
e->RequestStreamingUpdate();
ResourcesLock.Unlock();
}
GPUSampler* Streaming::GetTextureGroupSampler(int32 index)
{
GPUSampler* sampler = nullptr;
if (index >= 0 && index < TextureGroupSamplers.Count())
{
// Sampler from texture group options
auto& group = TextureGroups[index];
auto desc = GPUSamplerDescription::New(group.SamplerFilter);
desc.MaxAnisotropy = group.MaxAnisotropy;
sampler = TextureGroupSamplers[index];
if (!sampler)
{
sampler = GPUSampler::New();
sampler->Init(desc);
TextureGroupSamplers[index] = sampler;
}
if (sampler->GetDescription().Filter != desc.Filter || sampler->GetDescription().MaxAnisotropy != desc.MaxAnisotropy)
sampler->Init(desc);
}
if (!sampler)
{
// Default sampler to prevent issue
if (!FallbackSampler)
{
FallbackSampler = GPUSampler::New();
FallbackSampler->Init(GPUSamplerDescription::New(GPUSamplerFilter::Trilinear));
}
sampler = FallbackSampler;
}
return sampler;
}

View File

@@ -6,6 +6,8 @@
#include "Engine/Scripting/ScriptingType.h"
#include "TextureGroup.h"
class GPUSampler;
/// <summary>
/// The content streaming service.
/// </summary>
@@ -22,4 +24,11 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Streaming);
/// Requests the streaming update for all the loaded resources. Use it to refresh content streaming after changing configuration.
/// </summary>
API_FUNCTION() static void RequestStreamingUpdate();
/// <summary>
/// Gets the texture sampler for a given texture group. Sampler objects is managed and cached by streaming service. Returned value is always valid (uses fallback object).
/// </summary>
/// <param name="index">The texture group index.</param>
/// <returns>The texture sampler (always valid).</returns>
API_FUNCTION() static GPUSampler* GetTextureGroupSampler(int32 index);
};

View File

@@ -4,6 +4,7 @@
#include "Engine/Core/Types/String.h"
#include "Engine/Core/ISerializable.h"
#include "Engine/Graphics/Textures/GPUSamplerDescription.h"
#if USE_EDITOR
#include "Engine/Core/Collections/Dictionary.h"
#endif
@@ -22,6 +23,18 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextureGroup);
API_FIELD(Attributes="EditorOrder(10)")
String Name;
/// <summary>
/// The default filtering method for samplers using this texture group.
/// </summary>
API_FIELD(Attributes="EditorOrder(15)")
GPUSamplerFilter SamplerFilter = GPUSamplerFilter::Trilinear;
/// <summary>
/// The maximum number of samples that can be used to improve the quality of sample footprints that are anisotropic. Higher values improve texturing but reduce performance. Limited by GPU capabilities and used only if SamplerFilter is Anisotropic.
/// </summary>
API_FIELD(Attributes="EditorOrder(16), Limit(1, 16), VisibleIf(\"IsAnisotropic\")")
int32 MaxAnisotropy = 16;
/// <summary>
/// The quality scale factor applied to textures in this group. Can be used to increase or decrease textures resolution. In range 0-1 where 0 means lowest quality, 1 means full quality.
/// </summary>

View File

@@ -290,6 +290,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
PointClamp = 1,
LinearWrap = 2,
PointWrap = 3,
TextureGroup = 4,
};
const Char* SamplerNames[]
{
@@ -345,8 +346,18 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
const bool useLevel = levelBox->HasConnection() || (int32)node->Values[1] != -1;
const bool useOffset = offsetBox->HasConnection();
const auto offset = useOffset ? eatBox(offsetBox->GetParent<Node>(), offsetBox->FirstConnection()) : Value::Zero;
const Char* samplerName;
const int32 samplerIndex = node->Values[0].AsInt;
if (samplerIndex < 0 || samplerIndex >= ARRAY_COUNT(SamplerNames))
if (samplerIndex == TextureGroup)
{
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[2].AsInt);
samplerName = *textureGroupSampler.ShaderName;
}
else if (samplerIndex >= 0 && samplerIndex < ARRAY_COUNT(SamplerNames))
{
samplerName = SamplerNames[samplerIndex];
}
else
{
OnError(node, box, TEXT("Invalid texture sampler."));
return;
@@ -372,7 +383,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
// Sample texture
const String sampledValue = String::Format(format,
texture.Value, // {0}
SamplerNames[samplerIndex], // {1}
samplerName, // {1}
uvs.Value, // {2}
level.Value, // {3}
offset.Value // {4}

View File

@@ -422,7 +422,7 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo
// Resources
{
int32 srv = 0;
int32 srv = 0, sampler = GPU_STATIC_SAMPLERS_COUNT;
switch (baseLayer->Domain)
{
case MaterialDomain::Surface:
@@ -466,7 +466,9 @@ bool MaterialGenerator::Generate(WriteStream& source, MaterialInfo& materialInfo
}
if (_parameters.HasItems())
{
const auto error = ShaderGraphUtilities::GenerateShaderResources(_writer, _parameters, srv);
auto error = ShaderGraphUtilities::GenerateShaderResources(_writer, _parameters, srv);
if (!error)
error = ShaderGraphUtilities::GenerateSamplers(_writer, _parameters, sampler);
if (error)
{
OnError(nullptr, nullptr, error);

View File

@@ -1273,6 +1273,30 @@ SerializedMaterialParam ShaderGenerator::findOrAddSceneTexture(MaterialSceneText
return param;
}
SerializedMaterialParam& ShaderGenerator::findOrAddTextureGroupSampler(int32 index)
{
// Find
for (int32 i = 0; i < _parameters.Count(); i++)
{
SerializedMaterialParam& param = _parameters[i];
if (!param.IsPublic && param.Type == MaterialParameterType::TextureGroupSampler && param.AsInteger == index)
{
return param;
}
}
// Create
SerializedMaterialParam& param = _parameters.AddOne();
param.Type = MaterialParameterType::TextureGroupSampler;
param.IsPublic = false;
param.Override = true;
param.Name = TEXT("Texture Group Sampler");
param.ShaderName = getParamName(_parameters.Count());
param.AsInteger = index;
param.ID = Guid(_parameters.Count(), 0, 0, 3); // Assign temporary id
return param;
}
String ShaderGenerator::getLocalName(int32 index)
{
return TEXT("local") + StringUtils::ToString(index);

View File

@@ -285,6 +285,7 @@ protected:
SerializedMaterialParam findOrAddNormalMap(const Guid& id);
SerializedMaterialParam findOrAddCubeTexture(const Guid& id);
SerializedMaterialParam findOrAddSceneTexture(MaterialSceneTextures type);
SerializedMaterialParam& findOrAddTextureGroupSampler(int32 index);
static String getLocalName(int32 index);
static String getParamName(int32 index);

View File

@@ -136,11 +136,9 @@ void ShaderGraphUtilities::GenerateShaderConstantBuffer(TextWriterUnicode& write
const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& writer, Array<SerializedMaterialParam>& parameters, int32 startRegister)
{
int32 registerIndex = startRegister;
for (int32 i = 0; i < parameters.Count(); i++)
{
auto& param = parameters[i];
const Char* format;
switch (param.Type)
{
@@ -164,15 +162,12 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri
format = nullptr;
break;
}
if (format)
{
param.Offset = 0;
param.RegisterIndex = registerIndex;
writer.WriteLine(format, param.ShaderName, static_cast<int32>(registerIndex));
registerIndex++;
// Validate Shader Resource count limit
param.RegisterIndex = (byte)startRegister;
writer.WriteLine(format, param.ShaderName, startRegister);
startRegister++;
if (param.RegisterIndex >= GPU_MAX_SR_BINDED)
{
return TEXT("Too many textures used. The maximum supported amount is " MACRO_TO_STR(GPU_MAX_SR_BINDED) " (including lightmaps and utility textures for lighting).");
@@ -182,6 +177,36 @@ const Char* ShaderGraphUtilities::GenerateShaderResources(TextWriterUnicode& wri
return nullptr;
}
const Char* ShaderGraphUtilities::GenerateSamplers(TextWriterUnicode& writer, Array<SerializedMaterialParam>& parameters, int32 startRegister)
{
for (int32 i = 0; i < parameters.Count(); i++)
{
auto& param = parameters[i];
const Char* format;
switch (param.Type)
{
case MaterialParameterType::TextureGroupSampler:
format = TEXT("sampler {0} : register(s{1});");
break;
default:
format = nullptr;
break;
}
if (format)
{
param.Offset = 0;
param.RegisterIndex = (byte)startRegister;
writer.WriteLine(format, param.ShaderName, startRegister);
startRegister++;
if (param.RegisterIndex >= GPU_MAX_SAMPLER_BINDED)
{
return TEXT("Too many samplers used. The maximum supported amount is " MACRO_TO_STR(GPU_MAX_SAMPLER_BINDED) ".");
}
}
}
return nullptr;
}
template<typename T>
const Char* GetTypename()
{

View File

@@ -12,6 +12,7 @@ namespace ShaderGraphUtilities
{
void GenerateShaderConstantBuffer(TextWriterUnicode& writer, Array<SerializedMaterialParam>& parameters);
const Char* GenerateShaderResources(TextWriterUnicode& writer, Array<SerializedMaterialParam>& parameters, int32 startRegister);
const Char* GenerateSamplers(TextWriterUnicode& writer, Array<SerializedMaterialParam>& parameters, int32 startRegister);
template<typename T>
void SampleCurve(TextWriterUnicode& writer, const BezierCurve<T>& curve, const String& time, const String& value);
}