[ATOM-15472] Shader Build Pipeline: Remove Deprecated Files And Funct… (#1079)

* [ATOM-15472] Shader Build Pipeline: Remove Deprecated Files And Functions That
Predate The Shader Supervariants

These are the essential impactful changes as a result of deprecating the
ShaderResourceGroupAsset.

* Addressed feedback by @moudgils. Better comments in header files.

* More updates related with deprecation of ShaderResourceGroupAsset

* Deleted the temporary version 2 classes.

* Updated version of the shader asset builders.

* Updated version of all the shader related classes impacted
by the Supervariant concept and deprecation of ShaderResourceGroupAsset

* Changes to *.pass and DGI, Reflections and RayTracing.

* changes to material related assets

* changes to core lights

* Changes to auxgeom/dynamic draw.

* changes to decals, lyshine, imguipass

* changes to RPI Pass classes

* Shader for SceneSrg, ViewSrg and ForwardPass Srgs.

* changes to mesh, skinned mesh, Morphtarget.

* Fixes to RayTracingPass.cpp & now allow empty srg in shaders.

* Updated Atom_RPI.Tests

* Simplified InstanceDatabase by removing AddHandler

------------------------------------------------------------------------------------
* Updated DiffuseGI precompiled shaders.
Added RayTracingSceneSrg and RayTracingMaterialSrg shader asset.
Updated ShaderAssetCreator::Clone to handle the supervariant when processing root variants.
Co-authored-by: Doug McDiarmid <dmcdiar@amazon.com>
------------------------------------------------------------------------------------

* Changed semantics for some PassSrg to SRG_PerPass_WithFallback.

AuxGeom/FixedShapeProcessor.cpp requires SRG_PerDraw on ObjectSrg.

Removed names of SceneSrg and ViewSrg from RPISystemDescriptor.cpp

* Moved ShaderLib/Atom/Features/DummyEntryFunctions.azsli
To  Gems/Atom/RPI/Assets/ShaderLib/Atom/RPI/DummyEntryFunctions.azsli

Removed redundant checking for finalization in
ShaderResourceGroupLayout.cpp

* Fixed race condition bug for Shader::FindOrCreate.
InstanceDatabase<>::CreateInstance() needs to be atomic
for instance creation and initialization.

Added optional InstanceHandler::CreateFunctionWithParams to accomodate
to the needs of Instances that need more than an asset reference
to be able to be created an initialzed.

Removed ShaderResourceGroup::FindOrCreate() only ::Create is available
now.

* Renamed scene_and_view_srgs.* as SceneAndViewSrgs.*

Changed GetAzslFileOfOrigin for GetUniqueId

* Fixed unit tests.

* Reverted the serialization name of m_uniqueId back to
"m_azslFileOfOrigin" so precompiled shaders don't fail
in layout comparison.

* Fixed AtomCore.Tests
Removed non-applicable test. InstanceDatabase.AddHandler() is not
available anymore.

* The Null rhi is re-enabled for shader compilation.

Signed-off-by: garrieta <garrieta@amazon.com>
This commit is contained in:
galibzon
2021-06-11 07:48:23 -05:00
committed by GitHub
parent 1435982330
commit cc615a8f32
253 changed files with 3389 additions and 65834 deletions
@@ -76,7 +76,7 @@ namespace AZ
template <typename Type>
friend struct AZStd::IntrusivePtrCountPolicy;
template <typename Type>
template<typename Type>
friend class InstanceDatabase;
// Pointer to the InstanceDatabase that owns this instance. Will be null if the InstanceData object
@@ -46,14 +46,22 @@ namespace AZ
*/
using CreateFunction = AZStd::function<Instance<Type>(AssetData*)>;
using CreateFunctionWithParam = AZStd::function<Instance<Type>(AssetData*, const AZStd::any* param)>;
/**
* Deletion takes an asset as input and transfers ownership to the method.
*/
using DeleteFunction = AZStd::function<void(Type*)>;
/// [Required] The function to use when creating an instance.
/// The system will assert if no creation function is provided.
CreateFunction m_createFunction;
/// A function to use when creating an instance.
/// The system will assert if both @m_createFunction and @m_createFunctionWithParam
/// creation functions are invalid.
CreateFunction m_createFunction = nullptr;
/// A function with an additional custom param to use when creating an instance.
/// The system will assert if both @m_createFunction and @m_createFunctionWithParam
/// creation functions are invalid.
CreateFunctionWithParam m_createFunctionWithParam = nullptr;
/// [Optional] The function to use when deleting an instance.
DeleteFunction m_deleteFunction = [](Type* t) { delete t; };
@@ -156,28 +164,14 @@ namespace AZ
* Use this function when creating an InstanceDatabase that will handle concrete classes of @ref Type.
* \param assetType - All instances will be based on subclasses of this asset type.
* \param handler - An InstanceHandler that creates instances of @ref assetType assets.
* \param checkAssetIds - If true, it will be validated that "instance->m_assetId == asset.GetId()"
*/
static void Create(const AssetType& assetType, const InstanceHandler<Type>& handler);
/**
* Create the InstanceDatabase with no handlers. Individual handlers must be added using @ref AddHandler().
* Use this function when creating an InstanceDatabase that will handle subclasses of @ref Type.
* \param assetType - All instances will be based on subclasses of this asset type.
*/
static void Create(const AssetType& assetType);
static void Create(const AssetType& assetType, const InstanceHandler<Type>& handler, bool checkAssetIds = true);
static void Destroy();
static bool IsReady();
static InstanceDatabase& Instance();
/**
* Add an InstanceHandler that will create instances for assets of type @ref assetType.
*/
void AddHandler(const AssetType& assetType, const InstanceHandler<Type>& handler);
void AddHandler(const AssetType& assetType, typename InstanceHandler<Type>::CreateFunction createFunction);
void RemoveHandler(const AssetType& assetType);
/**
* Attempts to find an instance associated with the provided id. If the instance exists, it
* is returned. If no instance is found, nullptr is returned. If is safe to call this from
@@ -205,18 +199,20 @@ namespace AZ
* when acquiring an instance.
* @return Returns a smart pointer to the instance, which was either found or created.
*/
Data::Instance<Type> FindOrCreate(const InstanceId& id, const Asset<AssetData>& asset);
Data::Instance<Type> FindOrCreate(const InstanceId& id, const Asset<AssetData>& asset, const AZStd::any* param = nullptr);
//! Calls the above FindOrCreate using an InstanceId created from the asset
Data::Instance<Type> FindOrCreate(const Asset<AssetData>& asset);
Data::Instance<Type> FindOrCreate(const Asset<AssetData>& asset, const AZStd::any* param = nullptr);
//! Calls FindOrCreate using a random InstanceId
Data::Instance<Type> Create(const Asset<AssetData>& asset);
Data::Instance<Type> Create(const Asset<AssetData>& asset, const AZStd::any* param = nullptr);
private:
InstanceDatabase(const AssetType& assetType);
~InstanceDatabase();
bool m_checkAssetIds = true;
//useAssetTypeAsKeyForHandlers;
static const char* GetEnvironmentName();
// Utility function called by InstanceData to remove the instance from the database.
@@ -224,11 +220,7 @@ namespace AZ
void ValidateSameAsset(InstanceData* instance, const Data::Asset<AssetData>& asset) const;
// Performs a thread-safe search for the InstanceHandler for a given asset type.
bool FindHandler(const AssetType& assetType, InstanceHandler<Type>& handlerOut);
mutable AZStd::shared_mutex m_handlersMutex;
AZStd::unordered_map<AssetType, InstanceHandler<Type>> m_handlers;
InstanceHandler<Type> m_instanceHandler;
// m_database uses a recursive_mutex instead of a shared_mutex because it's possible to recursively
// create or destroy instances on the same thread while in the midst of creating or destroying an instance.
@@ -241,10 +233,11 @@ namespace AZ
static EnvironmentVariable<InstanceDatabase*> ms_instance;
};
template <typename Type>
EnvironmentVariable<InstanceDatabase<Type>*> InstanceDatabase<Type>::ms_instance = nullptr;
template<typename Type>
EnvironmentVariable<InstanceDatabase<Type>*>
InstanceDatabase<Type>::ms_instance = nullptr;
template <typename Type>
template<typename Type>
InstanceDatabase<Type>::~InstanceDatabase()
{
#ifdef AZ_DEBUG_BUILD
@@ -261,52 +254,7 @@ namespace AZ
"AZ::Data::%s still has active references.", Type::GetDatabaseName());
}
template <typename Type>
void InstanceDatabase<Type>::AddHandler(const AssetType& assetType, const InstanceHandler<Type>& handler)
{
AZ_Assert(handler.m_createFunction, "You are required to provide a create function to InstanceDatabase.");
AZStd::unique_lock<AZStd::shared_mutex> lock(m_handlersMutex);
auto result = m_handlers.emplace(assetType, handler);
AZ_Assert(result.second, "An InstanceHandler already exists for this AssetType");
}
template <typename Type>
void InstanceDatabase<Type>::AddHandler(const AssetType& assetType, typename InstanceHandler<Type>::CreateFunction createFunction)
{
InstanceHandler<Type> instanceHandler;
instanceHandler.m_createFunction = createFunction;
AddHandler(assetType, instanceHandler);
}
template <typename Type>
void InstanceDatabase<Type>::RemoveHandler(const AssetType& assetType)
{
AZStd::unique_lock<AZStd::shared_mutex> lock(m_handlersMutex);
m_handlers.erase(assetType);
}
template <typename Type>
bool InstanceDatabase<Type>::FindHandler(const AssetType& assetType, InstanceHandler<Type>& handlerOut)
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_handlersMutex);
auto handlerIter = m_handlers.find(assetType);
if (handlerIter != m_handlers.end())
{
// Since the handler is just a couple pointers, we copy the handler so we can
// release the lock right away.
handlerOut = handlerIter->second;
return true;
}
else
{
return false;
}
}
template <typename Type>
template<typename Type>
Data::Instance<Type> InstanceDatabase<Type>::Find(const InstanceId& id) const
{
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
@@ -318,8 +266,9 @@ namespace AZ
return nullptr;
}
template <typename Type>
Data::Instance<Type> InstanceDatabase<Type>::FindOrCreate(const InstanceId& id, const Asset<AssetData>& asset)
template<typename Type>
Data::Instance<Type> InstanceDatabase<Type>::FindOrCreate(
const InstanceId& id, const Asset<AssetData>& asset, const AZStd::any* param)
{
if (!id.IsValid())
{
@@ -358,24 +307,6 @@ namespace AZ
}
}
if (!azrtti_istypeof(m_baseAssetType, assetLocal.Get()))
{
InstanceHandler<Type> instanceHandler;
// If a handler was incorrectly registered for an unrelated asset type, this is the
// first chance we have to discover that fact, because up until now all we had was two
// TypeIds.
if (FindHandler(assetLocal.GetType(), instanceHandler))
{
AZ_Assert(false, "An InstanceHandler was added for asset type %s which is not a subclass of the base asset type %s.",
assetLocal.GetType().ToString<AZStd::string>().data(),
m_baseAssetType.ToString<AZStd::string>().data()
);
return nullptr;
}
}
// Take a lock to guard the insertion. Note that this will not guard against recursive insertions on the same thread.
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
@@ -390,48 +321,46 @@ namespace AZ
}
// Emplace a new instance and return it.
InstanceHandler<Type> instanceHandler;
if (FindHandler(assetLocal.GetType(), instanceHandler))
// It's possible for the m_createFunction call to recursively trigger another FindOrCreate call, so be aware that
// the contents of m_database may change within this call.
Data::Instance<Type> instance = nullptr;
if (!param)
{
// It's possible for the m_createFunction call to recursively trigger another FindOrCreate call, so be aware that
// the contents of m_database may change within this call.
Data::Instance<Type> instance = instanceHandler.m_createFunction(assetLocal.Get());
if (instance)
{
AZ_Assert(m_database.find(id) == m_database.end(),
"Instance creation for asset id %s resulted in a recursive creation of that asset, which was unexpected. "
"This asset might be erroneously referencing itself as a dependent asset.", id.ToString<AZStd::string>().c_str());
instance->m_id = id;
instance->m_parentDatabase = this;
instance->m_assetId = assetLocal.GetId();
instance->m_assetType = assetLocal.GetType();
m_database.emplace(id, instance.get());
}
return AZStd::move(instance);
instance = m_instanceHandler.m_createFunction(assetLocal.Get());
}
else
{
AZ_Warning(
"InstanceDatabase", false,
"No InstanceHandler found for asset type %s", assetLocal.GetType().ToString<AZStd::string>().data());
return nullptr;
instance = m_instanceHandler.m_createFunctionWithParam(assetLocal.Get(), param);
}
if (instance)
{
AZ_Assert(m_database.find(id) == m_database.end(),
"Instance creation for asset id %s resulted in a recursive creation of that asset, which was unexpected. "
"This asset might be erroneously referencing itself as a dependent asset.", id.ToString<AZStd::string>().c_str());
instance->m_id = id;
instance->m_parentDatabase = this;
instance->m_assetId = assetLocal.GetId();
instance->m_assetType = assetLocal.GetType();
m_database.emplace(id, instance.get());
}
return AZStd::move(instance);
}
template <typename Type>
Data::Instance<Type> InstanceDatabase<Type>::FindOrCreate(const Asset<AssetData>& asset)
template<typename Type>
Data::Instance<Type> InstanceDatabase<Type>::FindOrCreate(const Asset<AssetData>& asset, const AZStd::any* param)
{
return FindOrCreate(Data::InstanceId::CreateFromAssetId(asset.GetId()), asset);
return FindOrCreate(Data::InstanceId::CreateFromAssetId(asset.GetId()), asset, param);
}
template <typename Type>
Data::Instance<Type> InstanceDatabase<Type>::Create(const Asset<AssetData>& asset)
template<typename Type>
Data::Instance<Type> InstanceDatabase<Type>::Create(const Asset<AssetData>& asset, const AZStd::any* param)
{
return FindOrCreate(Data::InstanceId::CreateRandom(), asset);
return FindOrCreate(Data::InstanceId::CreateRandom(), asset, param);
}
template <typename Type>
template<typename Type>
void InstanceDatabase<Type>::ReleaseInstance(InstanceData* instance, const InstanceId& instanceId)
{
AZStd::scoped_lock<AZStd::recursive_mutex> lock(m_databaseMutex);
@@ -447,22 +376,13 @@ namespace AZ
instance->m_useCount.compare_exchange_strong(expectedRefCount, -1))
{
m_database.erase(instance->GetId());
InstanceHandler<Type> instanceHandler;
if (FindHandler(instance->GetAssetType(), instanceHandler))
{
instanceHandler.m_deleteFunction(static_cast<Type*>(instance));
}
else
{
AZ_Assert(false,
"Cannot delete Instance. No InstanceHandler found for asset type %s", instance->GetAssetType().ToString<AZStd::string>().data());
}
m_instanceHandler.m_deleteFunction(static_cast<Type*>(instance));
}
}
template <typename Type>
void InstanceDatabase<Type>::ValidateSameAsset(InstanceData* instance, const Data::Asset<AssetData>& asset) const
template<typename Type>
void InstanceDatabase<Type>::ValidateSameAsset(
InstanceData* instance, const Data::Asset<AssetData>& asset) const
{
/**
* The following validation layer is disabled in release, but is designed to catch a couple related edge cases
@@ -476,46 +396,47 @@ namespace AZ
*/
#if defined (AZ_DEBUG_BUILD)
AZ_Error("InstanceDatabase", instance->m_assetId == asset.GetId(),
"InstanceDatabase::FindOrCreate found the requested instance, but a different asset was used to create it. "
"Instances of a specific id should be acquired using the same asset. Either make sure the instance id "
"is actually unique, or that you are using the same asset each time for that particular id.");
if (m_checkAssetIds)
{
AZ_Error(
"InstanceDatabase", (instance->m_assetId == asset.GetId()),
"InstanceDatabase::FindOrCreate found the requested instance, but a different asset was used to create it. "
"Instances of a specific id should be acquired using the same asset. Either make sure the instance id "
"is actually unique, or that you are using the same asset each time for that particular id.");
}
#else
AZ_UNUSED(instance);
AZ_UNUSED(asset);
#endif
}
template <typename Type>
template<typename Type>
InstanceDatabase<Type>::InstanceDatabase(const AssetType& assetType)
: m_baseAssetType(assetType)
{
}
template <typename Type>
void InstanceDatabase<Type>::Create(const AssetType& assetType)
template<typename Type>
void InstanceDatabase<Type>::Create(const AssetType& assetType, const InstanceHandler<Type>& handler, bool checkAssetIds)
{
AZ_Assert(!ms_instance || !ms_instance.Get(), "InstanceDatabase already created!");
if (!ms_instance)
{
ms_instance = Environment::CreateVariable<InstanceDatabase*>(GetEnvironmentName());
ms_instance = Environment::CreateVariable<InstanceDatabase<Type>*>(GetEnvironmentName());
}
if (!ms_instance.Get())
{
ms_instance.Set(aznew InstanceDatabase<Type>(assetType));
}
AZ_Assert(handler.m_createFunction || handler.m_createFunctionWithParam, "At least one create function must be valid");
ms_instance.Get()->m_instanceHandler = handler;
ms_instance.Get()->m_checkAssetIds = checkAssetIds;
}
template <typename Type>
void InstanceDatabase<Type>::Create(const AssetType& assetType, const InstanceHandler<Type>& handler)
{
Create(assetType);
Instance().AddHandler(assetType, handler);
}
template <typename Type>
template<typename Type>
void InstanceDatabase<Type>::Destroy()
{
AZ_Assert(ms_instance, "InstanceDatabase not created!");
@@ -523,7 +444,7 @@ namespace AZ
*ms_instance = nullptr;
}
template <typename Type>
template<typename Type>
bool InstanceDatabase<Type>::IsReady()
{
if (!ms_instance)
@@ -534,7 +455,7 @@ namespace AZ
return ms_instance && *ms_instance;
}
template <typename Type>
template<typename Type>
InstanceDatabase<Type>& InstanceDatabase<Type>::Instance()
{
if (!ms_instance)
@@ -546,10 +467,12 @@ namespace AZ
return *(*ms_instance);
}
template <typename Type>
template<typename Type>
const char* InstanceDatabase<Type>::GetEnvironmentName()
{
static_assert(HasInstanceDatabaseName<Type>::value, "All classes used as instances in an InstanceDatabase need to define AZ_INSTANCE_DATA in the class.");
static_assert(
HasInstanceDatabaseName<Type>::value,
"All classes used as instances in an InstanceDatabase need to define AZ_INSTANCE_DATA in the class.");
return Type::GetDatabaseName();
}
}
@@ -34,8 +34,7 @@ namespace UnitTest
static const AssetId s_assetId3{ Uuid("{D9CDAB04-D206-431E-BDC0-1DD615D56197}") };
// test asset type
class TestAssetType
: public AssetData
class TestAssetType : public AssetData
{
public:
AZ_CLASS_ALLOCATOR(TestAssetType, AZ::SystemAllocator, 0);
@@ -47,30 +46,30 @@ namespace UnitTest
}
};
class TestInstanceA
: public InstanceData
class TestInstanceA : public InstanceData
{
public:
AZ_INSTANCE_DATA(TestInstanceA, "{65CBF1C8-F65F-4A84-8A11-B510BC435DB0}");
AZ_CLASS_ALLOCATOR(TestInstanceA, AZ::SystemAllocator, 0);
TestInstanceA(TestAssetType* asset)
: m_asset{asset, AZ::Data::AssetLoadBehavior::Default}
{}
: m_asset{ asset, AZ::Data::AssetLoadBehavior::Default }
{
}
Asset<TestAssetType> m_asset;
};
class TestInstanceB
: public InstanceData
class TestInstanceB : public InstanceData
{
public:
AZ_INSTANCE_DATA(TestInstanceB, "{4ED0A8BF-7800-44B2-AC73-2CB759C61C37}");
AZ_CLASS_ALLOCATOR(TestInstanceB, AZ::SystemAllocator, 0);
TestInstanceB(TestAssetType* asset)
: m_asset{asset, AZ::Data::AssetLoadBehavior::Default }
{}
: m_asset{ asset, AZ::Data::AssetLoadBehavior::Default }
{
}
~TestInstanceB()
{
@@ -86,8 +85,7 @@ namespace UnitTest
// test asset handler
template<typename AssetDataT>
class MyAssetHandler
: public AssetHandler
class MyAssetHandler : public AssetHandler
{
public:
AZ_CLASS_ALLOCATOR(MyAssetHandler, AZ::SystemAllocator, 0);
@@ -120,13 +118,12 @@ namespace UnitTest
}
};
class InstanceDatabaseTest
: public AllocatorsFixture
class InstanceDatabaseTest : public AllocatorsFixture
{
protected:
MyAssetHandler<TestAssetType>* m_assetHandler;
public:
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
@@ -200,7 +197,7 @@ namespace UnitTest
AZStd::vector<Uuid> guids;
AZStd::vector<Asset<TestAssetType>> assets;
for (size_t i = 0; i < assetIdCount; ++i)
{
Uuid guid = Uuid::CreateRandom();
@@ -211,7 +208,6 @@ namespace UnitTest
assets.emplace_back(assetManager.CreateAsset<TestAssetType>(guid, AZ::Data::AssetLoadBehavior::Default));
}
AZStd::vector<AZStd::thread> threads;
AZStd::mutex mutex;
AZStd::atomic<int> threadCount((int)threadCountMax);
@@ -232,27 +228,29 @@ namespace UnitTest
for (size_t i = 0; i < threadCountMax; ++i)
{
threads.emplace_back([&instanceManager, &threadCount, &cv, &guids, &assets, &durationSeconds]()
{
AZ::Debug::Timer timer;
timer.Stamp();
while(timer.GetDeltaTimeInSeconds() < durationSeconds)
threads.emplace_back(
[&instanceManager, &threadCount, &cv, &guids, &assets, &durationSeconds]()
{
const size_t index = rand() % guids.size();
const Uuid uuid = guids[index];
const InstanceId instanceId{uuid};
const AssetId assetId{uuid};
AZ::Debug::Timer timer;
timer.Stamp();
Instance<TestInstanceA> instance = instanceManager.FindOrCreate(instanceId, Asset<TestAssetType>(assetId, azrtti_typeid<TestAssetType>()));
EXPECT_NE(instance, nullptr);
EXPECT_EQ(instance->GetId(), instanceId);
EXPECT_EQ(instance->m_asset, assets[index]);
}
while (timer.GetDeltaTimeInSeconds() < durationSeconds)
{
const size_t index = rand() % guids.size();
const Uuid uuid = guids[index];
const InstanceId instanceId{ uuid };
const AssetId assetId{ uuid };
threadCount--;
cv.notify_one();
});
Instance<TestInstanceA> instance =
instanceManager.FindOrCreate(instanceId, Asset<TestAssetType>(assetId, azrtti_typeid<TestAssetType>()));
EXPECT_NE(instance, nullptr);
EXPECT_EQ(instance->GetId(), instanceId);
EXPECT_EQ(instance->m_asset, assets[index]);
}
threadCount--;
cv.notify_one();
});
}
bool timedOut = false;
@@ -261,7 +259,9 @@ namespace UnitTest
while (threadCount > 0 && !timedOut)
{
AZStd::unique_lock<AZStd::mutex> lock(mutex);
timedOut = (AZStd::cv_status::timeout == cv.wait_until(lock, AZStd::chrono::system_clock::now() + AZStd::chrono::seconds(durationSeconds * 2)));
timedOut =
(AZStd::cv_status::timeout ==
cv.wait_until(lock, AZStd::chrono::system_clock::now() + AZStd::chrono::seconds(durationSeconds * 2)));
}
EXPECT_TRUE(threadCount == 0) << "One or more threads appear to be deadlocked at " << timer.GetDeltaTimeInSeconds() << " seconds";
@@ -280,8 +280,8 @@ namespace UnitTest
TEST_F(InstanceDatabaseTest, ParallelInstanceCreate)
{
// This is the original test scenario from when InstanceDatabase was first implemented
// threads, AssetIds, seconds
ParallelInstanceCreateHelper( 8, 100, 5 );
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(8, 100, 5);
// This value is checked in as 1 so this test doesn't take too much time, but can be increased locally to soak the test.
const size_t attempts = 1;
@@ -291,10 +291,10 @@ namespace UnitTest
printf("Attempt %zu of %zu... \n", i, attempts);
// The idea behind this series of tests is that there are two threads sharing one Instance, and both threads try to
// create or release that instance at the same time.
// create or release that instance at the same time.
// At the time, this set of scenarios has something like a 10% failure rate.
const size_t duration = 2;
// threads, AssetIds, seconds
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(2, 1, duration);
ParallelInstanceCreateHelper(4, 1, duration);
ParallelInstanceCreateHelper(8, 1, duration);
@@ -306,7 +306,7 @@ namespace UnitTest
// Here we try a bunch of different threadCount:assetCount ratios to be thorough
const size_t duration = 2;
// threads, AssetIds, seconds
// threads, AssetIds, seconds
ParallelInstanceCreateHelper(2, 1, duration);
ParallelInstanceCreateHelper(4, 1, duration);
ParallelInstanceCreateHelper(4, 2, duration);
@@ -328,7 +328,10 @@ namespace UnitTest
// Tests whether the deleter actually calls delete properly without
// a parent database.
instance->m_onDeleteCallback = [this, &m_deleted] () { m_deleted = true; };
instance->m_onDeleteCallback = [this, &m_deleted]()
{
m_deleted = true;
};
}
EXPECT_TRUE(m_deleted);
@@ -386,242 +389,4 @@ namespace UnitTest
InstanceDatabase<TestInstanceB>::Destroy();
}
class InstanceDatabaseTestWithMultipleSubclasses
: public AllocatorsFixture
{
protected:
// We have "BaseAsset" with subclasses "FooAsset" and "BarAsset",
// and corresponding "BaseInstance" with subclasses "FooInstance" and "BarInstance".
// There is one "InstanceDatabse<BaseInstance>" that can create instances of both subtypes.
class BaseAsset
: public AssetData
{
public:
AZ_CLASS_ALLOCATOR(BaseAsset, AZ::SystemAllocator, 0);
AZ_RTTI(FooAsset, "{35B443A6-D8ED-4C3C-A3F0-D642251F0AA5}", AssetData);
BaseAsset()
{
m_status = AssetStatus::Ready;
}
};
class BaseInstance
: public InstanceData
{
public:
AZ_INSTANCE_DATA(BaseInstance, "{EFEC3406-2CB7-462E-A676-C22177E143E6}");
AZ_CLASS_ALLOCATOR(BaseInstance, AZ::SystemAllocator, 0);
BaseInstance(BaseAsset* asset)
: m_asset{ asset, AZ::Data::AssetLoadBehavior::Default }
{}
Asset<BaseAsset> m_asset;
};
class FooAsset
: public BaseAsset
{
public:
AZ_CLASS_ALLOCATOR(FooAsset, AZ::SystemAllocator, 0);
AZ_RTTI(FooAsset, "{74BAE278-3DCA-4ADD-807E-2A6873F9EA3C}", BaseAsset);
};
class BarAsset
: public BaseAsset
{
public:
AZ_CLASS_ALLOCATOR(BarAsset, AZ::SystemAllocator, 0);
AZ_RTTI(FooAsset, "{2BCD66F5-768B-4569-9FC2-DE92ABC9C0BF}", BaseAsset);
};
class FooInstance
: public BaseInstance
{
public:
AZ_RTTI(FooInstance, "{B5487509-5518-4591-AC96-03E623A584B7}", BaseInstance);
AZ_CLASS_ALLOCATOR(FooInstance, AZ::SystemAllocator, 0);
FooInstance(BaseAsset* asset)
: BaseInstance(asset)
{
EXPECT_TRUE(azrtti_typeid<FooAsset>() == asset->GetType());
}
};
class BarInstance
: public BaseInstance
{
public:
AZ_RTTI(BarInstance, "{CE9C844A-625D-4899-B7DB-8127D4618D25}", BaseInstance);
AZ_CLASS_ALLOCATOR(BarInstance, AZ::SystemAllocator, 0);
BarInstance(BaseAsset* asset)
: BaseInstance(asset)
{
EXPECT_TRUE(azrtti_typeid<BarAsset>() == asset->GetType());
}
};
MyAssetHandler<FooAsset> m_fooAssetHandler;
MyAssetHandler<BarAsset> m_barAssetHandler;
public:
void SetUp() override
{
AllocatorsFixture::SetUp();
AllocatorInstance<PoolAllocator>::Create();
AllocatorInstance<ThreadPoolAllocator>::Create();
// create the asset database
{
AssetManager::Descriptor desc;
AssetManager::Create(desc);
}
// create the instance database
{
InstanceDatabase<BaseInstance>::Create(azrtti_typeid<BaseAsset>());
InstanceHandler<BaseInstance> fooHandler;
fooHandler.m_createFunction = [](AssetData* assetData)
{
EXPECT_TRUE(azrtti_istypeof<FooAsset>(assetData));
return aznew FooInstance(static_cast<FooAsset*>(assetData));
};
InstanceDatabase<BaseInstance>::Instance().AddHandler(azrtti_typeid<FooAsset>(), fooHandler);
// Using a different overload of AddHandler()
InstanceDatabase<BaseInstance>::Instance().AddHandler(azrtti_typeid<BarAsset>(), [](AssetData* assetData)
{
EXPECT_TRUE(azrtti_istypeof<BarAsset>(assetData));
return aznew BarInstance(static_cast<BarAsset*>(assetData));
});
}
AssetManager::Instance().RegisterHandler(&m_fooAssetHandler, AzTypeInfo<FooAsset>::Uuid());
AssetManager::Instance().RegisterHandler(&m_barAssetHandler, AzTypeInfo<BarAsset>::Uuid());
}
void TearDown() override
{
AssetManager::Instance().UnregisterHandler(&m_fooAssetHandler);
AssetManager::Instance().UnregisterHandler(&m_barAssetHandler);
AssetManager::Destroy();
InstanceDatabase<BaseInstance>::Destroy();
AllocatorInstance<ThreadPoolAllocator>::Destroy();
AllocatorInstance<PoolAllocator>::Destroy();
AllocatorsFixture::TearDown();
}
};
TEST_F(InstanceDatabaseTestWithMultipleSubclasses, InstanceCreate)
{
auto& assetManager = AssetManager::Instance();
auto& instanceDatabase = InstanceDatabase<BaseInstance>::Instance();
Asset<FooAsset> fooAsset = assetManager.CreateAsset<FooAsset>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
Asset<BarAsset> barAsset = assetManager.CreateAsset<BarAsset>(s_assetId1, AZ::Data::AssetLoadBehavior::Default);
// Run the creation tests on 'A' first.
Instance<BaseInstance> fooInstanceA = instanceDatabase.Find(s_instanceId0);
EXPECT_EQ(fooInstanceA, nullptr);
Instance<BaseInstance> barInstanceA = instanceDatabase.Find(s_instanceId1);
EXPECT_EQ(barInstanceA, nullptr);
fooInstanceA = instanceDatabase.FindOrCreate(s_instanceId0, fooAsset);
EXPECT_NE(fooInstanceA, nullptr);
EXPECT_EQ(fooInstanceA->m_asset, fooAsset);
EXPECT_TRUE(azrtti_typeid<FooInstance>() == fooInstanceA->RTTI_GetType());
EXPECT_EQ(fooInstanceA, instanceDatabase.Find(s_instanceId0));
barInstanceA = instanceDatabase.FindOrCreate(s_instanceId1, barAsset);
EXPECT_NE(barInstanceA, nullptr);
EXPECT_EQ(barInstanceA->m_asset, barAsset);
EXPECT_TRUE(azrtti_typeid<BarInstance>() == barInstanceA->RTTI_GetType());
EXPECT_EQ(barInstanceA, instanceDatabase.Find(s_instanceId1));
// Run the same test on 'B' to make sure it works independently.
Instance<BaseInstance> fooInstanceB = instanceDatabase.Find(s_instanceId2);
EXPECT_EQ(fooInstanceB, nullptr);
Instance<BaseInstance> barInstanceB = instanceDatabase.Find(s_instanceId3);
EXPECT_EQ(barInstanceB, nullptr);
fooInstanceB = instanceDatabase.FindOrCreate(s_instanceId2, fooAsset);
EXPECT_NE(fooInstanceB, nullptr);
EXPECT_EQ(fooInstanceB->m_asset, fooAsset);
EXPECT_TRUE(azrtti_typeid<FooInstance>() == fooInstanceB->RTTI_GetType());
EXPECT_EQ(fooInstanceB, instanceDatabase.Find(s_instanceId2));
barInstanceB = instanceDatabase.FindOrCreate(s_instanceId3, barAsset);
EXPECT_NE(barInstanceB, nullptr);
EXPECT_EQ(barInstanceB->m_asset, barAsset);
EXPECT_TRUE(azrtti_typeid<BarInstance>() == barInstanceB->RTTI_GetType());
EXPECT_EQ(barInstanceB, instanceDatabase.Find(s_instanceId3));
// Make sure the instances are unique
EXPECT_NE(fooInstanceA, fooInstanceB);
EXPECT_NE(barInstanceA, barInstanceB);
}
TEST_F(InstanceDatabaseTestWithMultipleSubclasses, TestError_AddHandler_AssetTypeIsNotSubclass)
{
MyAssetHandler<TestAssetType> testAssetHandler;
AssetManager::Instance().RegisterHandler(&testAssetHandler, azrtti_typeid<TestAssetType>());
// Register an instance handler with an unrelated asset type. This can't actually
// check the AssetType yet because all it has are AssetType GUIDs, no actual data.
{
InstanceHandler<BaseInstance> instanceHandler;
instanceHandler.m_createFunction = [](AssetData* assetData)
{
return aznew BaseInstance(static_cast<BaseAsset*>(assetData));
};
AssetType unrelatedAssetType = azrtti_typeid<TestAssetType>();
InstanceDatabase<BaseInstance>::Instance().AddHandler(unrelatedAssetType, instanceHandler);
}
// Try to use the unrelated handler. This is where we'll actually get an error.
{
AZ_TEST_START_ASSERTTEST;
Asset<TestAssetType> testAsset = AssetManager::Instance().CreateAsset<TestAssetType>(s_assetId0, AZ::Data::AssetLoadBehavior::Default);
EXPECT_EQ(nullptr, InstanceDatabase<BaseInstance>::Instance().FindOrCreate(s_instanceId0, testAsset));
AZ_TEST_STOP_ASSERTTEST(1);
}
AssetManager::Instance().UnregisterHandler(&testAssetHandler);
}
TEST_F(InstanceDatabaseTestWithMultipleSubclasses, TestError_AddHandler_AlreadyExists)
{
InstanceHandler<BaseInstance> instanceHandler;
instanceHandler.m_createFunction = [](AssetData*)
{
return nullptr; // Doesn't matter
};
AZ_TEST_START_ASSERTTEST;
// The SetUp() function already registered a handler for FooAsset so this should fail
InstanceDatabase<BaseInstance>::Instance().AddHandler(azrtti_typeid<FooAsset>(), instanceHandler);
InstanceDatabase<BaseInstance>::Instance().AddHandler(azrtti_typeid<FooAsset>(), [](AssetData*) { return nullptr; });
AZ_TEST_STOP_ASSERTTEST(2);
}
}
} // namespace UnitTest
@@ -1,402 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AzslBuilder.h"
#include "ShaderBuilderUtility.h"
#include "ShaderPlatformInterfaceRequest.h"
#include <CommonFiles/GlobalBuildOptions.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <AzCore/IO/SystemFile.h>
#include <math.h>
namespace AZ
{
namespace ShaderBuilder
{
enum class JobParameterIndices : uint32_t
{
ApiName,
PreprocessedCode,
PreprocessorError,
SkipJob
};
AZ::Uuid AzslBuilder::GetUUID()
{
return AZ::Uuid::CreateString("{72DCFC95-1B9E-4A8D-8633-D497CACD98AB}");
}
RHI::ShaderPlatformInterface* GetShaderPlatformInterfaceForApi(const AZStd::string& apiNameFilter, const AssetBuilderSDK::PlatformInfo& currentPlatform)
{
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces;
ShaderPlatformInterfaceRequestBus::BroadcastResult(platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, currentPlatform);
for (RHI::ShaderPlatformInterface* oneInterface : platformInterfaces)
{
if (oneInterface && oneInterface->GetAPIName().GetStringView() == apiNameFilter)
{
return oneInterface;
}
}
return nullptr;
}
PreprocessorData PreprocessSource(const AZStd::string& inputFile, const AZStd::string& originalPath, const PreprocessorOptions& options)
{
// run mcpp
PreprocessorData output;
PreprocessFile(inputFile, output, options, true, true);
// do not let the filename.api.azsl.prepend be the filename that will be regarded as the source.
// because SRG assets are located using the 'containingFile' so we need to preserve the true origin:
MutateLineDirectivesFileOrigin(output.code, originalPath);
RHI::ReportErrorMessages(AzslBuilder::BuilderName, output.diagnostics);
return output;
}
void AddAzslBuilderJobDependency(AssetBuilderSDK::JobDescriptor& jobDescriptor, const AZStd::string& platformInfoIdentifier, AZStd::string_view apiName, AZStd::string_view fullFilePath)
{
AssetBuilderSDK::SourceFileDependency fileDependency;
fileDependency.m_sourceFileDependencyPath = fullFilePath;
AssetBuilderSDK::JobDependency dependency;
dependency.m_jobKey = AzslBuilder::JobKey;
dependency.m_jobKey += " ";
dependency.m_jobKey += apiName;
dependency.m_platformIdentifier = platformInfoIdentifier;
dependency.m_sourceFile = fileDependency;
dependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
jobDescriptor.m_jobDependencyList.emplace_back(dependency);
}
void AzslBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{
AZStd::string fullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true);
// this builder may take as input:
// .shader .azsl .azsli .srgi
// it will not behave strictly exactly the same for each type
// Only *.srgi files are supposed to include files that define "partial" qualified SRGs.
const bool isSrgi = AzFramework::StringFunc::Path::IsExtension(fullPath.c_str(), SrgIncludeExtension);
// .azsli needs "skip check"
const bool isAzsli = AzFramework::StringFunc::Path::IsExtension(fullPath.c_str(), "azsli");
// .shader files must be opened to get their build options and the referenced azsl file
const bool isShader = AzFramework::StringFunc::Path::IsExtension(fullPath.c_str(), RPI::ShaderSourceData::Extension);
// .azsl must not be skipped, otherwise we're creating a risk of two .shader referring to the same .azsl racing for its output product
// To avoid the warning:
// "No job was found to match the job dependency criteria declared by file "..."
// We will schedule the job, but will do nothing
//bool shouldSkipFile = false;
// We treat some issues as warnings and return "Success" from CreateJobs allows us to report the dependency.
// If/when a valid dependency file appears, that will trigger the ShaderVariantAssetBuilder to run again.
// Since CreateJobs will pass, we forward this message to ProcessJob which will report it as an error.
//bool gotPreprocessingError = false;
// The following if-block will be removed once [GFX TODO][ATOM-5302] is addressed, and
// azslc allows redundant SrgSemantics for "partial" qualified SRGs.
if (isAzsli)
{
auto skipCheck = ShaderBuilderUtility::ShouldSkipFileForSrgProcessing(BuilderName, fullPath);
if (skipCheck != ShaderBuilderUtility::SrgSkipFileResult::ContinueProcess)
{
response.m_result = skipCheck == ShaderBuilderUtility::SrgSkipFileResult::Error ?
AssetBuilderSDK::CreateJobsResultCode::Failed : AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
}
if (isShader)
{
// Need to get the path to the shader file from the template, so that we can preprocess the shader data and setup
// source file dependencies.
auto descriptorParseOutput = ShaderBuilderUtility::LoadShaderDataJson(fullPath);
if (!descriptorParseOutput.IsSuccess())
{
AZ_Error(BuilderName, false, "Failed to parse Shader Descriptor JSON: %s", descriptorParseOutput.GetError().c_str());
return;
}
// update the value of fullPath to mean directly, the azsl file:
ShaderBuilderUtility::GetAbsolutePathToAzslFile(fullPath, descriptorParseOutput.GetValue().m_source, fullPath);
}
GlobalBuildOptions buildOptions = ReadBuildOptions(BuilderName);
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
AZ_TraceContext("For platform", info.m_identifier.data());
// Get the platform interfaces to be able to access the prepend file
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(info);
// Preprocess the shader file, per activated platform.
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
auto apiNameAsStringView = shaderPlatformInterface->GetAPIName().GetStringView();
AssetBuilderSDK::JobDescriptor jobDescriptor;
jobDescriptor.m_priority = 2;
// [GFX TODO][ATOM-2830] Set 'm_critical' back to 'false' once proper fix for Atom startup issues are in
jobDescriptor.m_critical = true;
jobDescriptor.m_jobKey = JobKey;
jobDescriptor.m_jobKey += " ";
jobDescriptor.m_jobKey += apiNameAsStringView;
jobDescriptor.SetPlatformIdentifier(info.m_identifier.data());
jobDescriptor.m_jobParameters[(u32)JobParameterIndices::ApiName] = apiNameAsStringView;
if (isShader)
{
// add a job dependency on the azsl run (of that same job: AzslBuilder - because it also runs on .azsl)
AddAzslBuilderJobDependency(jobDescriptor, info.m_identifier, apiNameAsStringView, fullPath);
}
// execute azsl prepending here, before preprocess, in order to support macros in AzslcHeader.azsli
AZStd::string prependedAzslSourceCode;
RHI::PrependArguments args;
args.m_sourceFile = fullPath.c_str();
args.m_prependFile = shaderPlatformInterface->GetAzslHeader(info);
args.m_addSuffixToFileName = shaderPlatformInterface->GetAPIName().GetCStr();
args.m_destinationStringOpt = &prependedAzslSourceCode;
if (RHI::PrependFile(args) == fullPath) // error case. it returns the combined-file's name on success, or original path on failure, but here we use the "direct to string" mode so we don't need to store the returned name.
{
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
return;
}
AZStd::string originalLocation;
// extract the (full) directory chain from the path: (eg from "d:/p/f.e" extract "d:/p/")
AzFramework::StringFunc::Path::GetFullPath(fullPath.c_str(), originalLocation);
// have to go through filesystem because we have no way to pipe data through mcpp (because of single threaded static link call and buffer limits)
// we can't use a temporary folder because CreateJobs API does not warrant side effects, and does not prepare a temp folder.
// we can't use the OS temp folder anyway, because many includes (eg #include "../RPI/Shadow.h") are relative and will only work from the original location
AZStd::string prependedPath = ShaderBuilderUtility::DumpAzslPrependedCode(
BuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()),
shaderPlatformInterface->GetAPIName().GetStringView());
// run mcpp
PreprocessorData preprocessorData = PreprocessSource(prependedPath, fullPath, buildOptions.m_preprocessorSettings);
jobDescriptor.m_jobParameters[(u32)JobParameterIndices::PreprocessorError] = preprocessorData.diagnostics; // save for ProcessJob
jobDescriptor.m_jobParameters[(u32)JobParameterIndices::PreprocessedCode] = preprocessorData.code; // save for ProcessJob
AZ::IO::SystemFile::Delete(prependedPath.c_str()); // don't let that intermediate file dirty a folder under source version control.
for (AZStd::string includePath : preprocessorData.includedPaths)
{
// m_sourceFileDependencyList does not support paths with "." or ".." for relative lookup, but the preprocessor
// may produce path strings like "C:/a/b/c/../../d/file.azsli" so we have to normalize
AzFramework::StringFunc::Path::Normalize(includePath);
AssetBuilderSDK::SourceFileDependency includeFileDependency;
includeFileDependency.m_sourceFileDependencyPath = includePath;
response.m_sourceFileDependencyList.emplace_back(includeFileDependency);
}
response.m_createJobOutputs.push_back(jobDescriptor);
} // all RHI platforms
} // for all request.m_enabledPlatforms
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
enum class ErrorStrategy { HardFailIfAbsent, ReturnEmptyIfAbsent };
static AZStd::string GetJobParameterValue(const AssetBuilderSDK::ProcessJobRequest& request, JobParameterIndices index, [[maybe_unused]] ErrorStrategy failBehavior)
{
auto iterator = request.m_jobDescription.m_jobParameters.find(static_cast<u32>(index));
if (iterator == request.m_jobDescription.m_jobParameters.end())
{
AZ_Error(AzslBuilder::BuilderName, failBehavior != ErrorStrategy::HardFailIfAbsent, "Saved data is missing in job parameters. for index [%d]", index);
return {};
}
return iterator->second;
}
// eg: ("D:/p/x.a", "D:/p/x.b") -> yes
static bool HasSameFileName(const AZStd::string& lhsPath, const AZStd::string& rhsPath)
{
using namespace StringFunc::Path;
AZStd::string stem1;
GetFileName(lhsPath.c_str(), stem1);
AZStd::string stem2;
GetFileName(rhsPath.c_str(), stem2);
return stem1 == stem2;
}
void AzslBuilder::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
if (request.m_jobDescription.m_jobParameters.find((u32)JobParameterIndices::SkipJob) != request.m_jobDescription.m_jobParameters.end())
{
AZ_TracePrintf(BuilderName, "Early out because this file was determined to not need an independent build\n");
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
// report the deffered diagnostics:
const AZStd::string& preprocessorErrors = GetJobParameterValue(request, JobParameterIndices::PreprocessorError, ErrorStrategy::ReturnEmptyIfAbsent);
if (!preprocessorErrors.empty())
{
bool foundErrors = RHI::ReportErrorMessages(BuilderName, preprocessorErrors.c_str());
if (foundErrors)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
}
const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks();
AZStd::string fullSourcePath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullSourcePath, true);
// extract "name" from "P:/F/name.x"
AZStd::string sourceStemName;
AzFramework::StringFunc::Path::GetFileName(fullSourcePath.c_str(), sourceStemName);
GlobalBuildOptions buildOptions = ReadBuildOptions(BuilderName);
// get the shader platform interface that matches this job's API
const AZStd::string& apiName = GetJobParameterValue(request, JobParameterIndices::ApiName, ErrorStrategy::HardFailIfAbsent);
const AZStd::string& preprocessedCode = GetJobParameterValue(request, JobParameterIndices::PreprocessedCode, ErrorStrategy::ReturnEmptyIfAbsent);
RHI::ShaderPlatformInterface* platformInterface = GetShaderPlatformInterfaceForApi(apiName, request.m_platformInfo);
if (!platformInterface)
{
AZ_Error(BuilderName, false, "Could not retreive Shader Platform Interface");
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
const bool isSrgi = AzFramework::StringFunc::Path::IsExtension(fullSourcePath.c_str(), SrgIncludeExtension);
const bool isAzsli = AzFramework::StringFunc::Path::IsExtension(fullSourcePath.c_str(), "azsli");
const bool isShader = AzFramework::StringFunc::Path::IsExtension(fullSourcePath.c_str(), RPI::ShaderSourceData::Extension);
if (isShader)
{
// read .shader -> access azsl path -> make absolute
RPI::ShaderSourceData shaderAssetSource;
AZStd::shared_ptr<ShaderFiles> inputFiles = ShaderBuilderUtility::PrepareSourceInput(BuilderName, fullSourcePath, shaderAssetSource);
if (!inputFiles)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (shaderAssetSource.IsRhiBackendDisabled(platformInterface->GetAPIName()))
{
// Gracefully do nothing and return success.
AZ_TracePrintf(
BuilderName, "Skipping shader compilation [%s] for API [%s]\n", fullSourcePath.c_str(),
platformInterface->GetAPIName().GetCStr());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
// Save .shader file name
AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.data(), inputFiles->m_shaderFileName);
// Verify the presence of potential differences between the global options, and local options:
bool mustRebuild = buildOptions.m_compilerArguments.HasDifferentAzslcArguments(shaderAssetSource.m_compiler);
// Merge compiler options coming from 2 source: global options (from project Config/), and .shader options.
// We define a merge behavior that is: ".shader wins if set" (local overrides global)
buildOptions.m_compilerArguments.Merge(shaderAssetSource.m_compiler);
// Earlier, we declared a job dependency on the .azsl's job, let's access the produced assets:
uint32_t subId = ShaderBuilderUtility::MakeAzslBuildProductSubId(
RPI::ShaderAssetSubId::GeneratedHlslSource, platformInterface->GetAPIType());
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(inputFiles->m_azslSourceFullPath, subId);
AZ_Warning(BuilderName, assetIdOutcome.IsSuccess(), "Product of dependency %s not found: this is an oddity but build can continue.", inputFiles->m_azslSourceFullPath.c_str());
if (assetIdOutcome.IsSuccess())
{
// The .azsl build job didn't know about the build options listed in the .shader
// so it produced "generic" artifacts xxx.ia.json, xxx.hlsl, etc.
if (!mustRebuild)
{
// They are in fact sufficient. nothing more to do
AZ_TracePrintf(BuilderName, "Product output already built by %s. exiting.", inputFiles->m_azslSourceFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
// Otherwise, let's go again, but we need to modify the output's name to avoid product conflicts.*
AZ_TracePrintf(BuilderName, "Product output already built by %s is not reusable because of incompatible azslc CompilerHints: launching independent build", inputFiles->m_azslSourceFullPath.c_str());
}
if (HasSameFileName(fullSourcePath, inputFiles->m_azslSourceFullPath))
{
// let's add a "distinguisher" to the names of the outproduct artifacts of this build round.*
// Because otherwise the asset processor is not going to accept an overwrite of the ones output by the .azsl job
static constexpr char RebuildSuffix[] = ".shader-w-diff-azslc-opts";
sourceStemName += RebuildSuffix;
}
}
AZStd::string preprocessedPath = ShaderBuilderUtility::DumpPreprocessedCode(
BuilderName,
preprocessedCode,
request.m_tempDirPath,
sourceStemName,
apiName);
AZ_TraceContext("Platform API", apiName);
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
// compiler setup
ShaderBuilder::AzslCompiler azslc(preprocessedPath);
AZStd::string compilerParameters = platformInterface->GetAzslCompilerParameters(buildOptions.m_compilerArguments);
compilerParameters += " ";
compilerParameters += platformInterface->GetAzslCompilerWarningParameters(buildOptions.m_compilerArguments);
AtomShaderConfig::AddParametersFromConfigFile(compilerParameters, request.m_platformInfo);
if (isSrgi || isAzsli)
{
// When compiling srgi or azsli files, the SRGs may appear as unused. It is necessary
// to remove the flag --strip-unused-srgs in case it is present in the compiler parameters.
AzFramework::StringFunc::Replace(compilerParameters, " --strip-unused-srgs", "");
}
AZStd::string outputName = AZStd::string::format("%s.%s.hlsl", sourceStemName.c_str(), apiName.c_str());
AzFramework::StringFunc::Path::Join(request.m_tempDirPath.c_str(), outputName.c_str(), outputName, true);
auto emitFullOutcome = azslc.EmitFullData(compilerParameters, outputName);
if (!emitFullOutcome.IsSuccess())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
for (int i = 0; i < emitFullOutcome.GetValue().size(); ++i)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = emitFullOutcome.GetValue()[i];
static const AZ::Uuid AzslOutcomeType = "{6977AEB1-17AD-4992-957B-23BB2E85B18B}";
jobProduct.m_productAssetType = AzslOutcomeType;
jobProduct.m_productSubID = ShaderBuilderUtility::MakeAzslBuildProductSubId(ShaderBuilderUtility::AzslSubProducts::SubList[i], platformInterface->GetAPIType());
jobProduct.m_dependenciesHandled = true;
// Note that the output products are not traditional product assets that will be used by the game project.
// They are artifacts that are produced once, cached, and used later by other AssetBuilders as a way to centralize build organization.
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
const AZStd::sys_time_t endTime = AZStd::GetTimeNowTicks();
const AZStd::sys_time_t deltaTime = endTime - startTime;
const float elapsedTimeSeconds = (float)(deltaTime) / (float)AZStd::GetTimeTicksPerSecond();
AZ_TracePrintf(BuilderName, "Finished compiling %s in %.2f seconds\n", request.m_sourceFile.c_str(), elapsedTimeSeconds);
ShaderBuilderUtility::LogProfilingData(BuilderName, sourceStemName);
} // end ProcessJob
} // ShaderBuilder
} // AZ
@@ -1,68 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Atom/RHI.Edit/Utils.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/IO/FileIO.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <CommonFiles/Preprocessor.h>
#include <AzslCompiler.h>
#include "AtomShaderConfig.h"
#include "ShaderBuilderUtility.h"
namespace AZ
{
namespace ShaderBuilder
{
static constexpr char AzslBuilderName[] = "AzslBuilder";
//! This builder is the forerunner of the shader build pipeline.
//! It will perform the first 3 transformations on shader files (.shader) and AZSL-containing files (.azsl/.azsli/.srgi)
//! Which are: [prepend common header -> preprocess -> transpile AZSL to HLSL & reflect all shader program properties of interest into json files]
//! The next builders in the chain: SRG/Shader/Variant, will consume the output products of this builder, without having to re-run Azslc nor Mcpp
//! Note that the output products are not traditional product assets that will be used by the game project.
//! They are artifacts that are produced once, cached, and used later by other AssetBuilders as a way to centralize build organization.
class AzslBuilder
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler
{
public:
static constexpr const char* BuilderName = AzslBuilderName;
static constexpr char JobKey[] = "AZSL Build";
static constexpr char SrgIncludeExtension[] = "srgi";
// Asset Builder Callback Functions
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const;
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
//////////////////////////////////////////////////////////////////////////
// AssetBuilderSDK::AssetBuilderCommandBus interface
void ShutDown() override {};
//////////////////////////////////////////////////////////////////////////
static AZ::Uuid GetUUID();
};
//! Helper for dependent builders
void AddAzslBuilderJobDependency(AssetBuilderSDK::JobDescriptor& jobDescriptor, const AZStd::string& platformInfoIdentifier, AZStd::string_view apiName, AZStd::string_view fullFilePath);
} // ShaderBuilder
} // AZ
@@ -123,7 +123,7 @@ namespace AZ
namespace SubProducts = ShaderBuilderUtility::AzslSubProducts;
Outcome<SubProducts::Paths> AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/, const char * addSuffix) const
Outcome<SubProducts::Paths> AzslCompiler::EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile /* = ""*/) const
{
bool success = Compile("--full " + parameters, outputFile);
if (!success)
@@ -139,16 +139,6 @@ namespace AZ
// append .json if it's one of those subs:
auto listOfJsons = { SubProducts::ia, SubProducts::om, SubProducts::srg, SubProducts::options, SubProducts::bindingdep };
subProductFilePath += AZStd::any_of(AZ_BEGIN_END(listOfJsons), [&](auto v) { return v == subProduct.m_value; }) ? ".json" : "";
// [GFX TODO] Remove when [ATOM-15472]
if (addSuffix)
{
// Rename the product file.
AZStd::string finalSubProductFilePath = AZStd::string::format("%s%s", subProductFilePath.c_str(), addSuffix);
AZ::IO::Move(subProductFilePath.c_str(), finalSubProductFilePath.c_str());
subProductFilePath = finalSubProductFilePath;
}
productPaths[subProduct.m_value] = subProductFilePath;
}
productPaths[SubProducts::azslin] = GetInputFilePath(); // post-fixup this one after the loop, because it's not an output of azslc, it's an output of the builder though.
@@ -38,9 +38,8 @@ namespace AZ
//! @param inputFilePath The target input file to compile. Should be a valid AZSL file with no preprocessing directives.
AzslCompiler(const AZStd::string& inputFilePath);
//! [GFX TODO] Remove @addSuffix when [ATOM-15472]
//! compile with --full and generate all .json files
Outcome<ShaderBuilderUtility::AzslSubProducts::Paths> EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "", const char * addSuffix = nullptr) const;
Outcome<ShaderBuilderUtility::AzslSubProducts::Paths> EmitFullData(const AZStd::string& parameters, const AZStd::string& outputFile = "") const;
//! compile to HLSL independently
bool EmitShader(AZ::IO::GenericStream& outputStream, const AZStd::string& extraCompilerParams) const;
//! compile with --ia independently and populate document @output
@@ -22,7 +22,6 @@
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <AzToolsFramework/ToolsComponents/ToolsAssetCatalogBus.h>
@@ -83,42 +82,10 @@ namespace AZ
RHI::ShaderPlatformInterfaceRegisterBus::Handler::BusConnect();
ShaderPlatformInterfaceRequestBus::Handler::BusConnect();
// Register AZSL's compilation products Builder
AssetBuilderSDK::AssetBuilderDesc azslBuilderDescriptor;
azslBuilderDescriptor.m_name = "AZSL Builder";
azslBuilderDescriptor.m_version = 8; // ATOM-15276
// register all extensions thay may carry azsl code. header. main shader. or SRG
azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsli", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
azslBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", SrgLayoutBuilder::MergedPartialSrgsExtension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
azslBuilderDescriptor.m_busId = AzslBuilder::GetUUID();
azslBuilderDescriptor.m_createJobFunction = AZStd::bind(&AzslBuilder::CreateJobs, &m_azslBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
azslBuilderDescriptor.m_processJobFunction = AZStd::bind(&AzslBuilder::ProcessJob, &m_azslBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
m_azslBuilder.BusConnect(azslBuilderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, azslBuilderDescriptor);
// Register Shader Resource Group Layout Builder
AssetBuilderSDK::AssetBuilderDesc srgLayoutBuilderDescriptor;
srgLayoutBuilderDescriptor.m_name = "Shader Resource Group Layout Builder";
srgLayoutBuilderDescriptor.m_version = 55; // ATOM-15276
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.azsli", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
srgLayoutBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", SrgLayoutBuilder::MergedPartialSrgsExtension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
srgLayoutBuilderDescriptor.m_busId = SrgLayoutBuilder::GetUUID();
srgLayoutBuilderDescriptor.m_createJobFunction = AZStd::bind(&SrgLayoutBuilder::CreateJobs, &m_srgLayoutBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
srgLayoutBuilderDescriptor.m_processJobFunction = AZStd::bind(&SrgLayoutBuilder::ProcessJob, &m_srgLayoutBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
m_srgLayoutBuilder.BusConnect(srgLayoutBuilderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, srgLayoutBuilderDescriptor);
m_srgLayoutBuilder.Activate();
// Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
shaderAssetBuilderDescriptor.m_version = 100; // ATOM-14298
shaderAssetBuilderDescriptor.m_version = 101; // ATOM-15472
// .shader file changes trigger rebuilds
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
@@ -133,7 +100,7 @@ namespace AZ
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilderDescriptor.m_version = 21; // ATOM-14298
shaderVariantAssetBuilderDescriptor.m_version = 22; // ATOM-15472
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
@@ -145,7 +112,7 @@ namespace AZ
// Register Precompiled Shader Builder
AssetBuilderSDK::AssetBuilderDesc precompiledShaderBuilderDescriptor;
precompiledShaderBuilderDescriptor.m_name = "Precompiled Shader Builder";
precompiledShaderBuilderDescriptor.m_version = 8; // ATOM-15276
precompiledShaderBuilderDescriptor.m_version = 9; // ATOM-15472
precompiledShaderBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", AZ::PrecompiledShaderBuilder::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
precompiledShaderBuilderDescriptor.m_busId = azrtti_typeid<PrecompiledShaderBuilder>();
precompiledShaderBuilderDescriptor.m_createJobFunction = AZStd::bind(&PrecompiledShaderBuilder::CreateJobs, &m_precompiledShaderBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
@@ -153,53 +120,13 @@ namespace AZ
m_precompiledShaderBuilder.BusConnect(precompiledShaderBuilderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, precompiledShaderBuilderDescriptor);
// Register Shader Asset Builder 2
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilder2Descriptor;
shaderAssetBuilder2Descriptor.m_name = "Shader Asset Builder 2";
shaderAssetBuilder2Descriptor.m_version = 1; // ATOM-15276
// .shader2 file changes trigger rebuilds
shaderAssetBuilder2Descriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(
AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension2),
AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilder2Descriptor.m_busId = azrtti_typeid<ShaderAssetBuilder2>();
shaderAssetBuilder2Descriptor.m_createJobFunction =
AZStd::bind(&ShaderAssetBuilder2::CreateJobs, &m_shaderAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
shaderAssetBuilder2Descriptor.m_processJobFunction =
AZStd::bind(&ShaderAssetBuilder2::ProcessJob, &m_shaderAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
m_shaderAssetBuilder2.BusConnect(shaderAssetBuilder2Descriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(
&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderAssetBuilder2Descriptor);
// Register Shader Variant Asset Builder 2
AssetBuilderSDK::AssetBuilderDesc shaderVariantAssetBuilder2Descriptor;
shaderVariantAssetBuilder2Descriptor.m_name = "Shader Variant Asset Builder 2";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilder2Descriptor.m_version = 1; // ATOM-15276
shaderVariantAssetBuilder2Descriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(
AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension2),
AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilder2Descriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder2>();
shaderVariantAssetBuilder2Descriptor.m_createJobFunction = AZStd::bind(
&ShaderVariantAssetBuilder2::CreateJobs, &m_shaderVariantAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
shaderVariantAssetBuilder2Descriptor.m_processJobFunction = AZStd::bind(
&ShaderVariantAssetBuilder2::ProcessJob, &m_shaderVariantAssetBuilder2, AZStd::placeholders::_1, AZStd::placeholders::_2);
m_shaderVariantAssetBuilder2.BusConnect(shaderVariantAssetBuilder2Descriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(
&AssetBuilderSDK::AssetBuilderBus::Handler::RegisterBuilderInformation, shaderVariantAssetBuilder2Descriptor);
}
void AzslShaderBuilderSystemComponent::Deactivate()
{
m_shaderAssetBuilder.BusDisconnect();
m_srgLayoutBuilder.BusDisconnect();
m_shaderVariantAssetBuilder.BusDisconnect();
m_precompiledShaderBuilder.BusDisconnect();
m_shaderAssetBuilder2.BusDisconnect();
m_shaderVariantAssetBuilder2.BusDisconnect();
RHI::ShaderPlatformInterfaceRegisterBus::Handler::BusDisconnect();
ShaderPlatformInterfaceRequestBus::Handler::BusDisconnect();
@@ -18,14 +18,10 @@
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include "AzslBuilder.h"
#include "SrgLayoutBuilder.h"
#include "ShaderAssetBuilder.h"
#include "ShaderVariantAssetBuilder.h"
#include "PrecompiledShaderBuilder.h"
#include "ShaderPlatformInterfaceRequest.h"
#include "ShaderAssetBuilder2.h"
#include "ShaderVariantAssetBuilder2.h"
namespace AZ
{
@@ -68,13 +64,9 @@ namespace AZ
AZStd::vector<RHI::ShaderPlatformInterface*> GetShaderPlatformInterface(const AssetBuilderSDK::PlatformInfo& platformInfo) override;
private:
AzslBuilder m_azslBuilder;
SrgLayoutBuilder m_srgLayoutBuilder;
ShaderAssetBuilder m_shaderAssetBuilder;
ShaderVariantAssetBuilder m_shaderVariantAssetBuilder;
PrecompiledShaderBuilder m_precompiledShaderBuilder;
ShaderAssetBuilder2 m_shaderAssetBuilder2;
ShaderVariantAssetBuilder2 m_shaderVariantAssetBuilder2;
/// Contains the ShaderPlatformInterface for all registered RHIs
AZStd::unordered_map<RHI::APIType, RHI::ShaderPlatformInterface*> m_shaderPlatformInterfaces;
@@ -72,23 +72,6 @@ namespace AZ
{
AZStd::vector<AssetBuilderSDK::JobDependency> jobDependencyList;
// setup dependencies on the azsrg asset file names
for (const auto& srgFileName : precompiledShaderAsset.m_srgAssetFileNames)
{
AZStd::string srgAssetPath = RPI::AssetUtils::ResolvePathReference(request.m_sourceFile.c_str(), srgFileName);
AssetBuilderSDK::SourceFileDependency sourceDependency;
sourceDependency.m_sourceFileDependencyPath = srgAssetPath;
response.m_sourceFileDependencyList.push_back(sourceDependency);
AssetBuilderSDK::JobDependency jobDependency;
jobDependency.m_jobKey = "azsrg";
jobDependency.m_platformIdentifier = platformInfo.m_identifier;
jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
jobDependency.m_sourceFile = sourceDependency;
jobDependencyList.push_back(jobDependency);
}
// setup dependencies on the root azshadervariant asset file names
for (const auto& rootShaderVariantAsset : precompiledShaderAsset.m_rootShaderVariantAssets)
{
@@ -158,25 +141,6 @@ namespace AZ
AssetBuilderSDK::JobProduct jobProduct;
// load Srg product assets
// these are the dependency Srg asset products that were processed prior to running this job
RPI::ShaderAssetCreator::ShaderResourceGroupAssets srgProductAssets;
for (const auto& srgAssetFileName : precompiledShaderAsset.m_srgAssetFileNames)
{
auto assetOutcome = RPI::AssetUtils::LoadAsset<RPI::ShaderResourceGroupAsset>(request.m_fullPath, srgAssetFileName, 0);
if (!assetOutcome)
{
AZ_Error(PrecompiledShaderBuilderName, false, "Failed to retrieve Srg asset for file [%s]", srgAssetFileName.c_str());
return;
}
srgProductAssets.push_back(assetOutcome.GetValue());
AssetBuilderSDK::ProductDependency productDependency;
productDependency.m_dependencyId = assetOutcome.GetValue().GetId();
productDependency.m_flags = AZ::Data::ProductDependencyInfo::CreateFlags(AZ::Data::AssetLoadBehavior::PreLoad);
jobProduct.m_dependencies.push_back(productDependency);
}
// load the variant product assets
// these are the dependency root variant asset products that were processed prior to running this job
RPI::ShaderAssetCreator::ShaderRootVariantAssets rootVariantProductAssets;
@@ -202,8 +166,7 @@ namespace AZ
// Note that the Srg and Variant assets do not have embedded asset references and are processed with the RC Copy functionality
RPI::ShaderAssetCreator shaderAssetCreator;
shaderAssetCreator.Clone(Uuid::CreateRandom(),
shaderAsset,
srgProductAssets,
*shaderAsset,
rootVariantProductAssets);
Data::Asset<RPI::ShaderAsset> outputShaderAsset;
@@ -47,8 +47,7 @@
#include <AzCore/std/sort.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include "AzslBuilder.h"
#include "SrgLayoutBuilder.h"
#include "AzslCompiler.h"
#include "ShaderVariantAssetBuilder.h"
#include "ShaderBuilderUtility.h"
#include "ShaderPlatformInterfaceRequest.h"
@@ -63,19 +62,6 @@ namespace AZ
static constexpr char ShaderAssetBuilderName[] = "ShaderAssetBuilder";
static constexpr uint32_t ShaderAssetBuildTimestampParam = 0;
static void AddSrgLayoutJobDependency(AssetBuilderSDK::JobDescriptor& jobDescriptor, const AssetBuilderSDK::PlatformInfo& platformInfo, AZStd::string_view fullFilePath)
{
AssetBuilderSDK::SourceFileDependency fileDependency;
fileDependency.m_sourceFileDependencyPath = fullFilePath;
AssetBuilderSDK::JobDependency srgLayoutJobDependency;
srgLayoutJobDependency.m_jobKey = SrgLayoutBuilder::SrgLayoutBuilderJobKey;
srgLayoutJobDependency.m_platformIdentifier = platformInfo.m_identifier;
srgLayoutJobDependency.m_sourceFile = fileDependency;
srgLayoutJobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
jobDescriptor.m_jobDependencyList.emplace_back(srgLayoutJobDependency);
}
void ShaderAssetBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{
AZStd::string fullPath;
@@ -90,20 +76,58 @@ namespace AZ
// the PC's ShaderAsset).
AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond();
// *** block (remove all this when [ATOM-4225] addressed)
AZStd::string azslFullPath;
// Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job.
// and the macro options to preprocess.
auto descriptorParseOutput = ShaderBuilderUtility::LoadShaderDataJson(fullPath);
if (!descriptorParseOutput.IsSuccess())
auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(fullPath);
if (!descriptorParseOutcome.IsSuccess())
{
AZ_Error(ShaderAssetBuilderName, false, "Failed to parse Shader Descriptor JSON: %s", descriptorParseOutput.GetError().c_str());
AZ_Error(
ShaderAssetBuilderName, false, "Failed to parse Shader Descriptor JSON: %s",
descriptorParseOutcome.GetError().c_str());
return;
}
ShaderBuilderUtility::GetAbsolutePathToAzslFile(fullPath, descriptorParseOutput.GetValue().m_source, azslFullPath);
AZ_Warning(ShaderAssetBuilderName, IO::FileIOBase::GetInstance()->Exists(azslFullPath.c_str()), "Shader program listed as the source entry does not exist: %s.", azslFullPath.c_str());
RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
AZStd::string azslFullPath;
ShaderBuilderUtility::GetAbsolutePathToAzslFile(fullPath, shaderSourceData.m_source, azslFullPath);
if (!IO::FileIOBase::GetInstance()->Exists(azslFullPath.c_str()))
{
AZ_Error(
ShaderAssetBuilderName, false, "Shader program listed as the source entry does not exist: %s.", azslFullPath.c_str());
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
return;
}
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilderName);
// *** end block (remove when [Atom-4225])
// [GFX TODO] [ATOM-14966] In principle, based on macro definitions, included files can change per supervariant.
// So, the list of source asset dependencies must be collected by running MCPP on each supervariant.
// For now, we will run MCPP only once because CreateJobs() should be as light as possible.
//
// Regardless of the PlatformInfo and enabled ShaderPlatformInterfaces, the azsl file will be preprocessed
// with the sole purpose of extracting all included files. For each included file a SourceDependency will be declared.
PreprocessorData output;
buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler);
PreprocessFile(azslFullPath, output, buildOptions.m_preprocessorSettings, true, true);
for (auto includePath : output.includedPaths)
{
// m_sourceFileDependencyList does not support paths with "." or ".." for relative lookup, but the preprocessor
// may produce path strings like "C:/a/b/c/../../d/file.azsli" so we have to normalize
AzFramework::StringFunc::Path::Normalize(includePath);
AssetBuilderSDK::SourceFileDependency includeFileDependency;
includeFileDependency.m_sourceFileDependencyPath = includePath;
response.m_sourceFileDependencyList.emplace_back(includeFileDependency);
}
{
// Add the AZSL as source dependency
AssetBuilderSDK::SourceFileDependency azslFileDependency;
azslFileDependency.m_sourceFileDependencyPath = azslFullPath;
response.m_sourceFileDependencyList.emplace_back(azslFileDependency);
}
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
@@ -111,6 +135,10 @@ namespace AZ
// Get the platform interfaces to be able to access the prepend file
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(platformInfo);
if (platformInterfaces.empty())
{
continue;
}
AssetBuilderSDK::JobDescriptor jobDescriptor;
jobDescriptor.m_priority = 2;
@@ -118,46 +146,6 @@ namespace AZ
jobDescriptor.m_critical = true;
jobDescriptor.m_jobKey = ShaderAssetBuilderJobKey;
jobDescriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
// queue up AzslBuilder dependencies:
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
AddAzslBuilderJobDependency(jobDescriptor, platformInfo.m_identifier, shaderPlatformInterface->GetAPIName().GetCStr(), fullPath);
{// *** block (remove all this when [ATOM-4225] addressed)
// this is the same code as in AzslBuilder.cpp's CreateJobs. This is not supposed to be repeated here (temporary hack), so not factorized. refer to AzslBuilder.cpp for code comments
AZStd::string prependedAzslSourceCode;
RHI::PrependArguments args;
args.m_sourceFile = fullPath.c_str();
args.m_prependFile = shaderPlatformInterface->GetAzslHeader(platformInfo);
args.m_addSuffixToFileName = shaderPlatformInterface->GetAPIName().GetCStr();
args.m_destinationStringOpt = &prependedAzslSourceCode;
if (RHI::PrependFile(args) == fullPath)
{
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
return;
}
AZStd::string originalLocation;
AzFramework::StringFunc::Path::GetFullPath(fullPath.c_str(), originalLocation);
AZStd::string prependedPath = ShaderBuilderUtility::DumpAzslPrependedCode(
ShaderAssetBuilderName, prependedAzslSourceCode, originalLocation, ShaderBuilderUtility::ExtractStemName(fullPath.c_str()), shaderPlatformInterface->GetAPIName().GetStringView());
PreprocessorData output;
buildOptions.m_compilerArguments.Merge(descriptorParseOutput.GetValue().m_compiler);
PreprocessFile(azslFullPath, output, buildOptions.m_preprocessorSettings, true, true);
// srg layout builder job dependency on the azsl file itself.
// If there's a ShaderResourceGroup defined in this azsl file
// then its azsrg asset must be ready before We compile it. The azsrg requirement
// is only for diagnostics, because a preprocessed (flattened) azsl file contains
// all the SRG data it needs.
AddSrgLayoutJobDependency(jobDescriptor, platformInfo, azslFullPath);
for (auto included : output.includedPaths)
{
AddSrgLayoutJobDependency(jobDescriptor, platformInfo, included.c_str());
}
AZ::IO::SystemFile::Delete(prependedPath.c_str()); // don't let that intermediate file dirty a folder under source version control.
}// *** end block remove when [Atom-4225]
}
jobDescriptor.m_jobParameters.emplace(ShaderAssetBuildTimestampParam, AZStd::to_string(shaderAssetBuildTimestamp));
response.m_createJobOutputs.push_back(jobDescriptor);
@@ -166,141 +154,55 @@ namespace AZ
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
static AssetBuilderSDK::ProcessJobResultCode CompileForAPI(
const ShaderBuilderUtility::AzslSubProducts::Paths& pathOfProductFiles,
RPI::ShaderAssetCreator& shaderAssetCreator,
RHI::ShaderPlatformInterface* shaderPlatformInterface,
AssetBuilderSDK::ProcessJobResponse& response,
AzslData& azslData,
const RHI::ShaderCompilerArguments& shaderCompilerArguments,
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout,
const RPI::ShaderSourceData& shaderSourceDataDescriptor,
AZStd::sys_time_t shaderAssetBuildTimestamp,
const ShaderResourceGroupAssets& srgAssets,
BindingDependencies& bindingDependencies,
const RootConstantData& rootConstantData,
const AssetBuilderSDK::ProcessJobRequest& request)
static bool SerializeOutShaderAsset(Data::Asset<RPI::ShaderAsset> shaderAsset,
const AZStd::string& tempDirPath,
AssetBuilderSDK::ProcessJobResponse& response)
{
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
const AZStd::string& tempDirPath = request.m_tempDirPath;
// discover entry points
MapOfStringToStageType shaderEntryPoints;
if (shaderSourceDataDescriptor.m_programSettings.m_entryPoints.empty())
AZStd::string shaderAssetFileName = AZStd::string::format("%s.%s", shaderAsset->GetName().GetCStr(), RPI::ShaderAsset::Extension);
AZStd::string shaderAssetOutputPath;
AzFramework::StringFunc::Path::ConstructFull(tempDirPath.data(), shaderAssetFileName.data(), shaderAssetOutputPath, true);
if (!Utils::SaveObjectToFile(shaderAssetOutputPath, DataStream::ST_BINARY, shaderAsset.Get()))
{
AZ_TracePrintf(ShaderAssetBuilderName, "ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
AZ_Error(ShaderAssetBuilderName, false, "Failed to output Shader Descriptor");
return false;
}
else
AssetBuilderSDK::JobProduct shaderJobProduct;
if (!AssetBuilderSDK::OutputObject(shaderAsset.Get(), shaderAssetOutputPath, azrtti_typeid<RPI::ShaderAsset>(),
aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::ShaderAsset), shaderJobProduct))
{
for (auto& iter : shaderSourceDataDescriptor.m_programSettings.m_entryPoints)
{
shaderEntryPoints[iter.m_name] = iter.m_type;
}
AZ_Error(ShaderAssetBuilderName, false, "Failed to output product dependencies.");
return false;
}
response.m_outputProducts.push_back(AZStd::move(shaderJobProduct));
return true;
}
// Check if we were canceled before we do any heavy processing of
// the shader data (compiling the shader kernels, processing SRG
// and pipeline layout data, etc.).
if (jobCancelListener.IsCancelled())
{
return AssetBuilderSDK::ProcessJobResult_Cancelled;
}
// Signal the begin of shader data for an RHI API.
shaderAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor = ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi(
ShaderAssetBuilderName, shaderPlatformInterface, bindingDependencies, srgAssets, shaderEntryPoints, shaderCompilerArguments, &rootConstantData);
if (!pipelineLayoutDescriptor)
{
AZ_Error(ShaderAssetBuilderName, false, "Failed to build pipeline layout descriptor for api=[%s]",
shaderPlatformInterface->GetAPIName().GetCStr());
AssetBuilderSDK::ProcessJobResult_Failed;
}
for (const auto& srgAsset : srgAssets)
{
shaderAssetCreator.AddShaderResourceGroupAsset(srgAsset);
}
shaderAssetCreator.SetPipelineLayout(pipelineLayoutDescriptor);
// Generate shader source.
AZStd::string hlslSourcePath = pathOfProductFiles[ShaderBuilderUtility::AzslSubProducts::hlsl];
Outcome<AZStd::string, AZStd::string> hlslSourceContent = Utils::ReadFile(hlslSourcePath);
if (!hlslSourceContent.IsSuccess())
{
AZ_Error(ShaderAssetBuilderName, false, "Failed to obtain shader source from %s. [%s]", hlslSourcePath.c_str(), hlslSourceContent.TakeError().c_str());
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// The root ShaderVariantAsset needs to be created with the known uuid of the source .shader asset because
// the ShaderAsset owns a Data::Asset<> reference that gets serialized. It must have the correct uuid
// so the root ShaderVariantAsset is found when the ShaderAsset is deserialized.
AZStd::string fullSourcePath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullSourcePath, true);
const uint32_t productSubID = RPI::ShaderAsset::MakeAssetProductSubId(
shaderPlatformInterface->GetAPIUniqueIndex(),
aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::RootShaderVariantAsset));
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(fullSourcePath, productSubID);
AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", fullSourcePath.c_str());
const Data::AssetId variantAssetId = assetIdOutcome.TakeValue();
// We always include the root shader variant (all options are unspecified) in the ShaderAsset itself.
ShaderVariantCreationContext variantCreationContext = { variantAssetId, hlslSourcePath, hlslSourceContent.GetValue(), shaderSourceDataDescriptor,
tempDirPath, request.m_platformInfo, *shaderOptionGroupLayout, shaderEntryPoints, shaderAssetBuildTimestamp };
AZ::Outcome<Data::Asset<RPI::ShaderVariantAsset>, AZStd::string> outcomeForShaderVariantAsset = ShaderVariantAssetBuilder::CreateShaderVariantAssetForAPI(
RPI::ShaderVariantListSourceData::VariantInfo(),
variantCreationContext,
*shaderPlatformInterface,
azslData,
shaderCompilerArguments,
pathOfProductFiles[ShaderBuilderUtility::AzslSubProducts::om],
pathOfProductFiles[ShaderBuilderUtility::AzslSubProducts::ia]);
if (!outcomeForShaderVariantAsset.IsSuccess())
{
AZ_Error(ShaderAssetBuilderName, false, "Failed to serialize out the root shader variant for API [%s]: %s"
, shaderPlatformInterface->GetAPIName().GetCStr(), outcomeForShaderVariantAsset.GetError().c_str());
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Time to save, for the given rhi::api, the root shader variant as an asset.
Data::Asset<RPI::ShaderVariantAsset> shaderVariantAsset = outcomeForShaderVariantAsset.TakeValue();
AssetBuilderSDK::JobProduct variantAssetProduct;
if (!ShaderVariantAssetBuilder::SerializeOutShaderVariantAsset(shaderVariantAsset, fullSourcePath, tempDirPath,
*shaderPlatformInterface, productSubID, variantAssetProduct))
{
AZ_Error(ShaderAssetBuilderName, false, "Failed to serialize out the root shader variant for API [%s]"
, shaderPlatformInterface->GetAPIName().GetCStr());
return AssetBuilderSDK::ProcessJobResult_Failed;
}
response.m_outputProducts.push_back(variantAssetProduct);
// add byproducts as job output products:
if (variantCreationContext.m_outputByproducts)
{
uint32_t subProductType = aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::GeneratedHlslSource) + 1;
for (const AZStd::string& byproduct : variantCreationContext.m_outputByproducts->m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
jobProduct.m_productSubID = RPI::ShaderAsset::MakeAssetProductSubId(shaderPlatformInterface->GetAPIUniqueIndex(), subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
shaderAssetCreator.SetRootShaderVariantAsset(shaderVariantAsset);
// Populate the shader asset with all entry stage attributes
static AZ::Outcome<RHI::ShaderStageAttributeMapList, AZStd::string> BuildAttributesMap(
const RHI::ShaderPlatformInterface* shaderPlatformInterface,
const AzslData& azslData,
const MapOfStringToStageType& shaderEntryPoints,
bool& hasRasterProgram)
{
hasRasterProgram = false;
bool hasComputeProgram = false;
bool hasRayTracingProgram = false;
RHI::ShaderStageAttributeMapList attributeMaps;
attributeMaps.resize(RHI::ShaderStageCount);
for (const auto& shaderEntry : shaderSourceDataDescriptor.m_programSettings.m_entryPoints)
for (const auto& shaderEntryPoint : shaderEntryPoints)
{
auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntry](const auto& func)
{
return func.m_name == shaderEntry.m_name;
});
auto shaderEntryName = shaderEntryPoint.first;
auto shaderStageType = shaderEntryPoint.second;
auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType);
hasRasterProgram |= shaderPlatformInterface->IsShaderStageForRaster(assetBuilderShaderType);
hasComputeProgram |= shaderPlatformInterface->IsShaderStageForCompute(assetBuilderShaderType);
hasRayTracingProgram |= shaderPlatformInterface->IsShaderStageForRayTracing(assetBuilderShaderType);
auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntryPoint](const auto& func) {
return func.m_name == shaderEntryPoint.first;
});
if (findId == azslData.m_functions.end())
{
@@ -309,7 +211,7 @@ namespace AZ
continue;
}
const auto shaderStage = ToRHIShaderStage(ShaderBuilderUtility::ToAssetBuilderShaderType(shaderEntry.m_type));
const auto shaderStage = ToRHIShaderStage(assetBuilderShaderType);
for (const auto& attr : findId->attributesList)
{
// Some stages like RHI::ShaderStage::Tessellation are compound and consist of two or more shader entries
@@ -320,221 +222,461 @@ namespace AZ
attributeMaps[stageIndex][attributeName] = args;
}
}
shaderAssetCreator.SetShaderStageAttributeMapList(attributeMaps);
shaderAssetCreator.EndAPI();
return AssetBuilderSDK::ProcessJobResult_Success;
}
static bool SerializeOutShaderAsset(Data::Asset<RPI::ShaderAsset> shaderAsset,
const AZStd::string& tempDirPath,
AssetBuilderSDK::ProcessJobResponse& response)
{
AZStd::string shaderAssetFileName = AZStd::string::format("%s.%s", shaderAsset->GetName().GetCStr(), RPI::ShaderAsset::Extension);
AZStd::string shaderAssetOutputPath;
AzFramework::StringFunc::Path::ConstructFull(tempDirPath.data(), shaderAssetFileName.data(), shaderAssetOutputPath, true);
if (!Utils::SaveObjectToFile(shaderAssetOutputPath, DataStream::ST_BINARY, shaderAsset.Get()))
if (hasRasterProgram && hasComputeProgram)
{
AZ_Error(ShaderAssetBuilderName, false, "Failed to output Shader Descriptor");
return false;
return AZ::Failure(AZStd::string(" Shader asset descriptor defines both a raster entry point and a compute entry point."));
}
AssetBuilderSDK::JobProduct shaderJobProduct;
if (!AssetBuilderSDK::OutputObject(shaderAsset.Get(), shaderAssetOutputPath, azrtti_typeid<RPI::ShaderAsset>(),
aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::ShaderAsset), shaderJobProduct))
if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram)
{
AZ_Error(ShaderAssetBuilderName, false, "Failed to output product dependencies.");
return false;
AZStd::string entryPointNames = ShaderBuilderUtility::GetAcceptableDefaultEntryPointNames(azslData);
return AZ::Failure(
AZStd::string::format( "Shader asset descriptor has a program variant that does not define any entry points. Either declare entry "
"points in the .shader file, or use one of the available default names (not case-sensitive): [%s]",
entryPointNames.c_str()));
}
response.m_outputProducts.push_back(AZStd::move(shaderJobProduct));
return true;
return AZ::Success(attributeMaps);
}
void ShaderAssetBuilder::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks();
AZStd::string fullSourcePath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), fullSourcePath, true);
AZStd::string shaderFullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), shaderFullPath, true);
// Save .shader file name (no extension and no parent directory path)
AZStd::string shaderFileName;
AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), shaderFileName);
RPI::ShaderAssetCreator shaderAssetCreator;
shaderAssetCreator.Begin(Uuid::CreateRandom());
// No error checking because the same calls were already executed during CreateJobs()
auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderFullPath);
RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
AZStd::string azslFullPath;
ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderFullPath, shaderSourceData.m_source, azslFullPath);
AZ_TracePrintf(ShaderAssetBuilderName, "Original AZSL File: %s \n", azslFullPath.c_str());
// read .shader -> access azsl path -> make absolute
RPI::ShaderSourceData shaderAssetSource;
AZStd::shared_ptr<ShaderFiles> inputFiles = ShaderBuilderUtility::PrepareSourceInput(ShaderAssetBuilderName, fullSourcePath, shaderAssetSource);
if (!inputFiles)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilderName);
// Save .shader file name
AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.data(), inputFiles->m_shaderFileName);
// The directory where the Azsl file was found must be added to the list of include paths
AZStd::string azslFolderPath;
AzFramework::StringFunc::Path::GetFolderPath(azslFullPath.c_str(), azslFolderPath);
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilderName, azslFolderPath.c_str());
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces;
ShaderPlatformInterfaceRequestBus::BroadcastResult(platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, request.m_platformInfo);
// Generate shaders for each of those ShaderPlatformInterfaces.
uint32_t countOfCompiledPlatformInterfaces = 0;
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(
request.m_platformInfo, shaderSourceData);
if (platformInterfaces.empty())
{
ShaderResourceGroupAssets srgAssets;
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create();
if (!shaderPlatformInterface)
{
AZ_Error(ShaderAssetBuilderName, false, "ShaderPlatformInterface for [%s] is not registered, can't compile [%s]", request.m_platformInfo.m_identifier.c_str(), request.m_sourceFile.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (shaderAssetSource.IsRhiBackendDisabled(shaderPlatformInterface->GetAPIName()))
{
// Gracefully do nothing and continue with the next shaderPlatformInterface.
AZ_TracePrintf(
ShaderAssetBuilderName, "Skipping shader compilation [%s] for API [%s]\n", inputFiles->m_shaderFileName.c_str(),
shaderPlatformInterface->GetAPIName().GetCStr());
continue;
}
AZStd::sys_time_t shaderAssetBuildTimestamp = 0;
auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam);
if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end())
{
shaderAssetBuildTimestamp = AZStd::stoull(shaderAssetBuildTimestampIterator->second);
if (AZStd::to_string(shaderAssetBuildTimestamp) != shaderAssetBuildTimestampIterator->second)
{
AZ_Assert(false, "Incorrect conversion of ShaderAssetBuildTimestampParam");
return;
}
}
AzslData azslData(inputFiles);
AZ_TraceContext("Platform API", AZStd::string{ shaderPlatformInterface->GetAPIName().GetStringView() });
AZ_TracePrintf(ShaderAssetBuilderName, "Preprocessed AZSL File: %s \n", azslData.m_preprocessedFullPath.c_str());
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
// obtain the build artifacts from the azsl builder:
auto azslArtifactsOutcome = ShaderBuilderUtility::ObtainBuildArtifactsFromAzslBuilder(ShaderAssetBuilderName, fullSourcePath, shaderPlatformInterface->GetAPIType(), request.m_platformInfo.m_identifier);
if (!azslArtifactsOutcome.IsSuccess())
{
// If it failed, it may be because the .shader source file created no products! (this happens when the build options are similar)
// (the .azsl file *always* produces AzslBuilder artifacts. The.shader file *sometimes* produces AzslBuilder artifacts, when it has build options that have to be accounted for)
// If there are no artifacts from the .shader, then we fall back to the ones from the .azsl:
azslArtifactsOutcome = ShaderBuilderUtility::ObtainBuildArtifactsFromAzslBuilder(ShaderAssetBuilderName, inputFiles->m_azslSourceFullPath, shaderPlatformInterface->GetAPIType(), request.m_platformInfo.m_identifier);
if (!azslArtifactsOutcome.IsSuccess())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
}
shaderAssetCreator.SetName(Name(azslData.m_sources->m_shaderFileName));
shaderAssetCreator.SetDrawListName(Name(shaderAssetSource.m_drawListName));
shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp);
BindingDependencies bindingDependencies;
RootConstantData rootConstantData;
AssetBuilderSDK::ProcessJobResultCode azslJsonReadResult = ShaderBuilderUtility::PopulateAzslDataFromJsonFiles(
ShaderAssetBuilderName,
azslArtifactsOutcome.GetValue(),
azslData,
srgAssets,
shaderOptionGroupLayout,
bindingDependencies,
rootConstantData);
if (azslJsonReadResult != AssetBuilderSDK::ProcessJobResult_Success)
{
response.m_resultCode = azslJsonReadResult;
return;
}
shaderAssetCreator.SetName(Name(azslData.m_sources->m_shaderFileName));
shaderAssetCreator.SetDrawListName(Name(shaderAssetSource.m_drawListName));
shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp);
// The ShaderOptionGroupLayout dictates what options/range can be used to create variants
shaderAssetCreator.SetShaderOptionGroupLayout(shaderOptionGroupLayout);
AZ_TracePrintf(ShaderAssetBuilderName, "Original AZSL File: %s \n", azslData.m_sources->m_azslSourceFullPath.c_str());
const uint32_t usedShaderOptionBits = shaderOptionGroupLayout->GetBitSize();
AZ_TracePrintf(ShaderAssetBuilderName, "Note: This shader uses %u of %u available shader variant key bits. \n", usedShaderOptionBits, RPI::ShaderVariantKeyBitCount);
// The idea of this merge is that we have compiler options coming from 2 source:
// global options (from project Config/), and .shader options.
// We define a merge behavior that is: ".shader wins if set"
RHI::ShaderCompilerArguments mergedArguments = buildOptions.m_compilerArguments;
mergedArguments.Merge(shaderAssetSource.m_compiler);
AssetBuilderSDK::ProcessJobResultCode compileResult =
CompileForAPI(
azslArtifactsOutcome.GetValue(),
shaderAssetCreator,
shaderPlatformInterface,
response,
azslData,
mergedArguments,
shaderOptionGroupLayout,
shaderAssetSource,
shaderAssetBuildTimestamp,
srgAssets,
bindingDependencies,
rootConstantData,
request);
if (compileResult != AssetBuilderSDK::ProcessJobResult_Success)
{
response.m_resultCode = compileResult;
return;
}
++countOfCompiledPlatformInterfaces;
} // end for all platforms
if (!countOfCompiledPlatformInterfaces)
{
AZ_TracePrintf(
ShaderAssetBuilderName, "No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n", request.m_sourceFile.c_str());
//No work to do. Exit gracefully.
AZ_TracePrintf(ShaderAssetBuilderName,
"No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
shaderFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
// Get the time stamp string as sys_time_t, and also convert back to string to make sure it was converted correctly.
AZStd::sys_time_t shaderAssetBuildTimestamp = 0;
auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam);
if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end())
{
shaderAssetBuildTimestamp = AZStd::stoull(shaderAssetBuildTimestampIterator->second);
if (AZStd::to_string(shaderAssetBuildTimestamp) != shaderAssetBuildTimestampIterator->second)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
AZ_Assert(false, "Incorrect conversion of ShaderAssetBuildTimestampParam");
return;
}
}
auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceData);
RPI::ShaderAssetCreator shaderAssetCreator;
shaderAssetCreator.Begin(Uuid::CreateRandom());
shaderAssetCreator.SetName(AZ::Name{shaderFileName.c_str()});
shaderAssetCreator.SetDrawListName(Name(shaderSourceData.m_drawListName));
shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp);
// The ShaderOptionGroupLayout must be the same across all supervariants because
// there can be only a single ShaderVariantTreeAsset per ShaderAsset.
// We will store here the one that results when the *.azslin file is
// compiled for the default, nameless, supervariant.
// For all other supervariants we just make sure the hashes are the same
// as this one.
RPI::Ptr<RPI::ShaderOptionGroupLayout> finalShaderOptionGroupLayout = nullptr;
// Time to describe the big picture.
// 1- Preprocess an AZSL file with MCPP (a C-Preprocessor), and generate a flat AZSL file without #include lines and any macros in it.
// Let's call it the Flat-AZSL file. There are two levels of macro definition that need to be merged before we can invoke MCPP:
// 1.1- From <GameProject>/Config/shader_global_build_options.json, which we have stored in the local variable @buildOptions.
// 1.2- From the "Supervariant" definition key, which can be different for each supervariant.
// 2- There will be one Flat-AZSL per supervariant. Each Flat-AZSL will be transpiled to HLSL with AZSLc. This means there will be one HLSL file
// per supervariant.
// 3- The generated HLSL (one HLSL per supervariant) file may contain C-Preprocessor Macros inserted by AZSLc. And that file will be given to DXC.
// DXC has a preprocessor embedded in it. DXC will be executed once for each entry function listed in the .shader file.
// There will be one DXIL compiled binary for each entry function. All the DXIL compiled binaries for each supervariant will be combined
// in the ROOT ShaderVariantAsset.
// Remark: In general, the work done by the ShaderVariantAssetBuilder is similar, but it will start from the HLSL file created; in step 2, mentioned above; by this builder,
// for each supervariant.
// At this moment We have global build options that should be merged with the build options that are common
// to all the supervariants of this shader.
buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler);
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
AZStd::string apiName(shaderPlatformInterface->GetAPIName().GetCStr());
AZ_TraceContext("Platform API", apiName);
// Signal the begin of shader data for an RHI API.
shaderAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
// Each shaderPlatformInterface has its own azsli header that needs to be prepended to the AZSL file before
// preprocessing. We will create a new temporary file that contains the combined data.
RHI::PrependArguments args;
args.m_sourceFile = azslFullPath.c_str();
args.m_prependFile = shaderPlatformInterface->GetAzslHeader(request.m_platformInfo);
args.m_addSuffixToFileName = apiName.c_str();
args.m_destinationFolder = request.m_tempDirPath.c_str();
AZStd::string prependedAzslFilePath = RHI::PrependFile(args);
if (prependedAzslFilePath == azslFullPath)
{
// The specific error is already reported by RHI::PrependFile().
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Cache common AZSLC invokation arguments related with the current RHI Backend.
// Each supervariant can, optionally, remove or add more arguments for AZSLc.
AZStd::string commonAzslcCompilerParameters =
shaderPlatformInterface->GetAzslCompilerParameters(buildOptions.m_compilerArguments);
commonAzslcCompilerParameters += " ";
commonAzslcCompilerParameters +=
shaderPlatformInterface->GetAzslCompilerWarningParameters(buildOptions.m_compilerArguments);
AtomShaderConfig::AddParametersFromConfigFile(commonAzslcCompilerParameters, request.m_platformInfo);
// The register number only makes sense if the platform uses "spaces",
// since the register Id of the resource will not change even if the pipeline layout changes.
// We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface
// appends the "--use-spaces" flag.
const bool platformUsesRegisterSpaces =
(AzFramework::StringFunc::Find(commonAzslcCompilerParameters, "--use-spaces") != AZStd::string::npos);
uint32_t supervariantIndex = 0;
for (const auto& supervariantInfo : supervariantList)
{
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
shaderAssetCreator.BeginSupervariant(supervariantInfo.m_name);
// Let's combine the global macro definitions, with the macro definitions particular to this
// supervariant. Two steps:
// 1- Supervariants can specify which macros to remove from the global definitions.
AZStd::vector<AZStd::string> macroDefinitionNamesToRemove = supervariantInfo.GetCombinedListOfMacroDefinitionNamesToRemove();
PreprocessorOptions preprocessorOptions = buildOptions.m_preprocessorSettings;
preprocessorOptions.RemovePredefinedMacros(macroDefinitionNamesToRemove);
// 2- Supervariants can specify which macros to add.
AZStd::vector<AZStd::string> macroDefinitionsToAdd = supervariantInfo.GetMacroDefinitionsToAdd();
preprocessorOptions.m_predefinedMacros.insert(
preprocessorOptions.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end());
// Run the preprocessor.
PreprocessorData output;
PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true);
RHI::ReportErrorMessages(ShaderAssetBuilderName, output.diagnostics);
// Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file.
AZStd::string superVariantAzslinStemName = shaderFileName;
if (!supervariantInfo.m_name.IsEmpty())
{
superVariantAzslinStemName += AZStd::string::format("-%s", supervariantInfo.m_name.GetCStr());
}
AZStd::string azslinFullPath = ShaderBuilderUtility::DumpPreprocessedCode(
ShaderAssetBuilderName, output.code, request.m_tempDirPath, superVariantAzslinStemName,
apiName);
if (azslinFullPath.empty())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AZ_TracePrintf(ShaderAssetBuilderName, "Preprocessed AZSL File: %s \n", prependedAzslFilePath.c_str());
// Before transpiling the flat-AZSL(.azslin) file into HLSL it is necessary
// to setup the AZSLc arguments as required by the current supervariant.
AZStd::string azslcCompilerParameters = supervariantInfo.GetCustomizedArgumentsForAzslc(commonAzslcCompilerParameters);
// Ready to transpile the azslin file into HLSL.
ShaderBuilder::AzslCompiler azslc(azslinFullPath);
AZStd::string hlslFullPath = AZStd::string::format("%s_%s.hlsl", superVariantAzslinStemName.c_str(), apiName.c_str());
AzFramework::StringFunc::Path::Join(request.m_tempDirPath.c_str(), hlslFullPath.c_str(), hlslFullPath, true);
auto emitFullOutcome = azslc.EmitFullData(azslcCompilerParameters, hlslFullPath);
if (!emitFullOutcome.IsSuccess())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
ShaderBuilderUtility::AzslSubProducts::Paths subProductsPaths = emitFullOutcome.TakeValue();
// In addition to the hlsl file, there are other json files that were generated.
// Each output file will become a product.
for (int i = 0; i < subProductsPaths.size(); ++i)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = subProductsPaths[i];
static const AZ::Uuid AzslOutcomeType = "{6977AEB1-17AD-4992-957B-23BB2E85B18B}";
jobProduct.m_productAssetType = AzslOutcomeType;
// uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType
jobProduct.m_productSubID = RPI::ShaderAsset::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
aznumeric_cast<uint32_t>(ShaderBuilderUtility::AzslSubProducts::SubList[i]));
jobProduct.m_dependenciesHandled = true;
// Note that the output products are not traditional product assets that will be used by the game project.
// They are artifacts that are produced once, cached, and used later by other AssetBuilders as a way to centralize
// build organization.
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
AZStd::shared_ptr<ShaderFiles> files(new ShaderFiles);
AzslData azslData(files);
azslData.m_preprocessedFullPath = azslinFullPath;
RPI::ShaderResourceGroupLayoutList srgLayoutList;
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create();
BindingDependencies bindingDependencies;
RootConstantData rootConstantData;
AssetBuilderSDK::ProcessJobResultCode azslJsonReadResult = ShaderBuilderUtility::PopulateAzslDataFromJsonFiles(
ShaderAssetBuilderName, subProductsPaths, platformUsesRegisterSpaces, azslData, srgLayoutList, shaderOptionGroupLayout,
bindingDependencies, rootConstantData);
if (azslJsonReadResult != AssetBuilderSDK::ProcessJobResult_Success)
{
response.m_resultCode = azslJsonReadResult;
return;
}
shaderAssetCreator.SetSrgLayoutList(srgLayoutList);
if (!finalShaderOptionGroupLayout)
{
finalShaderOptionGroupLayout = shaderOptionGroupLayout;
shaderAssetCreator.SetShaderOptionGroupLayout(finalShaderOptionGroupLayout);
const uint32_t usedShaderOptionBits = shaderOptionGroupLayout->GetBitSize();
AZ_TracePrintf(
ShaderAssetBuilderName, "Note: This shader uses %u of %u available shader variant key bits. \n",
usedShaderOptionBits, RPI::ShaderVariantKeyBitCount);
}
else
{
if (finalShaderOptionGroupLayout->GetHash() != shaderOptionGroupLayout->GetHash())
{
AZ_Error(
ShaderAssetBuilderName, false, "Supervariant %s has a different ShaderOptionGroupLayout",
supervariantInfo.m_name.GetCStr())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
}
// Discover entry points & type of programs.
MapOfStringToStageType shaderEntryPoints;
if (shaderSourceData.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(
ShaderAssetBuilderName,
"ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
}
else
{
for (const auto& entryPoint : shaderSourceData.m_programSettings.m_entryPoints)
{
shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type;
}
}
bool hasRasterProgram = false;
auto attributeMapsOutcome = BuildAttributesMap(shaderPlatformInterface, azslData, shaderEntryPoints, hasRasterProgram);
if (!attributeMapsOutcome.IsSuccess())
{
AZ_Error(ShaderAssetBuilderName, false, "%s\n", attributeMapsOutcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
shaderAssetCreator.SetShaderStageAttributeMapList(attributeMapsOutcome.TakeValue());
// Check if we were canceled before we do any heavy processing of
// the shader data (compiling the shader kernels, processing SRG
// and pipeline layout data, etc.).
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor =
ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi(
ShaderAssetBuilderName, srgLayoutList, shaderEntryPoints, buildOptions.m_compilerArguments, rootConstantData,
shaderPlatformInterface, bindingDependencies);
if (!pipelineLayoutDescriptor)
{
AZ_Error(
ShaderAssetBuilderName, false, "Failed to build pipeline layout descriptor for api=[%s]",
shaderPlatformInterface->GetAPIName().GetCStr());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
shaderAssetCreator.SetPipelineLayout(pipelineLayoutDescriptor);
RPI::ShaderInputContract shaderInputContract;
RPI::ShaderOutputContract shaderOutputContract;
size_t colorAttachmentCount = 0;
ShaderBuilderUtility::CreateShaderInputAndOutputContracts(
azslData, shaderEntryPoints, *shaderOptionGroupLayout.get(),
subProductsPaths[ShaderBuilderUtility::AzslSubProducts::om],
subProductsPaths[ShaderBuilderUtility::AzslSubProducts::ia],
shaderInputContract, shaderOutputContract, colorAttachmentCount);
shaderAssetCreator.SetInputContract(shaderInputContract);
shaderAssetCreator.SetOutputContract(shaderOutputContract);
if (hasRasterProgram)
{
// Set the various states to what is in the descriptor.
const RHI::TargetBlendState& targetBlendState = shaderSourceData.m_blendState;
RHI::RenderStates renderStates;
renderStates.m_rasterState = shaderSourceData.m_rasterState;
renderStates.m_depthStencilState = shaderSourceData.m_depthStencilState;
// [GFX TODO][ATOM-930] We should support unique blend states per RT
for (size_t i = 0; i < colorAttachmentCount; ++i)
{
renderStates.m_blendState.m_targets[i] = targetBlendState;
}
shaderAssetCreator.SetRenderStates(renderStates);
}
Outcome<AZStd::string, AZStd::string> hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath);
if (!hlslSourceCodeOutcome.IsSuccess())
{
AZ_Error(
ShaderAssetBuilderName, false, "Failed to obtain shader source from %s. [%s]", hlslFullPath.c_str(),
hlslSourceCodeOutcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AZStd::string hlslSourceCode = hlslSourceCodeOutcome.TakeValue();
// The root ShaderVariantAsset needs to be created with the known uuid of the source .shader asset because
// the ShaderAsset owns a Data::Asset<> reference that gets serialized. It must have the correct uuid
// so the root ShaderVariantAsset is found when the ShaderAsset is deserialized.
uint32_t rootVariantProductSubId = RPI::ShaderAsset::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::RootShaderVariantAsset));
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderFullPath, rootVariantProductSubId);
AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", shaderFullPath.c_str());
const Data::AssetId variantAssetId = assetIdOutcome.TakeValue();
RPI::ShaderVariantListSourceData::VariantInfo rootVariantInfo;
ShaderVariantCreationContext shaderVariantCreationContext = {
*shaderPlatformInterface,
request.m_platformInfo,
buildOptions.m_compilerArguments,
request.m_tempDirPath,
startTime,
shaderSourceData,
*shaderOptionGroupLayout.get(),
shaderEntryPoints,
variantAssetId,
superVariantAzslinStemName,
hlslFullPath,
hlslSourceCode};
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts> outputByproducts;
auto rootShaderVariantAssetOutcome = ShaderVariantAssetBuilder::CreateShaderVariantAsset(rootVariantInfo, shaderVariantCreationContext, outputByproducts);
if (!rootShaderVariantAssetOutcome.IsSuccess())
{
AZ_Error(ShaderAssetBuilderName, false, "%s\n", rootShaderVariantAssetOutcome.GetError().c_str())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
Data::Asset<RPI::ShaderVariantAsset> rootShaderVariantAsset = rootShaderVariantAssetOutcome.TakeValue();
shaderAssetCreator.SetRootShaderVariantAsset(rootShaderVariantAsset);
if (!shaderAssetCreator.EndSupervariant())
{
AZ_Error(
ShaderAssetBuilderName, false, "Failed to create shader asset for supervariant [%s]", supervariantInfo.m_name.GetCStr())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Time to save the root variant related assets in the cache.
AssetBuilderSDK::JobProduct assetProduct;
if (!ShaderVariantAssetBuilder::SerializeOutShaderVariantAsset(
rootShaderVariantAsset, superVariantAzslinStemName, request.m_tempDirPath, *shaderPlatformInterface,
rootVariantProductSubId,
assetProduct))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_outputProducts.push_back(assetProduct);
if (outputByproducts)
{
// add byproducts as job output products:
uint32_t subProductType = aznumeric_cast<uint32_t>(RPI::ShaderAssetSubId::FirstByProduct);
for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
jobProduct.m_productSubID = RPI::ShaderAsset::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
supervariantIndex++;
} // end for the supervariant
shaderAssetCreator.EndAPI();
} // end for all ShaderPlatformInterfaces
Data::Asset<RPI::ShaderAsset> shaderAsset;
if (!shaderAssetCreator.End(shaderAsset))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (!SerializeOutShaderAsset(shaderAsset, request.m_tempDirPath, response))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
const AZStd::sys_time_t endTime = AZStd::GetTimeNowTicks();
const AZStd::sys_time_t deltaTime = endTime - startTime;
const float elapsedTimeSeconds = (float)(deltaTime) / (float)AZStd::GetTimeTicksPerSecond();
AZ_TracePrintf(ShaderAssetBuilderName, "Finished processing %s in %.2f seconds\n", request.m_sourceFile.c_str(), elapsedTimeSeconds);
ShaderBuilderUtility::LogProfilingData(ShaderAssetBuilderName, inputFiles->m_azslFileName);
ShaderBuilderUtility::LogProfilingData(ShaderAssetBuilderName, shaderFileName);
}
} // ShaderBuilder
@@ -38,7 +38,7 @@ namespace AZ
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler
{
public:
AZ_TYPE_INFO(ShaderAssetBuilder, "{AEC304A9-940C-4851-AEF0-7D00482598F9}");
AZ_TYPE_INFO(ShaderAssetBuilder, "{C94DA151-82BC-4475-86FA-E6C92A0BD6F8}");
static constexpr const char* ShaderAssetBuilderJobKey = "Shader Asset";
@@ -1,683 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
#include "ShaderAssetBuilder2.h"
#include <CommonFiles/Preprocessor.h>
#include <CommonFiles/GlobalBuildOptions.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderAssetCreator2.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
#include <Atom/RHI.Edit/Utils.h>
#include <Atom/RHI.Edit/ShaderPlatformInterface.h>
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RHI.Reflect/ConstantsLayout.h>
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
#include <AtomCore/Serialization/Json/JsonUtils.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/JSON/document.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/sort.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include "AzslBuilder.h"
#include "ShaderVariantAssetBuilder2.h"
#include "ShaderBuilderUtility.h"
#include "ShaderPlatformInterfaceRequest.h"
#include "AtomShaderConfig.h"
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AssetBuilderSDK/SerializationDependencies.h>
namespace AZ
{
namespace ShaderBuilder
{
static constexpr char ShaderAssetBuilder2Name[] = "ShaderAssetBuilder2";
static constexpr uint32_t ShaderAssetBuildTimestampParam = 0;
void ShaderAssetBuilder2::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{
AZStd::string fullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true);
AZ_TracePrintf(ShaderAssetBuilder2Name, "CreateJobs for Shader \"%s\"\n", fullPath.data());
// Used to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload.
// Note it's probably important for this to be set once outside the platform loop so every platform's ShaderAsset
// has the same value, because later the ShaderVariantTreeAsset job will fetch this value from the local ShaderAsset
// which could cross platforms (i.e. building an android ShaderVariantTreeAsset on PC would fetch the tiemstamp from
// the PC's ShaderAsset).
AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond();
// Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job.
// and the macro options to preprocess.
auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(fullPath);
if (!descriptorParseOutcome.IsSuccess())
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Failed to parse Shader Descriptor JSON: %s",
descriptorParseOutcome.GetError().c_str());
return;
}
RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
AZStd::string azslFullPath;
ShaderBuilderUtility::GetAbsolutePathToAzslFile(fullPath, shaderSourceData.m_source, azslFullPath);
if (!IO::FileIOBase::GetInstance()->Exists(azslFullPath.c_str()))
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Shader program listed as the source entry does not exist: %s.", azslFullPath.c_str());
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
return;
}
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilder2Name);
// [GFX TODO] [ATOM-14966] In principle, based on macro definitions, included files can change per supervariant.
// So, the list of source asset dependencies must be collected by running MCPP on each supervariant.
// For now, we will run MCPP only once because CreateJobs() should be as light as possible.
//
// Regardless of the PlatformInfo and enabled ShaderPlatformInterfaces, the azsl file will be preprocessed
// with the sole purpose of extracting all included files. For each included file a SourceDependency will be declared.
PreprocessorData output;
buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler);
PreprocessFile(azslFullPath, output, buildOptions.m_preprocessorSettings, true, true);
for (auto includePath : output.includedPaths)
{
// m_sourceFileDependencyList does not support paths with "." or ".." for relative lookup, but the preprocessor
// may produce path strings like "C:/a/b/c/../../d/file.azsli" so we have to normalize
AzFramework::StringFunc::Path::Normalize(includePath);
AssetBuilderSDK::SourceFileDependency includeFileDependency;
includeFileDependency.m_sourceFileDependencyPath = includePath;
response.m_sourceFileDependencyList.emplace_back(includeFileDependency);
}
{
// Add the AZSL as source dependency
AssetBuilderSDK::SourceFileDependency azslFileDependency;
azslFileDependency.m_sourceFileDependencyPath = azslFullPath;
response.m_sourceFileDependencyList.emplace_back(azslFileDependency);
}
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
AZ_TraceContext("For platform", platformInfo.m_identifier.data());
// Get the platform interfaces to be able to access the prepend file
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(platformInfo);
if (platformInterfaces.empty())
{
continue;
}
AssetBuilderSDK::JobDescriptor jobDescriptor;
jobDescriptor.m_priority = 2;
// [GFX TODO][ATOM-2830] Set 'm_critical' back to 'false' once proper fix for Atom startup issues are in
jobDescriptor.m_critical = true;
jobDescriptor.m_jobKey = ShaderAssetBuilder2JobKey;
jobDescriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
jobDescriptor.m_jobParameters.emplace(ShaderAssetBuildTimestampParam, AZStd::to_string(shaderAssetBuildTimestamp));
response.m_createJobOutputs.push_back(jobDescriptor);
} // for all request.m_enabledPlatforms
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
static bool SerializeOutShaderAsset(Data::Asset<RPI::ShaderAsset2> shaderAsset,
const AZStd::string& tempDirPath,
AssetBuilderSDK::ProcessJobResponse& response)
{
AZStd::string shaderAssetFileName = AZStd::string::format("%s.%s", shaderAsset->GetName().GetCStr(), RPI::ShaderAsset2::Extension);
AZStd::string shaderAssetOutputPath;
AzFramework::StringFunc::Path::ConstructFull(tempDirPath.data(), shaderAssetFileName.data(), shaderAssetOutputPath, true);
if (!Utils::SaveObjectToFile(shaderAssetOutputPath, DataStream::ST_BINARY, shaderAsset.Get()))
{
AZ_Error(ShaderAssetBuilder2Name, false, "Failed to output Shader Descriptor");
return false;
}
AssetBuilderSDK::JobProduct shaderJobProduct;
if (!AssetBuilderSDK::OutputObject(shaderAsset.Get(), shaderAssetOutputPath, azrtti_typeid<RPI::ShaderAsset2>(),
aznumeric_cast<uint32_t>(RPI::ShaderAsset2ProductSubId::ShaderAsset2), shaderJobProduct))
{
AZ_Error(ShaderAssetBuilder2Name, false, "Failed to output product dependencies.");
return false;
}
response.m_outputProducts.push_back(AZStd::move(shaderJobProduct));
return true;
}
static AZ::Outcome<RHI::ShaderStageAttributeMapList, AZStd::string> BuildAttributesMap(
const RHI::ShaderPlatformInterface* shaderPlatformInterface,
const AzslData& azslData,
const MapOfStringToStageType& shaderEntryPoints,
bool& hasRasterProgram)
{
hasRasterProgram = false;
bool hasComputeProgram = false;
bool hasRayTracingProgram = false;
RHI::ShaderStageAttributeMapList attributeMaps;
attributeMaps.resize(RHI::ShaderStageCount);
for (const auto& shaderEntryPoint : shaderEntryPoints)
{
auto shaderEntryName = shaderEntryPoint.first;
auto shaderStageType = shaderEntryPoint.second;
auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType);
hasRasterProgram |= shaderPlatformInterface->IsShaderStageForRaster(assetBuilderShaderType);
hasComputeProgram |= shaderPlatformInterface->IsShaderStageForCompute(assetBuilderShaderType);
hasRayTracingProgram |= shaderPlatformInterface->IsShaderStageForRayTracing(assetBuilderShaderType);
auto findId = AZStd::find_if(AZ_BEGIN_END(azslData.m_functions), [&shaderEntryPoint](const auto& func) {
return func.m_name == shaderEntryPoint.first;
});
if (findId == azslData.m_functions.end())
{
// shaderData.m_functions only contains Vertex, Fragment and Compute entries for now
// Tessellation shaders will need to be handled too
continue;
}
const auto shaderStage = ToRHIShaderStage(assetBuilderShaderType);
for (const auto& attr : findId->attributesList)
{
// Some stages like RHI::ShaderStage::Tessellation are compound and consist of two or more shader entries
const Name& attributeName = attr.first;
const RHI::ShaderStageAttributeArguments& args = attr.second;
const auto stageIndex = static_cast<uint32_t>(shaderStage);
AZ_Assert(stageIndex < RHI::ShaderStageCount, "Invalid shader stage specified!");
attributeMaps[stageIndex][attributeName] = args;
}
}
if (hasRasterProgram && hasComputeProgram)
{
return AZ::Failure(AZStd::string(" Shader asset descriptor defines both a raster entry point and a compute entry point."));
}
if (!hasRasterProgram && !hasComputeProgram && !hasRayTracingProgram)
{
AZStd::string entryPointNames = ShaderBuilderUtility::GetAcceptableDefaultEntryPointNames(azslData);
return AZ::Failure(
AZStd::string::format( "Shader asset descriptor has a program variant that does not define any entry points. Either declare entry "
"points in the .shader file, or use one of the available default names (not case-sensitive): [%s]",
entryPointNames.c_str()));
}
return AZ::Success(attributeMaps);
}
void ShaderAssetBuilder2::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks();
AZStd::string shaderFullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), shaderFullPath, true);
// Save .shader file name (no extension and no parent directory path)
AZStd::string shaderFileName;
AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), shaderFileName);
// No error checking because the same calls were already executed during CreateJobs()
auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderFullPath);
RPI::ShaderSourceData shaderSourceData = descriptorParseOutcome.TakeValue();
AZStd::string azslFullPath;
ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderFullPath, shaderSourceData.m_source, azslFullPath);
AZ_TracePrintf(ShaderAssetBuilder2Name, "Original AZSL File: %s \n", azslFullPath.c_str());
// The directory where the Azsl file was found must be added to the list of include paths
AZStd::string azslFolderPath;
AzFramework::StringFunc::Path::GetFolderPath(azslFullPath.c_str(), azslFolderPath);
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderAssetBuilder2Name, azslFolderPath.c_str());
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(
request.m_platformInfo, shaderSourceData);
if (platformInterfaces.empty())
{
//No work to do. Exit gracefully.
AZ_TracePrintf(ShaderAssetBuilder2Name,
"No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
shaderFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
// Get the time stamp string as sys_time_t, and also convert back to string to make sure it was converted correctly.
AZStd::sys_time_t shaderAssetBuildTimestamp = 0;
auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam);
if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end())
{
shaderAssetBuildTimestamp = AZStd::stoull(shaderAssetBuildTimestampIterator->second);
if (AZStd::to_string(shaderAssetBuildTimestamp) != shaderAssetBuildTimestampIterator->second)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
AZ_Assert(false, "Incorrect conversion of ShaderAssetBuildTimestampParam");
return;
}
}
auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceData);
RPI::ShaderAssetCreator2 shaderAssetCreator;
shaderAssetCreator.Begin(Uuid::CreateRandom());
shaderAssetCreator.SetName(AZ::Name{shaderFileName.c_str()});
shaderAssetCreator.SetDrawListName(Name(shaderSourceData.m_drawListName));
shaderAssetCreator.SetShaderAssetBuildTimestamp(shaderAssetBuildTimestamp);
// The ShaderOptionGroupLayout must be the same across all supervariants because
// there can be only a single ShaderVariantTreeAsset per ShaderAsset.
// We will store here the one that results when the *.azslin file is
// compiled for the default, nameless, supervariant.
// For all other supervariants we just make sure the hashes are the same
// as this one.
RPI::Ptr<RPI::ShaderOptionGroupLayout> finalShaderOptionGroupLayout = nullptr;
// Time to describe the big picture.
// 1- Preprocess an AZSL file with MCPP (a C-Preprocessor), and generate a flat AZSL file without #include lines and any macros in it.
// Let's call it the Flat-AZSL file. There are two levels of macro definition that need to be merged before we can invoke MCPP:
// 1.1- From <GameProject>/Config/shader_global_build_options.json, which we have stored in the local variable @buildOptions.
// 1.2- From the "Supervariant" definition key, which can be different for each supervariant.
// 2- There will be one Flat-AZSL per supervariant. Each Flat-AZSL will be transpiled to HLSL with AZSLc. This means there will be one HLSL file
// per supervariant.
// 3- The generated HLSL (one HLSL per supervariant) file may contain C-Preprocessor Macros inserted by AZSLc. And that file will be given to DXC.
// DXC has a preprocessor embedded in it. DXC will be executed once for each entry function listed in the .shader file.
// There will be one DXIL compiled binary for each entry function. All the DXIL compiled binaries for each supervariant will be combined
// in the ROOT ShaderVariantAsset.
// Remark: In general, the work done by the ShaderVariantAssetBuilder is similar, but it will start from the HLSL file created; in step 2, mentioned above; by this builder,
// for each supervariant.
// At this moment We have global build options that should be merged with the build options that are common
// to all the supervariants of this shader.
buildOptions.m_compilerArguments.Merge(shaderSourceData.m_compiler);
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
AZStd::string apiName(shaderPlatformInterface->GetAPIName().GetCStr());
AZ_TraceContext("Platform API", apiName);
// Signal the begin of shader data for an RHI API.
shaderAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
// Each shaderPlatformInterface has its own azsli header that needs to be prepended to the AZSL file before
// preprocessing. We will create a new temporary file that contains the combined data.
RHI::PrependArguments args;
args.m_sourceFile = azslFullPath.c_str();
args.m_prependFile = shaderPlatformInterface->GetAzslHeader(request.m_platformInfo);
args.m_addSuffixToFileName = apiName.c_str();
args.m_destinationFolder = request.m_tempDirPath.c_str();
AZStd::string prependedAzslFilePath = RHI::PrependFile(args);
if (prependedAzslFilePath == azslFullPath)
{
// The specific error is already reported by RHI::PrependFile().
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Cache common AZSLC invokation arguments related with the current RHI Backend.
// Each supervariant can, optionally, remove or add more arguments for AZSLc.
AZStd::string commonAzslcCompilerParameters =
shaderPlatformInterface->GetAzslCompilerParameters(buildOptions.m_compilerArguments);
commonAzslcCompilerParameters += " ";
commonAzslcCompilerParameters +=
shaderPlatformInterface->GetAzslCompilerWarningParameters(buildOptions.m_compilerArguments);
AtomShaderConfig::AddParametersFromConfigFile(commonAzslcCompilerParameters, request.m_platformInfo);
// The register number only makes sense if the platform uses "spaces",
// since the register Id of the resource will not change even if the pipeline layout changes.
// We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface
// appends the "--use-spaces" flag.
const bool platformUsesRegisterSpaces =
(AzFramework::StringFunc::Find(commonAzslcCompilerParameters, "--use-spaces") != AZStd::string::npos);
uint32_t supervariantIndex = 0;
for (const auto& supervariantInfo : supervariantList)
{
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
shaderAssetCreator.BeginSupervariant(supervariantInfo.m_name);
// Let's combine the global macro definitions, with the macro definitions particular to this
// supervariant. Two steps:
// 1- Supervariants can specify which macros to remove from the global definitions.
AZStd::vector<AZStd::string> macroDefinitionNamesToRemove = supervariantInfo.GetCombinedListOfMacroDefinitionNamesToRemove();
PreprocessorOptions preprocessorOptions = buildOptions.m_preprocessorSettings;
preprocessorOptions.RemovePredefinedMacros(macroDefinitionNamesToRemove);
// 2- Supervariants can specify which macros to add.
AZStd::vector<AZStd::string> macroDefinitionsToAdd = supervariantInfo.GetMacroDefinitionsToAdd();
preprocessorOptions.m_predefinedMacros.insert(
preprocessorOptions.m_predefinedMacros.end(), macroDefinitionsToAdd.begin(), macroDefinitionsToAdd.end());
// Run the preprocessor.
PreprocessorData output;
PreprocessFile(prependedAzslFilePath, output, preprocessorOptions, true, true);
RHI::ReportErrorMessages(ShaderAssetBuilder2Name, output.diagnostics);
// Dump the preprocessed string as a flat AZSL file with extension .azslin, which will be given to AZSLc to generate the HLSL file.
AZStd::string superVariantAzslinStemName = shaderFileName;
if (!supervariantInfo.m_name.IsEmpty())
{
superVariantAzslinStemName += AZStd::string::format("-%s", supervariantInfo.m_name.GetCStr());
}
AZStd::string azslinFullPath = ShaderBuilderUtility::DumpPreprocessedCode(
ShaderAssetBuilder2Name, output.code, request.m_tempDirPath, superVariantAzslinStemName,
apiName, true /*add2*/);
if (azslinFullPath.empty())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AZ_TracePrintf(ShaderAssetBuilder2Name, "Preprocessed AZSL File: %s \n", prependedAzslFilePath.c_str());
// Before transpiling the flat-AZSL(.azslin) file into HLSL it is necessary
// to setup the AZSLc arguments as required by the current supervariant.
AZStd::string azslcCompilerParameters = supervariantInfo.GetCustomizedArgumentsForAzslc(commonAzslcCompilerParameters);
// Ready to transpile the azslin file into HLSL.
ShaderBuilder::AzslCompiler azslc(azslinFullPath);
AZStd::string hlslFullPath = AZStd::string::format("%s_%s.hlsl2", superVariantAzslinStemName.c_str(), apiName.c_str());
AzFramework::StringFunc::Path::Join(request.m_tempDirPath.c_str(), hlslFullPath.c_str(), hlslFullPath, true);
auto emitFullOutcome = azslc.EmitFullData(azslcCompilerParameters, hlslFullPath, "2");
if (!emitFullOutcome.IsSuccess())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
ShaderBuilderUtility::AzslSubProducts::Paths subProductsPaths = emitFullOutcome.TakeValue();
// In addition to the hlsl file, there are other json files that were generated.
// Each output file will become a product.
for (int i = 0; i < subProductsPaths.size(); ++i)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = subProductsPaths[i];
static const AZ::Uuid AzslOutcomeType = "{6977AEB1-17AD-4992-957B-23BB2E85B18B}";
jobProduct.m_productAssetType = AzslOutcomeType;
// uint32_t rhiApiUniqueIndex, uint32_t supervariantIndex, uint32_t subProductType
jobProduct.m_productSubID = RPI::ShaderAsset2::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
aznumeric_cast<uint32_t>(ShaderBuilderUtility::AzslSubProducts::SubList[i]));
jobProduct.m_dependenciesHandled = true;
// Note that the output products are not traditional product assets that will be used by the game project.
// They are artifacts that are produced once, cached, and used later by other AssetBuilders as a way to centralize
// build organization.
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
AZStd::shared_ptr<ShaderFiles> files(new ShaderFiles);
AzslData azslData(files);
azslData.m_preprocessedFullPath = azslinFullPath;
RPI::ShaderResourceGroupLayoutList srgLayoutList;
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create();
BindingDependencies bindingDependencies;
RootConstantData rootConstantData;
AssetBuilderSDK::ProcessJobResultCode azslJsonReadResult = ShaderBuilderUtility::PopulateAzslDataFromJsonFiles(
ShaderAssetBuilder2Name, subProductsPaths, platformUsesRegisterSpaces, azslData, srgLayoutList, shaderOptionGroupLayout,
bindingDependencies, rootConstantData);
if (azslJsonReadResult != AssetBuilderSDK::ProcessJobResult_Success)
{
response.m_resultCode = azslJsonReadResult;
return;
}
shaderAssetCreator.SetSrgLayoutList(srgLayoutList);
if (!finalShaderOptionGroupLayout)
{
finalShaderOptionGroupLayout = shaderOptionGroupLayout;
shaderAssetCreator.SetShaderOptionGroupLayout(finalShaderOptionGroupLayout);
const uint32_t usedShaderOptionBits = shaderOptionGroupLayout->GetBitSize();
AZ_TracePrintf(
ShaderAssetBuilder2Name, "Note: This shader uses %u of %u available shader variant key bits. \n",
usedShaderOptionBits, RPI::ShaderVariantKeyBitCount);
}
else
{
if (finalShaderOptionGroupLayout->GetHash() != shaderOptionGroupLayout->GetHash())
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Supervariant %s has a different ShaderOptionGroupLayout",
supervariantInfo.m_name.GetCStr())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
}
// Discover entry points & type of programs.
MapOfStringToStageType shaderEntryPoints;
if (shaderSourceData.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(
ShaderAssetBuilder2Name,
"ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslData.m_functions, shaderEntryPoints);
}
else
{
for (const auto& entryPoint : shaderSourceData.m_programSettings.m_entryPoints)
{
shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type;
}
}
bool hasRasterProgram = false;
auto attributeMapsOutcome = BuildAttributesMap(shaderPlatformInterface, azslData, shaderEntryPoints, hasRasterProgram);
if (!attributeMapsOutcome.IsSuccess())
{
AZ_Error(ShaderAssetBuilder2Name, false, "%s\n", attributeMapsOutcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
shaderAssetCreator.SetShaderStageAttributeMapList(attributeMapsOutcome.TakeValue());
// Check if we were canceled before we do any heavy processing of
// the shader data (compiling the shader kernels, processing SRG
// and pipeline layout data, etc.).
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor =
ShaderBuilderUtility::BuildPipelineLayoutDescriptorForApi(
ShaderAssetBuilder2Name, srgLayoutList, shaderEntryPoints, buildOptions.m_compilerArguments, rootConstantData,
shaderPlatformInterface, bindingDependencies);
if (!pipelineLayoutDescriptor)
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Failed to build pipeline layout descriptor for api=[%s]",
shaderPlatformInterface->GetAPIName().GetCStr());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
shaderAssetCreator.SetPipelineLayout(pipelineLayoutDescriptor);
RPI::ShaderInputContract shaderInputContract;
RPI::ShaderOutputContract shaderOutputContract;
size_t colorAttachmentCount = 0;
ShaderBuilderUtility::CreateShaderInputAndOutputContracts(
azslData, shaderEntryPoints, *shaderOptionGroupLayout.get(),
subProductsPaths[ShaderBuilderUtility::AzslSubProducts::om],
subProductsPaths[ShaderBuilderUtility::AzslSubProducts::ia],
shaderInputContract, shaderOutputContract, colorAttachmentCount);
shaderAssetCreator.SetInputContract(shaderInputContract);
shaderAssetCreator.SetOutputContract(shaderOutputContract);
if (hasRasterProgram)
{
// Set the various states to what is in the descriptor.
const RHI::TargetBlendState& targetBlendState = shaderSourceData.m_blendState;
RHI::RenderStates renderStates;
renderStates.m_rasterState = shaderSourceData.m_rasterState;
renderStates.m_depthStencilState = shaderSourceData.m_depthStencilState;
// [GFX TODO][ATOM-930] We should support unique blend states per RT
for (size_t i = 0; i < colorAttachmentCount; ++i)
{
renderStates.m_blendState.m_targets[i] = targetBlendState;
}
shaderAssetCreator.SetRenderStates(renderStates);
}
Outcome<AZStd::string, AZStd::string> hlslSourceCodeOutcome = Utils::ReadFile(hlslFullPath);
if (!hlslSourceCodeOutcome.IsSuccess())
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Failed to obtain shader source from %s. [%s]", hlslFullPath.c_str(),
hlslSourceCodeOutcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AZStd::string hlslSourceCode = hlslSourceCodeOutcome.TakeValue();
// The root ShaderVariantAsset needs to be created with the known uuid of the source .shader asset because
// the ShaderAsset owns a Data::Asset<> reference that gets serialized. It must have the correct uuid
// so the root ShaderVariantAsset is found when the ShaderAsset is deserialized.
uint32_t rootVariantProductSubId = RPI::ShaderAsset2::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
aznumeric_cast<uint32_t>(RPI::ShaderAsset2ProductSubId::RootShaderVariantAsset));
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderFullPath, rootVariantProductSubId);
AZ_Assert(assetIdOutcome.IsSuccess(), "Failed to get AssetId from shader %s", shaderFullPath.c_str());
const Data::AssetId variantAssetId = assetIdOutcome.TakeValue();
RPI::ShaderVariantListSourceData::VariantInfo rootVariantInfo;
ShaderVariantCreationContext2 shaderVariantCreationContext = {
*shaderPlatformInterface,
request.m_platformInfo,
buildOptions.m_compilerArguments,
request.m_tempDirPath,
startTime,
shaderSourceData,
*shaderOptionGroupLayout.get(),
shaderEntryPoints,
variantAssetId,
superVariantAzslinStemName,
hlslFullPath,
hlslSourceCode};
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts> outputByproducts;
auto rootShaderVariantAssetOutcome = ShaderVariantAssetBuilder2::CreateShaderVariantAsset(rootVariantInfo, shaderVariantCreationContext, outputByproducts);
if (!rootShaderVariantAssetOutcome.IsSuccess())
{
AZ_Error(ShaderAssetBuilder2Name, false, "%s\n", rootShaderVariantAssetOutcome.GetError().c_str())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
Data::Asset<RPI::ShaderVariantAsset2> rootShaderVariantAsset = rootShaderVariantAssetOutcome.TakeValue();
shaderAssetCreator.SetRootShaderVariantAsset(rootShaderVariantAsset);
if (!shaderAssetCreator.EndSupervariant())
{
AZ_Error(
ShaderAssetBuilder2Name, false, "Failed to create shader asset for supervariant [%s]", supervariantInfo.m_name.GetCStr())
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Time to save the root variant related assets in the cache.
AssetBuilderSDK::JobProduct assetProduct;
if (!ShaderVariantAssetBuilder2::SerializeOutShaderVariantAsset(
rootShaderVariantAsset, superVariantAzslinStemName, request.m_tempDirPath, *shaderPlatformInterface,
rootVariantProductSubId,
assetProduct))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_outputProducts.push_back(assetProduct);
if (outputByproducts)
{
// add byproducts as job output products:
uint32_t subProductType = aznumeric_cast<uint32_t>(RPI::ShaderAsset2ProductSubId::FirstByProduct);
for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
jobProduct.m_productSubID = RPI::ShaderAsset2::MakeProductAssetSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex,
subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
supervariantIndex++;
} // end for the supervariant
shaderAssetCreator.EndAPI();
} // end for all ShaderPlatformInterfaces
Data::Asset<RPI::ShaderAsset2> shaderAsset;
if (!shaderAssetCreator.End(shaderAsset))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (!SerializeOutShaderAsset(shaderAsset, request.m_tempDirPath, response))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
const AZStd::sys_time_t endTime = AZStd::GetTimeNowTicks();
const AZStd::sys_time_t deltaTime = endTime - startTime;
const float elapsedTimeSeconds = (float)(deltaTime) / (float)AZStd::GetTimeTicksPerSecond();
AZ_TracePrintf(ShaderAssetBuilder2Name, "Finished processing %s in %.2f seconds\n", request.m_sourceFile.c_str(), elapsedTimeSeconds);
ShaderBuilderUtility::LogProfilingData(ShaderAssetBuilder2Name, shaderFileName);
}
} // ShaderBuilder
} // AZ
@@ -1,60 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <Atom/RHI.Reflect/Base.h>
namespace AZ
{
namespace Data
{
class AssetHandler;
}
namespace RHI
{
class ShaderPlatformInterface;
}
namespace ShaderBuilder
{
struct AzslData;
class ShaderAssetBuilder2
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler
{
public:
AZ_TYPE_INFO(ShaderAssetBuilder2, "{C94DA151-82BC-4475-86FA-E6C92A0BD6F8}");
static constexpr const char* ShaderAssetBuilder2JobKey = "Shader Asset 2";
ShaderAssetBuilder2() = default;
~ShaderAssetBuilder2() = default;
// Asset Builder Callback Functions ...
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const;
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
// AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
void ShutDown() override { };
private:
AZ_DISABLE_COPY_MOVE(ShaderAssetBuilder2);
};
} // ShaderBuilder
} // AZ
@@ -29,8 +29,7 @@
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h> // DEPRECATED - [ATOM-15472]
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RHI.Edit/Utils.h>
@@ -87,68 +86,6 @@ namespace AZ
AzFramework::StringFunc::Path::ReplaceExtension(absoluteAzslPath, "azsl");
}
static bool LoadShaderResourceGroupAssets(
[[maybe_unused]] const char* builderName,
const SrgDataContainer& resourceGroups,
ShaderResourceGroupAssets& srgAssets)
{
bool readSRGsSuccessfuly = true;
// Load all SRGs included in source file
for (const SrgData& srgData : resourceGroups)
{
Data::AssetId assetId = {};
AZStd::string srgFilePath = "";
srgFilePath = srgData.m_containingFileName;
AzFramework::StringFunc::Path::Normalize(srgFilePath);
bool assetFound = false;
Data::AssetInfo sourceInfo;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(assetFound, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, srgFilePath.c_str(), sourceInfo, watchFolder);
if (!assetFound)
{
AZ_Error(builderName, false, "Could not find asset identified by path '%s'", srgFilePath.c_str());
readSRGsSuccessfuly = false;
continue;
}
assetId.m_guid = sourceInfo.m_assetId.m_guid;
assetId.m_subId = static_cast<uint32_t>(AZStd::hash<AZStd::string>()(srgData.m_name) & 0xFFFFFFFF);
Data::Asset<RPI::ShaderResourceGroupAsset> asset = Data::AssetManager::Instance().GetAsset<RPI::ShaderResourceGroupAsset>(
assetId, AZ::Data::AssetLoadBehavior::PreLoad);
asset.BlockUntilLoadComplete();
if (!asset.IsReady())
{
using Status = Data::AssetData::AssetStatus;
AZStd::string statusString = asset.GetStatus() == Status::Loading ? "loading"
: asset.GetStatus() == Status::ReadyPreNotify ? "ready-pre-notify"
: asset.GetStatus() == Status::Error ? "error" : "not-loaded/ready/unknown";
AZ_Error(builderName, false, "Searching SRG [%s]: Could not load SRG asset. (asset status [%s]) AssetId='%s' Path='%s'",
srgData.m_name.c_str(),
statusString.c_str(),
assetId.ToString<AZStd::string>().c_str(), srgFilePath.c_str());
readSRGsSuccessfuly = false;
continue;
}
else if (!asset->IsValid())
{
AZ_Error(builderName, false, "SRG asset has no layout information. AssetId='%s' Path='%s'",
assetId.ToString<AZStd::string>().c_str(), srgFilePath.c_str());
readSRGsSuccessfuly = false;
continue;
}
srgAssets.push_back(asset);
}
return readSRGsSuccessfuly;
}
AZStd::shared_ptr<ShaderFiles> PrepareSourceInput(
[[maybe_unused]] const char* builderName,
const AZStd::string& shaderAssetSourcePath,
@@ -171,87 +108,6 @@ namespace AZ
return files;
}
//! [GFX TODO] [ATOM-15472] Deprecated, remove when this ticket is addressed.
AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
const char* builderName,
const AzslSubProducts::Paths& pathOfJsonFiles,
AzslData& azslData,
ShaderResourceGroupAssets& srgAssets,
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout,
BindingDependencies& bindingDependencies,
RootConstantData& rootConstantData)
{
AzslCompiler azslc(azslData.m_preprocessedFullPath); // set the input file for eventual error messages, but the compiler won't be called on it.
bool allReadSuccess = true;
// read: input assembly reflection
// shader resource group reflection
// options reflection
// binding dependencies reflection
int indicesOfInterest[] = { AzslSubProducts::ia, AzslSubProducts::srg, AzslSubProducts::options, AzslSubProducts::bindingdep };
AZStd::unordered_map<int, Outcome<rapidjson::Document, AZStd::string>> outcomes;
for (int i : indicesOfInterest)
{
outcomes[i] = JsonSerializationUtils::ReadJsonFile(pathOfJsonFiles[i]);
if (!outcomes[i].IsSuccess())
{
AZ_Error(builderName, false, "%s", outcomes[i].GetError().c_str());
allReadSuccess = false;
}
}
if (!allReadSuccess)
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Get full list of functions eligible for vertex shader entry points
// along with metadata for constructing the InputAssembly for each of them
if (!azslc.ParseIaPopulateFunctionData(outcomes[AzslSubProducts::ia].GetValue(), azslData.m_functions))
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Each SRG is built as a separate asset in the SrgLayoutBuilder, here we just
// build the list and load the data from multiple dependency assets.
if (!azslc.ParseSrgPopulateSrgData(outcomes[AzslSubProducts::srg].GetValue(), azslData.m_srgData))
{
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// Add all Shader Resource Group Assets that were defined in the shader code to the shader asset
if (!LoadShaderResourceGroupAssets(builderName, azslData.m_srgData, srgAssets))
{
AZ_Error(builderName, false, "Failed to obtain shader resource group assets");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// The shader options define what options are available, what are the allowed values/range
// for each option and what is its default value.
if (!azslc.ParseOptionsPopulateOptionGroupLayout(outcomes[AzslSubProducts::options].GetValue(), shaderOptionGroupLayout))
{
AZ_Error(builderName, false, "Failed to find a valid list of shader options!");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// It analyzes the shader external bindings (all SRG contents)
// and informs us on register indexes and shader stages using these resources
if (!azslc.ParseBindingdepPopulateBindingDependencies(outcomes[AzslSubProducts::bindingdep].GetValue(), bindingDependencies)) // consuming data from binding-dep
{
AZ_Error(builderName, false, "Failed to obtain shader resource binding reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
// access the root constants reflection
if (!azslc.ParseSrgPopulateRootConstantData(outcomes[AzslSubProducts::srg].GetValue(), rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section)
{
AZ_Error(builderName, false, "Failed to obtain root constant data reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
}
return AssetBuilderSDK::ProcessJobResult_Success;
}
AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
const char* builderName,
const AzslSubProducts::Paths& pathOfJsonFiles,
@@ -361,7 +217,8 @@ namespace AZ
}
//! the binding dependency structure may store lots of high level function names which are not entry points
static void PruneNonEntryFunctions(BindingDependencies& bindingDependencies /*inout*/, const MapOfStringToStageType& shaderEntryPoints)
static void PruneNonEntryFunctions(
BindingDependencies& bindingDependencies /*inout*/, const MapOfStringToStageType& shaderEntryPoints)
{
auto cleaner = [&shaderEntryPoints](BindingDependencies::FunctionsNameVector& functionVector)
{
@@ -387,120 +244,6 @@ namespace AZ
});
}
}
RHI::Ptr<RHI::PipelineLayoutDescriptor> BuildPipelineLayoutDescriptorForApi(
[[maybe_unused]] const char* builderName,
RHI::ShaderPlatformInterface* shaderPlatformInterface,
BindingDependencies& bindingDependencies /*inout*/,
const ShaderResourceGroupAssets& srgAssets,
const MapOfStringToStageType& shaderEntryPoints,
const RHI::ShaderCompilerArguments& shaderCompilerArguments,
const RootConstantData* rootConstantData /*= nullptr*/)
{
PruneNonEntryFunctions(bindingDependencies, shaderEntryPoints);
// Translates from a list of function names that use a resource to a shader stage mask.
auto getRHIShaderStageMask = [&shaderEntryPoints](const BindingDependencies::FunctionsNameVector& functions)
{
RHI::ShaderStageMask mask = RHI::ShaderStageMask::None;
// Iterate through all the functions that are using the resource.
for (const auto& functionName : functions)
{
// Search the function name into the list of valid entry points into the shader.
auto findId = AZStd::find_if(shaderEntryPoints.begin(), shaderEntryPoints.end(), [&functionName, &mask](const auto& item)
{
return item.first == functionName;
});
if (findId != shaderEntryPoints.end())
{
// Use the entry point shader stage type to calculate the mask.
RHI::ShaderHardwareStage hardwareStage = ToAssetBuilderShaderType(findId->second);
mask |= static_cast<RHI::ShaderStageMask>(AZ_BIT(static_cast<uint32_t>(RHI::ToRHIShaderStage(hardwareStage))));
}
}
return mask;
};
// Build general PipelineLayoutDescriptor data that is provided for all platforms
RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDescriptor = shaderPlatformInterface->CreatePipelineLayoutDescriptor();
RHI::ShaderPlatformInterface::ShaderResourceGroupInfoList srgInfos;
for (const auto& srgAsset : srgAssets)
{
// Search the binding info for a Shader Resource Group.
AZStd::string_view srgName = srgAsset->GetName().GetStringView();
const BindingDependencies::SrgResources* srgResources = bindingDependencies.GetSrg(srgName);
if (!srgResources)
{
AZ_Error(builderName, false, "SRG %s not found in the dependency dataset", srgName.data());
return nullptr;
}
RHI::ShaderResourceGroupBindingInfo srgBindingInfo;
srgBindingInfo.m_spaceId = srgResources->m_registerSpace;
const RHI::ShaderResourceGroupLayout* layout = srgAsset->GetLayout(shaderPlatformInterface->GetAPIType());
// Calculate the binding in for the constant data. All constant data share the same binding info.
srgBindingInfo.m_constantDataBindingInfo = {
getRHIShaderStageMask(srgResources->m_srgConstantsDependencies.m_binding.m_dependentFunctions),
srgResources->m_srgConstantsDependencies.m_binding.m_registerId };
// Calculate the binding info for each resource of the Shader Resource Group.
for (auto const& resource : srgResources->m_resources)
{
auto const& resourceInfo = resource.second;
srgBindingInfo.m_resourcesRegisterMap.insert(
{ AZ::Name(resourceInfo.m_selfName),
RHI::ResourceBindingInfo(getRHIShaderStageMask(resourceInfo.m_dependentFunctions), resourceInfo.m_registerId) });
}
pipelineLayoutDescriptor->AddShaderResourceGroupLayoutInfo(*layout, srgBindingInfo);
srgInfos.push_back(RHI::ShaderPlatformInterface::ShaderResourceGroupInfo{ layout, srgBindingInfo });
}
RHI::Ptr<RHI::ConstantsLayout> rootConstantsLayout = RHI::ConstantsLayout::Create();
if (rootConstantData)
{
for (const auto& constantData : rootConstantData->m_constants)
{
RHI::ShaderInputConstantDescriptor rootConstantDesc(
constantData.m_nameId, constantData.m_constantByteOffset, constantData.m_constantByteSize,
rootConstantData->m_bindingInfo.m_registerId);
rootConstantsLayout->AddShaderInput(rootConstantDesc);
}
}
if (!rootConstantsLayout->Finalize())
{
AZ_Error(builderName, false, "Failed to finalize root constants layout");
return nullptr;
}
pipelineLayoutDescriptor->SetRootConstantsLayout(*rootConstantsLayout);
RHI::ShaderPlatformInterface::RootConstantsInfo rootConstantInfo;
if (rootConstantData)
{
rootConstantInfo.m_spaceId = rootConstantData->m_bindingInfo.m_space;
rootConstantInfo.m_registerId = rootConstantData->m_bindingInfo.m_registerId;
}
else
{
RootConstantData dummyRootConstantData;
rootConstantInfo.m_spaceId = dummyRootConstantData.m_bindingInfo.m_space;
rootConstantInfo.m_registerId = dummyRootConstantData.m_bindingInfo.m_registerId;
}
rootConstantInfo.m_totalSizeInBytes = rootConstantsLayout->GetDataSize();
// Build platform-specific PipelineLayoutDescriptor data, and finalize
if (!shaderPlatformInterface->BuildPipelineLayoutDescriptor(
pipelineLayoutDescriptor, srgInfos, rootConstantInfo, shaderCompilerArguments))
{
AZ_Error(builderName, false, "Failed to build pipeline layout descriptor");
return nullptr;
}
return pipelineLayoutDescriptor;
}
static AZStd::string DumpCode(
[[maybe_unused]] const char* builderName,
@@ -539,14 +282,8 @@ namespace AZ
return finalFilePath;
}
// [GFX TODO] Remove 'add2' when [ATOM-15472]
AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString, bool add2)
AZStd::string DumpPreprocessedCode(const char* builderName, const AZStd::string& preprocessedCode, const AZStd::string& tempDirPath, const AZStd::string& stemName, const AZStd::string& apiTypeString)
{
if (add2)
{
return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin2");
}
return DumpCode(builderName, preprocessedCode, tempDirPath, stemName, apiTypeString, "azslin");
}
@@ -584,8 +321,7 @@ namespace AZ
AZStd::remove_if(AZ_BEGIN_END(platformInterfaces),
[&](const RHI::ShaderPlatformInterface* shaderPlatformInterface) {
return !shaderPlatformInterface ||
shaderSourceData.IsRhiBackendDisabled(shaderPlatformInterface->GetAPIName()) ||
(shaderPlatformInterface->GetAPIUniqueIndex() == static_cast<uint32_t>(AZ::RHI::APIIndex::Null));
shaderSourceData.IsRhiBackendDisabled(shaderPlatformInterface->GetAPIName());
}),
platformInterfaces.end());
return platformInterfaces;
@@ -718,15 +454,7 @@ namespace AZ
#endif
}
uint32_t MakeAzslBuildProductSubId(RPI::ShaderAssetSubId subId, RHI::APIType apiType)
{
auto subIdMaxEnumerator = RPI::ShaderAssetSubId::GeneratedHlslSource;
// separate bit space between subid enum, and api-type:
int shiftLeft = static_cast<uint32_t>(log2(static_cast<uint32_t>(subIdMaxEnumerator))) + 1;
return static_cast<uint32_t>(subId) + (apiType << shiftLeft);
}
Outcome<AZStd::string, AZStd::string> ObtainBuildArtifactPathFromShaderAssetBuilder2(
Outcome<AZStd::string, AZStd::string> ObtainBuildArtifactPathFromShaderAssetBuilder(
const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath,
const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId)
{
@@ -749,12 +477,12 @@ namespace AZ
platformId = AzFramework::PlatformId::IOS;
}
uint32_t assetSubId = RPI::ShaderAsset2::MakeProductAssetSubId(rhiUniqueIndex, supervariantIndex, aznumeric_cast<uint32_t>(shaderAssetSubId));
uint32_t assetSubId = RPI::ShaderAsset::MakeProductAssetSubId(rhiUniqueIndex, supervariantIndex, aznumeric_cast<uint32_t>(shaderAssetSubId));
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(shaderJsonPath, assetSubId);
if (!assetIdOutcome.IsSuccess())
{
return Failure(AZStd::string::format(
"Missing ShaderAssetBuilder2 product %s, for sub %d", shaderJsonPath.c_str(), (uint32_t)shaderAssetSubId));
"Missing ShaderAssetBuilder product %s, for sub %d", shaderJsonPath.c_str(), (uint32_t)shaderAssetSubId));
}
Data::AssetId assetId = assetIdOutcome.TakeValue();
@@ -778,113 +506,6 @@ namespace AZ
return AZ::Success(assetFullPath);
}
Outcome<AzslSubProducts::Paths> ObtainBuildArtifactsFromAzslBuilder([[maybe_unused]] const char* builderName, const AZStd::string& sourceFullPath, RHI::APIType apiType, const AZStd::string& platform)
{
AzslSubProducts::Paths products;
// platform id from identifier
AzFramework::PlatformId platformId = AzFramework::PlatformId::PC;
if (platform == "pc")
{
platformId = AzFramework::PlatformId::PC;
}
else if (platform == "mac")
{
platformId = AzFramework::PlatformId::MAC_ID;
}
else if (platform == "android")
{
platformId = AzFramework::PlatformId::ANDROID_ID;
}
else if (platform == "ios")
{
platformId = AzFramework::PlatformId::IOS;
}
for (RPI::ShaderAssetSubId sub : AzslSubProducts::SubList)
{
uint32_t assetSubId = MakeAzslBuildProductSubId(sub, apiType);
auto assetIdOutcome = RPI::AssetUtils::MakeAssetId(sourceFullPath, assetSubId);
AZ_Error(builderName, assetIdOutcome.IsSuccess(), "Missing AZSL product %s, for sub %d", sourceFullPath.c_str(), (uint32_t)sub);
if (!assetIdOutcome.IsSuccess())
{
return Failure();
}
Data::AssetId assetId = assetIdOutcome.TakeValue();
// get the relative path:
AZStd::string assetPath;
Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &Data::AssetCatalogRequests::GetAssetPathById, assetId);
// get the root:
AZStd::string assetRoot = AzToolsFramework::PlatformAddressedAssetCatalog::GetAssetRootForPlatform(platformId);
// join
AZStd::string assetFullPath;
AzFramework::StringFunc::Path::Join(assetRoot.c_str(), assetPath.c_str(), assetFullPath);
bool fileExists = IO::FileIOBase::GetInstance()->Exists(assetFullPath.c_str()) && !IO::FileIOBase::GetInstance()->IsDirectory(assetFullPath.c_str());
if (!fileExists)
{
return Failure();
}
products.push_back(assetFullPath);
}
return AZ::Success(products);
}
// DEPRECATED [ATOM-15472]
// See header for info.
// REMARK: The approach to string searching and matching done in this function is kind of naive
// because the strings can match text within a comment block, etc. So it is not 100% fool proof.
// We would need proper grammar parsing to reach 100% confidence.
// [GFX TODO][ATOM-5302][ATOM-5308] The following function will be removed once, both, [ATOM-5302] & [ATOM-5308] are addressed, and
// azslc allows redundant SrgSemantics for "partial" qualified SRGs.
SrgSkipFileResult ShouldSkipFileForSrgProcessing([[maybe_unused]] const char* builderName, const AZStd::string_view fullPath)
{
AZ::IO::FileIOStream stream(fullPath.data(), AZ::IO::OpenMode::ModeRead);
if (!stream.IsOpen())
{
AZ_Warning(builderName, false, "\"%s\" source file could not be opened.", fullPath.data());
return SrgSkipFileResult::Error;
}
if (!stream.CanRead())
{
AZ_Warning(builderName, false, "\"%s\" source file could not be read.", fullPath.data());
return SrgSkipFileResult::Error;
}
// Do a quick check for "ShaderResourceGroup" to determine if this file might even have a ShaderResourceGroup to parse.
AZStd::string fileContents;
fileContents.resize(stream.GetLength());
stream.Read(stream.GetLength(), fileContents.data());
static const AZStd::regex partialSrgRegex("\n\\s*partial\\s+ShaderResourceGroup\\s+", AZStd::regex::ECMAScript);
if (AZStd::regex_search(fileContents.data(), partialSrgRegex))
{
// It is considered a programmer's error if a file declares both, non-partial and partial SRGs.
static const AZStd::regex srgRegex("\n\\s*ShaderResourceGroup\\s+", AZStd::regex::ECMAScript);
if (AZStd::regex_search(fileContents.data(), srgRegex))
{
AZ_Error(builderName, false, "\"%s\" defines both partial and non-partial SRGs.", fullPath.data());
return SrgSkipFileResult::Error;
}
// We should skip files that define partial Srgs because an srgi file will eventually
// include it.
return SrgSkipFileResult::SkipFile;
}
// This is an optimization to avoid unnecessary preprocessing a whole tree of azsli files; we can detect when a
// ShaderResourceGroupAsset wouldn't be produced and return early. Note, we could remove this early-return check
// if the preprocessing code below is updated to not follow include paths [ATOM-5302].
// (Note this optimization is not valid for srgi files because those do require scanning all include paths)"
if (fileContents.find("ShaderResourceGroup") == AZStd::string::npos)
{
// No ShaderResourceGroup in this file, so there's nothing to do. Create no jobs and report success.
return SrgSkipFileResult::SkipFile;
}
return SrgSkipFileResult::ContinueProcess;
}
RHI::Ptr<RHI::PipelineLayoutDescriptor> BuildPipelineLayoutDescriptorForApi(
[[maybe_unused]] const char* builderName, const RPI::ShaderResourceGroupLayoutList& srgLayoutList, const MapOfStringToStageType& shaderEntryPoints,
const RHI::ShaderCompilerArguments& shaderCompilerArguments, const RootConstantData& rootConstantData,
@@ -18,8 +18,7 @@
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include "AzslData.h"
@@ -32,7 +31,6 @@ namespace AZ
struct BindingDependencies;
struct RootConstantData;
using ShaderResourceGroupAssets = AZStd::fixed_vector<Data::Asset<RPI::ShaderResourceGroupAsset>, RHI::Limits::Pipeline::ShaderResourceGroupCountMax>;
using MapOfStringToStageType = AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>;
namespace ShaderBuilderUtility
@@ -53,7 +51,7 @@ namespace AZ
using SubId = RPI::ShaderAssetSubId;
// product sub id enumerators:
static constexpr SubId SubList[] = {SubId::PostPreprocessingPureAzsl,
static constexpr SubId SubList[] = {SubId::FlatAzsl,
SubId::IaJson,
SubId::OmJson,
SubId::SrgJson,
@@ -66,20 +64,6 @@ namespace AZ
using Paths = AZStd::fixed_vector<AZStd::string, AZ_ARRAY_SIZE(SubList)>;
};
//! [GFX TODO] [ATOM-15472] Deprecated, remove when this ticket is addressed.
//! Collects and generates the necessary data for compiling a shader.
//! @azslData must have paths correctly set.
//! shaderOptionGroupLayout, azslData, srgAssets get the output data.
AssetBuilderSDK::ProcessJobResultCode PopulateAzslDataFromJsonFiles(
const char* builderName,
const AzslSubProducts::Paths& pathOfJsonFiles,
AzslData& azslData,
ShaderResourceGroupAssets& srgAssets,
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout,
BindingDependencies& bindingDependencies,
RootConstantData& rootConstantData
);
//! Collects all the JSON files generated during AZSL compilation and loads the data as objects.
//! @azslData must have paths correctly set.
//! @azslData, @srgLayoutList, @shaderOptionGroupLayout, @bindingDependencies and @rootConstantData get the output data.
@@ -92,22 +76,6 @@ namespace AZ
RHI::ShaderHardwareStage ToAssetBuilderShaderType(RPI::ShaderStageType stageType);
//! Must be called before shaderPlatformInterface->CompilePlatformInternal()
//! This function will prune non entry functions from BindingDependencies and use the
//! rest of input data to create a pipeline layout descriptor.
//! The pipeline layout descriptor is returned, but the same data will also be set into the @shaderPlatformInterface
//! object, which is why it is important to call this method before calling shaderPlatformInterface->CompilePlatformInternal().
RHI::Ptr<RHI::PipelineLayoutDescriptor> BuildPipelineLayoutDescriptorForApi(
const char* builderName,
RHI::ShaderPlatformInterface* shaderPlatformInterface,
BindingDependencies& bindingDependencies /*inout*/,
const ShaderResourceGroupAssets& srgAssets,
const MapOfStringToStageType& shaderEntryPoints,
const RHI::ShaderCompilerArguments& shaderCompilerArguments,
const RootConstantData* rootConstantData = nullptr
);
//! Must be called before shaderPlatformInterface->CompilePlatformInternal()
//! This function will prune non entry functions from BindingDependencies and use the
//! rest of input data to create a pipeline layout descriptor.
@@ -145,8 +113,7 @@ namespace AZ
const AZStd::string& preprocessedCode,
const AZStd::string& tempDirPath,
const AZStd::string& preprocessedFileName,
const AZStd::string& apiTypeString = "",
bool add2 = false); // [GFX TODO] Remove add2 when [ATOM-15472]
const AZStd::string& apiTypeString = "");
//! Create a file from a string's content.
//! That file will be named filename.api.azsl.prepend
@@ -181,25 +148,11 @@ namespace AZ
void LogProfilingData(const char* builderName, AZStd::string_view shaderPath);
//! Job products sub id generation helper for AzslBuilder
uint32_t MakeAzslBuildProductSubId(RPI::ShaderAssetSubId subId, RHI::APIType apiType);
//! Returns the asset path of a product artifact produced by ShaderAssetBuilder2.
Outcome<AZStd::string, AZStd::string> ObtainBuildArtifactPathFromShaderAssetBuilder2(
//! Returns the asset path of a product artifact produced by ShaderAssetBuilder.
Outcome<AZStd::string, AZStd::string> ObtainBuildArtifactPathFromShaderAssetBuilder(
const uint32_t rhiUniqueIndex, const AZStd::string& platformIdentifier, const AZStd::string& shaderJsonPath,
const uint32_t supervariantIndex, RPI::ShaderAssetSubId shaderAssetSubId);
//! Reconstructs the expected output product paths of the AzslBuilder (from the 2 arguments @azslSourceFullPath and @apiType)
Outcome<AzslSubProducts::Paths> ObtainBuildArtifactsFromAzslBuilder(const char* builderName, const AZStd::string& azslSourceFullPath, RHI::APIType apiType, const AZStd::string& platform);
//! Returns true if a file should skip processing.
//! Should be called only for non *.srgi files.
//! If the file contains "partial ShaderResourceGroup" (validated through a proper regular expression)
//! then it is skippable. If this same file also defines non-partial SRGs it will be skipped with error.
//! If the file doesn't contain "ShaderResourceGroup" then it does not define a ShaderResourceGroup
//! and it is skippable too.
enum class SrgSkipFileResult { Error, SkipFile, ContinueProcess };
SrgSkipFileResult ShouldSkipFileForSrgProcessing(const char* builderName, const AZStd::string_view fullPath);
} // ShaderBuilderUtility namespace
} // ShaderBuilder namespace
} // AZ
File diff suppressed because it is too large Load Diff
@@ -29,25 +29,34 @@ namespace AZ
{
struct AzslData;
//! This is nothing more than a class to help consolidate all
//! the data needed to generate a shader variant and prevent
//! all the functions involved in the process to have too many
//! arguments.
struct ShaderVariantCreationContext
{
const Data::AssetId m_assetId;
const AZStd::string& m_hlslSourcePath;
const AZStd::string& m_hlslSourceContent;
const RPI::ShaderSourceData& m_shaderSourceDataDescriptor;
const AZStd::string& m_tempDirPath; //! Used to write temporary files during shader compilation, like *.hlsl, or *.air, or *.metallib, etc.
RHI::ShaderPlatformInterface& m_shaderPlatformInterface;
const AssetBuilderSDK::PlatformInfo& m_platformInfo;
const RHI::ShaderCompilerArguments& m_shaderCompilerArguments;
//! Used to write temporary files during shader compilation, like *.hlsl, or *.air, or *.metallib, etc.
const AZStd::string& m_tempDirPath;
//! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset,
//! especially during hot-reload. A (ShaderVariantAsset.timestamp) >= (ShaderAsset.timestamp).
const AZStd::sys_time_t m_assetBuildTimestamp;
const RPI::ShaderSourceData& m_shaderSourceDataDescriptor;
const RPI::ShaderOptionGroupLayout& m_shaderOptionGroupLayout;
const MapOfStringToStageType& m_shaderEntryPoints;
AZStd::sys_time_t m_shaderAssetBuildTimestamp; //!< Copied from the ShaderAsset, used to synchronize versions of the ShaderAsset and ShaderVariantAsset, especially during hot-reload.
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts> m_outputByproducts;
const Data::AssetId m_shaderVariantAssetId;
const AZStd::string& m_shaderStemNamePrefix; //<shaderName>-<supervariantName>
const AZStd::string& m_hlslSourcePath;
const AZStd::string& m_hlslSourceContent;
};
class ShaderVariantAssetBuilder
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler
{
public:
AZ_TYPE_INFO(ShaderVariantAssetBuilder, "{2F84C802-95AF-4B73-9017-2DA9AA8C0C89}");
AZ_TYPE_INFO(ShaderVariantAssetBuilder, "{C959AEC2-2083-4488-AD88-F61B1144535B}");
static constexpr char ShaderVariantAssetBuilderJobKey[] = "Shader Variant Asset";
@@ -61,16 +70,14 @@ namespace AZ
//! The ShaderVariantAsset returned by this function won't be written to the filesystem.
//! You should call SerializeOutShaderVariantAsset to write it to the temp folder assigned
//! by the asset processor.
static AZ::Outcome<Data::Asset<RPI::ShaderVariantAsset>, AZStd::string> CreateShaderVariantAssetForAPI(
static AZ::Outcome<Data::Asset<RPI::ShaderVariantAsset>, AZStd::string> CreateShaderVariantAsset(
const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo,
ShaderVariantCreationContext& context,
RHI::ShaderPlatformInterface& shaderPlatformInterface,
AzslData& azslData,
const RHI::ShaderCompilerArguments& shaderCompilerArguments,
const AZStd::string& pathToOmJson,
const AZStd::string& pathToIaJson);
ShaderVariantCreationContext& creationContext,
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts>& outputByproducts);
static bool SerializeOutShaderVariantAsset(const Data::Asset<RPI::ShaderVariantAsset> shaderVariantAsset, const AZStd::string& shaderFullPath, const AZStd::string& tempDirPath,
static bool SerializeOutShaderVariantAsset(
const Data::Asset<RPI::ShaderVariantAsset> shaderVariantAsset,
const AZStd::string& shaderStemNamePrefix, const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct);
// AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
@@ -79,6 +86,11 @@ namespace AZ
private:
AZ_DISABLE_COPY_MOVE(ShaderVariantAssetBuilder);
static constexpr uint32_t ShaderVariantLoadErrorParam = 0;
static constexpr uint32_t ShaderSourceFilePathJobParam = 2;
static constexpr uint32_t ShaderVariantJobVariantParam = 3;
static constexpr uint32_t ShouldExitEarlyFromProcessJobParam = 4;
//! Called from ProcessJob when the job is supposed to create a ShaderVariantTreeAsset.
void ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
@@ -1,978 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
#include <ShaderVariantAssetBuilder2.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantTreeAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderOptionGroup.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantAssetCreator2.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantTreeAssetCreator.h>
#include <Atom/RPI.Edit/Common/JsonUtils.h>
#include <AtomCore/Serialization/Json/JsonUtils.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
#include <Atom/RHI.Edit/Utils.h>
#include <Atom/RHI.Edit/ShaderPlatformInterface.h>
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RHI.Reflect/ConstantsLayout.h>
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Platform/PlatformDefaults.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/JSON/document.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/sort.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include "ShaderAssetBuilder2.h"
#include "ShaderBuilderUtility.h"
#include "AzslData.h"
#include "AzslCompiler.h"
#include "AzslBuilder.h"
#include <CommonFiles/Preprocessor.h>
#include <CommonFiles/GlobalBuildOptions.h>
#include <ShaderPlatformInterfaceRequest.h>
#include "AtomShaderConfig.h"
namespace AZ
{
namespace ShaderBuilder
{
static constexpr char ShaderVariantAssetBuilder2Name[] = "ShaderVariantAssetBuilder2";
static void AddShaderAssetJobDependency2(
AssetBuilderSDK::JobDescriptor& jobDescriptor, const AssetBuilderSDK::PlatformInfo& platformInfo,
const AZStd::string& shaderVariantListFilePath, const AZStd::string& shaderFilePath)
{
AZStd::vector<AZStd::string> possibleDependencies =
AZ::RPI::AssetUtils::GetPossibleDepenencyPaths(shaderVariantListFilePath, shaderFilePath);
for (auto& file : possibleDependencies)
{
AssetBuilderSDK::JobDependency jobDependency;
jobDependency.m_jobKey = ShaderAssetBuilder2::ShaderAssetBuilder2JobKey;
jobDependency.m_platformIdentifier = platformInfo.m_identifier;
jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
jobDependency.m_sourceFile.m_sourceFileDependencyPath = file;
jobDescriptor.m_jobDependencyList.push_back(jobDependency);
}
}
//! Returns true if @sourceFileFullPath starts with a valid asset processor scan folder, false otherwise.
//! In case of true, it splits @sourceFileFullPath into @scanFolderFullPath and @filePathFromScanFolder.
//! @sourceFileFullPath The full path to a source asset file.
//! @scanFolderFullPath [out] Gets the full path of the scan folder where the source file is located.
//! @filePathFromScanFolder [out] Get the file path relative to @scanFolderFullPath.
static bool SplitSourceAssetPathIntoScanFolderFullPathAndRelativeFilePath2(const AZStd::string& sourceFileFullPath, AZStd::string& scanFolderFullPath, AZStd::string& filePathFromScanFolder)
{
AZStd::vector<AZStd::string> scanFolders;
bool success = false;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(success, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetSafeFolders, scanFolders);
if (!success)
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Couldn't get the scan folders");
return false;
}
for (AZStd::string scanFolder : scanFolders)
{
AzFramework::StringFunc::Path::Normalize(scanFolder);
if (!AZ::StringFunc::StartsWith(sourceFileFullPath, scanFolder))
{
continue;
}
const size_t scanFolderSize = scanFolder.size();
const size_t sourcePathSize = sourceFileFullPath.size();
scanFolderFullPath = scanFolder;
filePathFromScanFolder = sourceFileFullPath.substr(scanFolderSize + 1, sourcePathSize - scanFolderSize - 1);
return true;
}
return false;
}
//! Validates if a given .shadervariantlist file is located at the correct path for a given .shader full path.
//! There are two valid paths:
//! 1- Lower Precedence: The same folder where the .shader file is located.
//! 2- Higher Precedence: <DEVROOT>/<GAME>/ShaderVariants/<Same Scan Folder Subpath as the .shader file>.
//! The "Higher Precedence" path gives the option to game projects to override what variants to generate. If this
//! file exists then the "Lower Precedence" path is disregarded.
//! A .shader full path is located under an AP scan folder.
//! Example: "<DEVROOT>/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shader"
//! - In this example the Scan Folder is "<DEVROOT>/Gems/Atom/Feature/Common/Assets", while the subfolder is "Materials/Types".
//! The "Higher Precedence" expected valid location for the .shadervariantlist would be:
//! - <DEVROOT>/<GameProject>/ShaderVariants/Materials/Types/StandardPBR_ForwardPass.shadervariantlist.
//! The "Lower Precedence" valid location would be:
//! - <DEVROOT>/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.shadervariantlist.
//! @shouldExitEarlyFromProcessJob [out] Set to true if ProcessJob should do no work but return successfully.
//! Set to false if ProcessJob should do work and create assets.
//! When @shaderVariantListFileFullPath is provided by a Gem/Feature instead of the Game Project
//! We check if the game project already defined the shader variant list, and if it did it means
//! ProcessJob should do no work, but return successfully nonetheless.
static bool ValidateShaderVariantListLocation2(const AZStd::string& shaderVariantListFileFullPath,
const AZStd::string& shaderFileFullPath, bool& shouldExitEarlyFromProcessJob)
{
AZStd::string scanFolderFullPath;
AZStd::string shaderProductFileRelativePath;
if (!SplitSourceAssetPathIntoScanFolderFullPathAndRelativeFilePath2(shaderFileFullPath, scanFolderFullPath, shaderProductFileRelativePath))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Couldn't get the scan folder for shader [%s]", shaderFileFullPath.c_str());
return false;
}
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "For shader [%s], Scan folder full path [%s], relative file path [%s]", shaderFileFullPath.c_str(), scanFolderFullPath.c_str(), shaderProductFileRelativePath.c_str());
AZStd::string shaderVariantListFileRelativePath = shaderProductFileRelativePath;
AzFramework::StringFunc::Path::ReplaceExtension(shaderVariantListFileRelativePath, RPI::ShaderVariantListSourceData::Extension);
const char * gameProjectPath = nullptr;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(gameProjectPath, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAbsoluteDevGameFolderPath);
AZStd::string expectedHigherPrecedenceFileFullPath;
AzFramework::StringFunc::Path::Join(gameProjectPath, RPI::ShaderVariantTreeAsset::CommonSubFolder, expectedHigherPrecedenceFileFullPath, false /* handle directory overlap? */, false /* be case insensitive? */);
AzFramework::StringFunc::Path::Join(expectedHigherPrecedenceFileFullPath.c_str(), shaderProductFileRelativePath.c_str(), expectedHigherPrecedenceFileFullPath, false /* handle directory overlap? */, false /* be case insensitive? */);
AzFramework::StringFunc::Path::ReplaceExtension(expectedHigherPrecedenceFileFullPath, AZ::RPI::ShaderVariantListSourceData::Extension);
AzFramework::StringFunc::Path::Normalize(expectedHigherPrecedenceFileFullPath);
AZStd::string normalizedShaderVariantListFileFullPath = shaderVariantListFileFullPath;
AzFramework::StringFunc::Path::Normalize(normalizedShaderVariantListFileFullPath);
if (expectedHigherPrecedenceFileFullPath == normalizedShaderVariantListFileFullPath)
{
// Whenever the Game Project declares a *.shadervariantlist file we always do work.
shouldExitEarlyFromProcessJob = false;
return true;
}
AZ::Data::AssetInfo assetInfo;
AZStd::string watchFolder;
bool foundHigherPrecedenceAsset = false;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundHigherPrecedenceAsset
, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath
, expectedHigherPrecedenceFileFullPath.c_str(), assetInfo, watchFolder);
if (foundHigherPrecedenceAsset)
{
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "The shadervariantlist [%s] has been overriden by the game project with [%s]",
normalizedShaderVariantListFileFullPath.c_str(), expectedHigherPrecedenceFileFullPath.c_str());
shouldExitEarlyFromProcessJob = true;
return true;
}
// Check the "Lower Precedence" case, .shader path == .shadervariantlist path.
AZStd::string normalizedShaderFileFullPath = shaderFileFullPath;
AzFramework::StringFunc::Path::Normalize(normalizedShaderFileFullPath);
AZStd::string normalizedShaderFileFullPathWithoutExtension = normalizedShaderFileFullPath;
AzFramework::StringFunc::Path::StripExtension(normalizedShaderFileFullPathWithoutExtension);
AZStd::string normalizedShaderVariantListFileFullPathWithoutExtension = normalizedShaderVariantListFileFullPath;
AzFramework::StringFunc::Path::StripExtension(normalizedShaderVariantListFileFullPathWithoutExtension);
#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
//In certain circumstances, the capitalization of the drive letter may not match
const bool caseSensitive = false;
#else
//On the other platforms there's no drive letter, so it should be a non-issue.
const bool caseSensitive = true;
#endif
if (!StringFunc::Equal(normalizedShaderFileFullPathWithoutExtension.c_str(), normalizedShaderVariantListFileFullPathWithoutExtension.c_str(), caseSensitive))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "For shader file at path [%s], the shader variant list [%s] is expected to be located at [%s.%s] or [%s]"
, normalizedShaderFileFullPath.c_str(), normalizedShaderVariantListFileFullPath.c_str(),
normalizedShaderFileFullPathWithoutExtension.c_str(), RPI::ShaderVariantListSourceData::Extension,
expectedHigherPrecedenceFileFullPath.c_str());
return false;
}
shouldExitEarlyFromProcessJob = false;
return true;
}
// We treat some issues as warnings and return "Success" from CreateJobs allows us to report the dependency.
// If/when a valid dependency file appears, that will trigger the ShaderVariantAssetBuilder2 to run again.
// Since CreateJobs will pass, we forward this message to ProcessJob which will report it as an error.
struct LoadResult2
{
enum class Code
{
Error,
DeferredError,
Success
};
Code m_code;
AZStd::string m_deferredMessage; // Only used when m_code == DeferredError
};
static LoadResult2 LoadShaderVariantList2(const AZStd::string& variantListFullPath, RPI::ShaderVariantListSourceData& shaderVariantList, AZStd::string& shaderSourceFileFullPath,
bool& shouldExitEarlyFromProcessJob)
{
// Need to get the name of the shader file from the template so that we can preprocess the shader data and setup
// source file dependencies.
if (!RPI::JsonUtils::LoadObjectFromFile(variantListFullPath, shaderVariantList))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to parse Shader Variant List Descriptor JSON from [%s]", variantListFullPath.c_str());
return LoadResult2{LoadResult2::Code::Error};
}
const AZStd::string resolvedShaderPath = AZ::RPI::AssetUtils::ResolvePathReference(variantListFullPath, shaderVariantList.m_shaderFilePath);
if (!AZ::IO::LocalFileIO::GetInstance()->Exists(resolvedShaderPath.c_str()))
{
return LoadResult2{LoadResult2::Code::DeferredError, AZStd::string::format("The shader path [%s] was not found.", resolvedShaderPath.c_str())};
}
shaderSourceFileFullPath = resolvedShaderPath;
if (!ValidateShaderVariantListLocation2(variantListFullPath, shaderSourceFileFullPath, shouldExitEarlyFromProcessJob))
{
return LoadResult2{LoadResult2::Code::Error};
}
if (shouldExitEarlyFromProcessJob)
{
return LoadResult2{LoadResult2::Code::Success};
}
auto resultOutcome = RPI::ShaderVariantTreeAssetCreator::ValidateStableIdsAreUnique(shaderVariantList.m_shaderVariants);
if (!resultOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Variant info validation error: %s", resultOutcome.GetError().c_str());
return LoadResult2{LoadResult2::Code::Error};
}
if (!IO::FileIOBase::GetInstance()->Exists(shaderSourceFileFullPath.c_str()))
{
return LoadResult2{LoadResult2::Code::DeferredError, AZStd::string::format("ShaderSourceData file does not exist: %s.", shaderSourceFileFullPath.c_str())};
}
return LoadResult2{LoadResult2::Code::Success};
} // LoadShaderVariantListAndAzslSource
void ShaderVariantAssetBuilder2::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{
AZStd::string variantListFullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), variantListFullPath, true);
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "CreateJobs for Shader Variant List \"%s\"\n", variantListFullPath.data());
RPI::ShaderVariantListSourceData shaderVariantList;
AZStd::string shaderSourceFileFullPath;
bool shouldExitEarlyFromProcessJob = false;
const LoadResult2 loadResult = LoadShaderVariantList2(variantListFullPath, shaderVariantList, shaderSourceFileFullPath, shouldExitEarlyFromProcessJob);
if (loadResult.m_code == LoadResult2::Code::Error)
{
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
return;
}
if (loadResult.m_code == LoadResult2::Code::DeferredError || shouldExitEarlyFromProcessJob)
{
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
// Let's create fake jobs that will fail ProcessJob, but are useful to establish dependency on the shader file.
AssetBuilderSDK::JobDescriptor jobDescriptor;
jobDescriptor.m_priority = -5000;
jobDescriptor.m_critical = false;
jobDescriptor.m_jobKey = ShaderVariantAssetBuilder2JobKey;
jobDescriptor.SetPlatformIdentifier(info.m_identifier.data());
AddShaderAssetJobDependency2(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath);
if (loadResult.m_code == LoadResult2::Code::DeferredError)
{
jobDescriptor.m_jobParameters.emplace(ShaderVariantLoadErrorParam, loadResult.m_deferredMessage);
}
if (shouldExitEarlyFromProcessJob)
{
// The value doesn't matter, what matters is the presence of the key which will
// signal that no assets should be produced on behalf of this shadervariantlist because
// the game project overrode it.
jobDescriptor.m_jobParameters.emplace(ShouldExitEarlyFromProcessJobParam, variantListFullPath);
}
response.m_createJobOutputs.push_back(jobDescriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
AZ_TraceContext("For platform", info.m_identifier.data());
// First job is for the ShaderVariantTreeAsset.
{
AssetBuilderSDK::JobDescriptor jobDescriptor;
// The ShaderVariantTreeAsset is high priority, but must be generated after the ShaderAsset
jobDescriptor.m_priority = 1;
jobDescriptor.m_critical = false;
jobDescriptor.m_jobKey = GetShaderVariantTreeAssetJobKey();
jobDescriptor.SetPlatformIdentifier(info.m_identifier.data());
AddShaderAssetJobDependency2(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath);
jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath);
response.m_createJobOutputs.push_back(jobDescriptor);
}
// One job for each variant. Each job will produce one ".azshadervariant" per RHI per supervariant.
for (const AZ::RPI::ShaderVariantListSourceData::VariantInfo& variantInfo : shaderVariantList.m_shaderVariants)
{
AZStd::string variantInfoAsJsonString;
const bool convertSuccess = AZ::RPI::JsonUtils::SaveObjectToJsonString(variantInfo, variantInfoAsJsonString);
AZ_Assert(convertSuccess, "Failed to convert VariantInfo to json string");
AssetBuilderSDK::JobDescriptor jobDescriptor;
// There can be tens/hundreds of thousands of shader variants. By default each shader will get
// a root variant that can be used at runtime. In order to prevent the AssetProcessor from
// being overtaken by shader variant compilation We mark all non-root shader variant generation
// as non critical and very low priority.
jobDescriptor.m_priority = -5000;
jobDescriptor.m_critical = false;
jobDescriptor.m_jobKey = GetShaderVariantAssetJobKey(RPI::ShaderVariantStableId{variantInfo.m_stableId});
jobDescriptor.SetPlatformIdentifier(info.m_identifier.data());
// The ShaderVariantAssets are job dependent on the ShaderVariantTreeAsset.
AssetBuilderSDK::SourceFileDependency fileDependency;
fileDependency.m_sourceFileDependencyPath = variantListFullPath;
AssetBuilderSDK::JobDependency variantTreeJobDependency;
variantTreeJobDependency.m_jobKey = GetShaderVariantTreeAssetJobKey();
variantTreeJobDependency.m_platformIdentifier = info.m_identifier;
variantTreeJobDependency.m_sourceFile = fileDependency;
variantTreeJobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
jobDescriptor.m_jobDependencyList.emplace_back(variantTreeJobDependency);
jobDescriptor.m_jobParameters.emplace(ShaderVariantJobVariantParam, variantInfoAsJsonString);
jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath);
response.m_createJobOutputs.push_back(jobDescriptor);
}
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
} // CreateJobs
void ShaderVariantAssetBuilder2::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
const auto& jobParameters = request.m_jobDescription.m_jobParameters;
if (jobParameters.find(ShaderVariantLoadErrorParam) != jobParameters.end())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Error during CreateJobs: %s", jobParameters.at(ShaderVariantLoadErrorParam).c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (jobParameters.find(ShouldExitEarlyFromProcessJobParam) != jobParameters.end())
{
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Doing nothing on behalf of [%s] because it's been overridden by game project.", jobParameters.at(ShaderVariantLoadErrorParam).c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
if (request.m_jobDescription.m_jobKey == GetShaderVariantTreeAssetJobKey())
{
ProcessShaderVariantTreeJob(request, response);
}
else
{
ProcessShaderVariantJob(request, response);
}
}
static RPI::Ptr<RPI::ShaderOptionGroupLayout> LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2(
const RHI::ShaderPlatformInterface* shaderPlatformInterface,
const AssetBuilderSDK::PlatformInfo& platformInfo,
const AzslCompiler& azslCompiler,
const AZStd::string& shaderSourceFileFullPath,
const RPI::SupervariantIndex supervariantIndex)
{
auto optionsGroupPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2(
shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(),
AZ::RPI::ShaderAssetSubId::OptionsJson);
if (!optionsGroupPathOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", optionsGroupPathOutcome.GetError().c_str());
return nullptr;
}
auto optionsGroupJsonPath = optionsGroupPathOutcome.TakeValue();
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = RPI::ShaderOptionGroupLayout::Create();
// The shader options define what options are available, what are the allowed values/range
// for each option and what is its default value.
auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(optionsGroupJsonPath);
if (!jsonOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", jsonOutcome.GetError().c_str());
return nullptr;
}
if (!azslCompiler.ParseOptionsPopulateOptionGroupLayout(jsonOutcome.GetValue(), shaderOptionGroupLayout))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to find a valid list of shader options!");
return nullptr;
}
return shaderOptionGroupLayout;
}
static void LoadShaderFunctionsFromShaderAssetBuilder2(
const RHI::ShaderPlatformInterface* shaderPlatformInterface, const AssetBuilderSDK::PlatformInfo& platformInfo,
const AzslCompiler& azslCompiler, const AZStd::string& shaderSourceFileFullPath,
const RPI::SupervariantIndex supervariantIndex,
AzslFunctions& functions)
{
auto functionsJsonPathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2(
shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(),
AZ::RPI::ShaderAssetSubId::IaJson);
if (!functionsJsonPathOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", functionsJsonPathOutcome.GetError().c_str());
return;
}
auto functionsJsonPath = functionsJsonPathOutcome.TakeValue();
auto jsonOutcome = JsonSerializationUtils::ReadJsonFile(functionsJsonPath);
if (!jsonOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", jsonOutcome.GetError().c_str());
return;
}
if (!azslCompiler.ParseIaPopulateFunctionData(jsonOutcome.GetValue(), functions))
{
functions.clear();
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to find shader functions.");
return;
}
}
// Returns the content of the hlsl file for the given supervariant as produced by ShaderAsssetBuilder2.
// In addition to the content it also returns the full path of the hlsl file in @hlslSourcePath.
static AZStd::string LoadHlslFileFromShaderAssetBuilder2(
const RHI::ShaderPlatformInterface* shaderPlatformInterface, const AssetBuilderSDK::PlatformInfo& platformInfo,
const AZStd::string& shaderSourceFileFullPath, const RPI::SupervariantIndex supervariantIndex, AZStd::string& hlslSourcePath)
{
auto hlslSourcePathOutcome = ShaderBuilderUtility::ObtainBuildArtifactPathFromShaderAssetBuilder2(
shaderPlatformInterface->GetAPIUniqueIndex(), platformInfo.m_identifier, shaderSourceFileFullPath, supervariantIndex.GetIndex(),
AZ::RPI::ShaderAssetSubId::GeneratedHlslSource);
if (!hlslSourcePathOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s", hlslSourcePathOutcome.GetError().c_str());
return "";
}
hlslSourcePath = hlslSourcePathOutcome.TakeValue();
Outcome<AZStd::string, AZStd::string> hlslSourceOutcome = Utils::ReadFile(hlslSourcePath);
if (!hlslSourceOutcome.IsSuccess())
{
AZ_Error(
ShaderVariantAssetBuilder2Name, false, "Failed to obtain shader source from %s. [%s]", hlslSourcePath.c_str(),
hlslSourceOutcome.TakeError().c_str());
return "";
}
return hlslSourceOutcome.TakeValue();
}
void ShaderVariantAssetBuilder2::ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
AZStd::string variantListFullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), variantListFullPath, true);
RPI::ShaderVariantListSourceData shaderVariantListDescriptor;
if (!RPI::JsonUtils::LoadObjectFromFile(variantListFullPath, shaderVariantListDescriptor))
{
AZ_Assert(false, "Failed to parse Shader Variant List Descriptor JSON [%s]", variantListFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
const AZStd::string& shaderSourceFileFullPath = request.m_jobDescription.m_jobParameters.at(ShaderSourceFilePathJobParam);
//For debugging purposes will create a dummy azshadervarianttree file.
AZStd::string shaderName;
AzFramework::StringFunc::Path::GetFileName(shaderSourceFileFullPath.c_str(), shaderName);
// No error checking because the same calls were already executed during CreateJobs()
auto descriptorParseOutcome = ShaderBuilderUtility::LoadShaderDataJson(shaderSourceFileFullPath);
RPI::ShaderSourceData shaderSourceDescriptor = descriptorParseOutcome.TakeValue();
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout;
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces =
ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(request.m_platformInfo, shaderSourceDescriptor);
if (platformInterfaces.empty())
{
// No work to do. Exit gracefully.
AZ_TracePrintf(
ShaderVariantAssetBuilder2Name,
"No azshadervarianttree is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
shaderSourceFileFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
// set the input file for eventual error messages, but the compiler won't be called on it.
AZStd::string azslFullPath;
ShaderBuilderUtility::GetAbsolutePathToAzslFile(shaderSourceFileFullPath, shaderSourceDescriptor.m_source, azslFullPath);
AzslCompiler azslc(azslFullPath);
AZStd::string previousLoopApiName;
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
auto thisLoopApiName = shaderPlatformInterface->GetAPIName().GetStringView();
RPI::Ptr<RPI::ShaderOptionGroupLayout> loopLocal_ShaderOptionGroupLayout =
LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2(
shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, RPI::DefaultSupervariantIndex);
if (!loopLocal_ShaderOptionGroupLayout)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (shaderOptionGroupLayout && shaderOptionGroupLayout->GetHash() != loopLocal_ShaderOptionGroupLayout->GetHash())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "There was a discrepancy in shader options between %s and %s", previousLoopApiName.c_str(), thisLoopApiName.data());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
shaderOptionGroupLayout = loopLocal_ShaderOptionGroupLayout;
previousLoopApiName = thisLoopApiName;
}
RPI::ShaderVariantTreeAssetCreator shaderVariantTreeAssetCreator;
shaderVariantTreeAssetCreator.Begin(Uuid::CreateRandom());
shaderVariantTreeAssetCreator.SetShaderOptionGroupLayout(*shaderOptionGroupLayout);
shaderVariantTreeAssetCreator.SetVariantInfos(shaderVariantListDescriptor.m_shaderVariants);
Data::Asset<RPI::ShaderVariantTreeAsset> shaderVariantTreeAsset;
if (!shaderVariantTreeAssetCreator.End(shaderVariantTreeAsset))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to build Shader Variant Tree Asset");
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AZStd::string filename = AZStd::string::format("%s.%s", shaderName.c_str(), RPI::ShaderVariantTreeAsset::Extension);
AZStd::string assetPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), filename.c_str(), assetPath, true);
if (!AZ::Utils::SaveObjectToFile(assetPath, AZ::DataStream::ST_BINARY, shaderVariantTreeAsset.Get()))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to save Shader Variant Tree Asset to \"%s\"", assetPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AssetBuilderSDK::JobProduct assetProduct;
assetProduct.m_productSubID = RPI::ShaderVariantTreeAsset::ProductSubID;
assetProduct.m_productFileName = assetPath;
assetProduct.m_productAssetType = azrtti_typeid<RPI::ShaderVariantTreeAsset>();
assetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output
response.m_outputProducts.push_back(assetProduct);
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Shader Variant Tree Asset [%s] compiled successfully.\n", assetPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
void ShaderVariantAssetBuilder2::ProcessShaderVariantJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
const AZStd::sys_time_t startTime = AZStd::GetTimeNowTicks();
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
AZStd::string fullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true);
const auto& jobParameters = request.m_jobDescription.m_jobParameters;
const AZStd::string& shaderSourceFileFullPath = jobParameters.at(ShaderSourceFilePathJobParam);
AZStd::string shaderFileName;
AzFramework::StringFunc::Path::GetFileName(shaderSourceFileFullPath.c_str(), shaderFileName);
const AZStd::string& variantJsonString = jobParameters.at(ShaderVariantJobVariantParam);
RPI::ShaderVariantListSourceData::VariantInfo variantInfo;
const bool fromJsonStringSuccess = AZ::RPI::JsonUtils::LoadObjectFromJsonString(variantJsonString, variantInfo);
AZ_Assert(fromJsonStringSuccess, "Failed to convert json string to VariantInfo");
RPI::ShaderSourceData shaderSourceDescriptor;
AZStd::shared_ptr<ShaderFiles> sources = ShaderBuilderUtility::PrepareSourceInput(ShaderVariantAssetBuilder2Name, shaderSourceFileFullPath, shaderSourceDescriptor);
// set the input file for eventual error messages, but the compiler won't be called on it.
AzslCompiler azslc(sources->m_azslSourceFullPath);
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces =
ShaderBuilderUtility::DiscoverEnabledShaderPlatformInterfaces(request.m_platformInfo, shaderSourceDescriptor);
if (platformInterfaces.empty())
{
// No work to do. Exit gracefully.
AZ_TracePrintf(ShaderVariantAssetBuilder2Name,
"No azshader is produced on behalf of %s because all valid RHI backends were disabled for this shader.\n",
shaderSourceFileFullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
return;
}
auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceDescriptor);
GlobalBuildOptions buildOptions = ReadBuildOptions(ShaderVariantAssetBuilder2Name);
// At this moment We have global build options that should be merged with the build options that are common
// to all the supervariants of this shader.
buildOptions.m_compilerArguments.Merge(shaderSourceDescriptor.m_compiler);
//! The ShaderOptionGroupLayout is common across all RHIs & Supervariants
RPI::Ptr<RPI::ShaderOptionGroupLayout> shaderOptionGroupLayout = nullptr;
// Generate shaders for each of those ShaderPlatformInterfaces.
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
AZ_TraceContext("ShaderPlatformInterface", shaderPlatformInterface->GetAPIName().GetCStr());
// Loop through all the Supervariants.
uint32_t supervariantIndexCounter = 0;
for (const auto& supervariantInfo : supervariantList)
{
RPI::SupervariantIndex supervariantIndex(supervariantIndexCounter);
// Check if we were canceled before we do any heavy processing of
// the shader variant data.
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
AZStd::string shaderStemNamePrefix = shaderFileName;
if (supervariantIndex.GetIndex() > 0)
{
shaderStemNamePrefix += supervariantInfo.m_name.GetStringView();
}
// We need these additional pieces of information To build a shader variant asset:
// 1- ShaderOptionsGroupLayout (Need to load it once, because it's the same acrosss all supervariants + RHIs)
// 2- entryFunctions
// 3- hlsl code.
// 1- ShaderOptionsGroupLayout
if (!shaderOptionGroupLayout)
{
shaderOptionGroupLayout =
LoadShaderOptionsGroupLayoutFromShaderAssetBuilder2(
shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex);
if (!shaderOptionGroupLayout)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
}
// 2- entryFunctions.
AzslFunctions azslFunctions;
LoadShaderFunctionsFromShaderAssetBuilder2(
shaderPlatformInterface, request.m_platformInfo, azslc, shaderSourceFileFullPath, supervariantIndex, azslFunctions);
if (azslFunctions.empty())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
MapOfStringToStageType shaderEntryPoints;
if (shaderSourceDescriptor.m_programSettings.m_entryPoints.empty())
{
AZ_TracePrintf(
ShaderVariantAssetBuilder2Name,
"ProgramSettings do not specify entry points, will use GetDefaultEntryPointsFromShader()\n");
ShaderBuilderUtility::GetDefaultEntryPointsFromFunctionDataList(azslFunctions, shaderEntryPoints);
}
else
{
for (const auto& entryPoint : shaderSourceDescriptor.m_programSettings.m_entryPoints)
{
shaderEntryPoints[entryPoint.m_name] = entryPoint.m_type;
}
}
// 3- hlslCode
AZStd::string hlslSourcePath;
AZStd::string hlslCode = LoadHlslFileFromShaderAssetBuilder2(
shaderPlatformInterface, request.m_platformInfo, shaderSourceFileFullPath, supervariantIndex, hlslSourcePath);
if (hlslCode.empty() || hlslSourcePath.empty())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// Setup the shader variant creation context:
ShaderVariantCreationContext2 shaderVariantCreationContext =
{
*shaderPlatformInterface, request.m_platformInfo, buildOptions.m_compilerArguments, request.m_tempDirPath,
startTime,
shaderSourceDescriptor,
*shaderOptionGroupLayout.get(),
shaderEntryPoints,
Uuid::CreateRandom(),
shaderStemNamePrefix,
hlslSourcePath, hlslCode
};
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts> outputByproducts;
auto shaderVariantAssetOutcome = CreateShaderVariantAsset(variantInfo, shaderVariantCreationContext, outputByproducts);
if (!shaderVariantAssetOutcome.IsSuccess())
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "%s\n", shaderVariantAssetOutcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
Data::Asset<RPI::ShaderVariantAsset2> shaderVariantAsset = shaderVariantAssetOutcome.TakeValue();
// Time to save the asset in the tmp folder so it ends up in the Cache folder.
const uint32_t productSubID = RPI::ShaderVariantAsset2::MakeAssetProductSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex.GetIndex(),
shaderVariantAsset->GetStableId());
AssetBuilderSDK::JobProduct assetProduct;
if (!SerializeOutShaderVariantAsset(shaderVariantAsset, shaderStemNamePrefix,
request.m_tempDirPath, *shaderPlatformInterface, productSubID,
assetProduct))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
response.m_outputProducts.push_back(assetProduct);
if (outputByproducts)
{
// add byproducts as job output products:
uint32_t subProductType = RPI::ShaderVariantAsset2::ShaderVariantAsset2SubProductType;
for (const AZStd::string& byproduct : outputByproducts.value().m_intermediatePaths)
{
AssetBuilderSDK::JobProduct jobProduct;
jobProduct.m_productFileName = byproduct;
jobProduct.m_productAssetType = Uuid::CreateName("DebugInfoByProduct-PdbOrDxilTxt");
jobProduct.m_productSubID = RPI::ShaderVariantAsset2::MakeAssetProductSubId(
shaderPlatformInterface->GetAPIUniqueIndex(), supervariantIndex.GetIndex(), shaderVariantAsset->GetStableId(),
subProductType++);
response.m_outputProducts.push_back(AZStd::move(jobProduct));
}
}
supervariantIndexCounter++;
} // End of supervariant for block
}
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
bool ShaderVariantAssetBuilder2::SerializeOutShaderVariantAsset(
const Data::Asset<RPI::ShaderVariantAsset2> shaderVariantAsset, const AZStd::string& shaderStemNamePrefix,
const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct)
{
AZStd::string filename = AZStd::string::format(
"%s_%s_%u.%s", shaderStemNamePrefix.c_str(), shaderPlatformInterface.GetAPIName().GetCStr(),
shaderVariantAsset->GetStableId().GetIndex(), RPI::ShaderVariantAsset2::Extension);
AZStd::string assetPath;
AzFramework::StringFunc::Path::ConstructFull(tempDirPath.c_str(), filename.c_str(), assetPath, true);
if (!AZ::Utils::SaveObjectToFile(assetPath, AZ::DataStream::ST_BINARY, shaderVariantAsset.Get()))
{
AZ_Error(ShaderVariantAssetBuilder2Name, false, "Failed to save Shader Variant Asset to \"%s\"", assetPath.c_str());
return false;
}
assetProduct.m_productSubID = productSubID;
assetProduct.m_productFileName = assetPath;
assetProduct.m_productAssetType = azrtti_typeid<RPI::ShaderVariantAsset2>();
assetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Shader Variant Asset [%s] compiled successfully.\n", assetPath.c_str());
return true;
}
AZ::Outcome<Data::Asset<RPI::ShaderVariantAsset2>, AZStd::string> ShaderVariantAssetBuilder2::CreateShaderVariantAsset(
const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo,
ShaderVariantCreationContext2& creationContext,
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts>& outputByproducts)
{
// Temporary structure used for sorting and caching intermediate results
struct OptionCache
{
AZ::Name m_optionName;
AZ::Name m_valueName;
RPI::ShaderOptionIndex m_optionIndex; // Cached m_optionName
RPI::ShaderOptionValue m_value; // Cached m_valueName
};
AZStd::vector<OptionCache> optionList;
// We can not have more options than the number of options in the layout:
optionList.reserve(creationContext.m_shaderOptionGroupLayout.GetShaderOptionCount());
// This loop will validate and cache the indices for each option value:
for (const auto& shaderOption : shaderVariantInfo.m_options)
{
Name optionName{shaderOption.first};
Name optionValue{shaderOption.second};
RPI::ShaderOptionIndex optionIndex = creationContext.m_shaderOptionGroupLayout.FindShaderOptionIndex(optionName);
if (optionIndex.IsNull())
{
return AZ::Failure(AZStd::string::format("Invalid shader option: %s", optionName.GetCStr()));
}
const RPI::ShaderOptionDescriptor& option = creationContext.m_shaderOptionGroupLayout.GetShaderOption(optionIndex);
RPI::ShaderOptionValue value = option.FindValue(optionValue);
if (value.IsNull())
{
return AZ::Failure(
AZStd::string::format("Invalid value (%s) for shader option: %s", optionValue.GetCStr(), optionName.GetCStr()));
}
optionList.push_back(OptionCache{optionName, optionValue, optionIndex, value});
}
// Create one instance of the shader variant
RPI::ShaderOptionGroup optionGroup(&creationContext.m_shaderOptionGroupLayout);
//! Contains the series of #define macro values that define a variant. Can be empty (root variant).
//! If this string is NOT empty, a new temporary hlsl file will be created that will be the combination
//! of this string + @m_hlslSourceContent.
AZStd::string hlslCodeToPrependForVariant;
// We want to go over all options listed in the variant and set their respective values
// This loop will populate the optionGroup and m_shaderCodePrefix in order of the option priority
for (const auto& optionCache : optionList)
{
const RPI::ShaderOptionDescriptor& option = creationContext.m_shaderOptionGroupLayout.GetShaderOption(optionCache.m_optionIndex);
// Assign the option value specified in the variant:
option.Set(optionGroup, optionCache.m_value);
// Populate all shader option defines. We have already confirmed they're valid.
hlslCodeToPrependForVariant += AZStd::string::format(
"#define %s_OPTION_DEF %s\n", optionCache.m_optionName.GetCStr(), optionCache.m_valueName.GetCStr());
}
AZStd::string variantShaderSourcePath;
// Check if we need to prepend any code prefix
if (!hlslCodeToPrependForVariant.empty())
{
// Prepend any shader code prefix that we should apply to this variant
// and save it back to a file.
AZStd::string variantShaderSourceString(hlslCodeToPrependForVariant);
variantShaderSourceString += creationContext.m_hlslSourceContent;
AZStd::string shaderAssetName = AZStd::string::format(
"%s_%s_%u.hlsl", creationContext.m_shaderStemNamePrefix.c_str(),
creationContext.m_shaderPlatformInterface.GetAPIName().GetCStr(), shaderVariantInfo.m_stableId);
AzFramework::StringFunc::Path::Join(
creationContext.m_tempDirPath.c_str(), shaderAssetName.c_str(), variantShaderSourcePath, true, true);
auto outcome = Utils::WriteFile(variantShaderSourceString, variantShaderSourcePath);
if (!outcome.IsSuccess())
{
return AZ::Failure(AZStd::string::format("Failed to create file %s", variantShaderSourcePath.c_str()));
}
}
else
{
variantShaderSourcePath = creationContext.m_hlslSourcePath;
}
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Variant StableId: %u", shaderVariantInfo.m_stableId);
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Variant Shader Options: %s", optionGroup.ToString().c_str());
const RPI::ShaderVariantStableId shaderVariantStableId{shaderVariantInfo.m_stableId};
// By this time the optionGroup was populated with all option values for the variant and
// the m_shaderCodePrefix contains all option related preprocessing macros
// Let's add the requested variant:
RPI::ShaderVariantAssetCreator2 variantCreator;
RPI::ShaderOptionGroup shaderOptions{&creationContext.m_shaderOptionGroupLayout, optionGroup.GetShaderVariantId()};
variantCreator.Begin(
creationContext.m_shaderVariantAssetId, optionGroup.GetShaderVariantId(), shaderVariantStableId,
shaderOptions.IsFullySpecified());
const AZStd::unordered_map<AZStd::string, RPI::ShaderStageType>& shaderEntryPoints = creationContext.m_shaderEntryPoints;
for (const auto& shaderEntryPoint : shaderEntryPoints)
{
auto shaderEntryName = shaderEntryPoint.first;
auto shaderStageType = shaderEntryPoint.second;
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Entry Point: %s", shaderEntryName.c_str());
AZ_TracePrintf(ShaderVariantAssetBuilder2Name, "Begin compiling shader function \"%s\"", shaderEntryName.c_str());
auto assetBuilderShaderType = ShaderBuilderUtility::ToAssetBuilderShaderType(shaderStageType);
// Compile HLSL to the platform specific shader.
RHI::ShaderPlatformInterface::StageDescriptor descriptor;
bool shaderWasCompiled = creationContext.m_shaderPlatformInterface.CompilePlatformInternal(
creationContext.m_platformInfo, variantShaderSourcePath, shaderEntryName, assetBuilderShaderType,
creationContext.m_tempDirPath, descriptor, creationContext.m_shaderCompilerArguments);
if (!shaderWasCompiled)
{
return AZ::Failure(AZStd::string::format("Could not compile the shader function %s", shaderEntryName.c_str()));
}
// bubble up the byproducts to the caller by moving them to the context.
outputByproducts.emplace(AZStd::move(descriptor.m_byProducts));
RHI::Ptr<RHI::ShaderStageFunction> shaderStageFunction = creationContext.m_shaderPlatformInterface.CreateShaderStageFunction(descriptor);
variantCreator.SetShaderFunction(ToRHIShaderStage(assetBuilderShaderType), shaderStageFunction);
if (descriptor.m_byProducts.m_dynamicBranchCount != AZ::RHI::ShaderPlatformInterface::ByProducts::UnknownDynamicBranchCount)
{
AZ_TracePrintf(
ShaderVariantAssetBuilder2Name, "Finished compiling shader function. Number of dynamic branches: %u",
descriptor.m_byProducts.m_dynamicBranchCount);
}
else
{
AZ_TracePrintf(
ShaderVariantAssetBuilder2Name, "Finished compiling shader function. Number of dynamic branches: unknown");
}
}
Data::Asset<RPI::ShaderVariantAsset2> shaderVariantAsset;
variantCreator.End(shaderVariantAsset);
return AZ::Success(AZStd::move(shaderVariantAsset));
}
} // ShaderBuilder
} // AZ
@@ -1,107 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <Atom/RHI.Reflect/Base.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Edit/Shader/ShaderSourceData.h>
#include <Atom/RPI.Edit/Shader/ShaderVariantListSourceData.h>
#include "ShaderBuilderUtility.h"
namespace AZ
{
namespace ShaderBuilder
{
struct AzslData;
//! This is nothing more than a class to help consolidate all
//! the data needed to generate a shader variant and prevent
//! all the functions involved in the process to have too many
//! arguments.
struct ShaderVariantCreationContext2
{
RHI::ShaderPlatformInterface& m_shaderPlatformInterface;
const AssetBuilderSDK::PlatformInfo& m_platformInfo;
const RHI::ShaderCompilerArguments& m_shaderCompilerArguments;
//! Used to write temporary files during shader compilation, like *.hlsl, or *.air, or *.metallib, etc.
const AZStd::string& m_tempDirPath;
//! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset,
//! especially during hot-reload. A (ShaderVariantAsset.timestamp) >= (ShaderAsset.timestamp).
const AZStd::sys_time_t m_assetBuildTimestamp;
const RPI::ShaderSourceData& m_shaderSourceDataDescriptor;
const RPI::ShaderOptionGroupLayout& m_shaderOptionGroupLayout;
const MapOfStringToStageType& m_shaderEntryPoints;
const Data::AssetId m_shaderVariantAssetId;
const AZStd::string& m_shaderStemNamePrefix; //<shaderName>-<supervariantName>
const AZStd::string& m_hlslSourcePath;
const AZStd::string& m_hlslSourceContent;
};
class ShaderVariantAssetBuilder2
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler
{
public:
AZ_TYPE_INFO(ShaderVariantAssetBuilder2, "{C959AEC2-2083-4488-AD88-F61B1144535B}");
static constexpr char ShaderVariantAssetBuilder2JobKey[] = "Shader Variant Asset 2";
ShaderVariantAssetBuilder2() = default;
~ShaderVariantAssetBuilder2() = default;
// Asset Builder Callback Functions ...
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const;
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
//! The ShaderVariantAsset returned by this function won't be written to the filesystem.
//! You should call SerializeOutShaderVariantAsset to write it to the temp folder assigned
//! by the asset processor.
static AZ::Outcome<Data::Asset<RPI::ShaderVariantAsset2>, AZStd::string> CreateShaderVariantAsset(
const RPI::ShaderVariantListSourceData::VariantInfo& shaderVariantInfo,
ShaderVariantCreationContext2& creationContext,
AZStd::optional<RHI::ShaderPlatformInterface::ByProducts>& outputByproducts);
static bool SerializeOutShaderVariantAsset(
const Data::Asset<RPI::ShaderVariantAsset2> shaderVariantAsset,
const AZStd::string& shaderStemNamePrefix, const AZStd::string& tempDirPath,
const RHI::ShaderPlatformInterface& shaderPlatformInterface, const uint32_t productSubID, AssetBuilderSDK::JobProduct& assetProduct);
// AssetBuilderSDK::AssetBuilderCommandBus interface overrides ...
void ShutDown() override { };
private:
AZ_DISABLE_COPY_MOVE(ShaderVariantAssetBuilder2);
static constexpr uint32_t ShaderVariantLoadErrorParam = 0;
static constexpr uint32_t ShaderSourceFilePathJobParam = 2;
static constexpr uint32_t ShaderVariantJobVariantParam = 3;
static constexpr uint32_t ShouldExitEarlyFromProcessJobParam = 4;
//! Called from ProcessJob when the job is supposed to create a ShaderVariantTreeAsset.
void ProcessShaderVariantTreeJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
//! Called from ProcessJob when the job is supposed to create ShaderVariantAssets. One ShaderVariantAsset will be produced per RHI::APIType
//! supported by the platform.
void ProcessShaderVariantJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
static AZStd::string GetShaderVariantTreeAssetJobKey() { return AZStd::string::format("%s_varianttree", ShaderVariantAssetBuilder2JobKey); }
static AZStd::string GetShaderVariantAssetJobKey(RPI::ShaderVariantStableId variantStableId) { return AZStd::string::format("%s_variant_%u", ShaderVariantAssetBuilder2JobKey, variantStableId.GetIndex()); }
};
} // ShaderBuilder
} // AZ
@@ -1,541 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <regex>
#include <Atom/RHI.Edit/ShaderPlatformInterface.h>
#include <Atom/RHI.Edit/Utils.h>
#include <Atom/RHI.Reflect/ShaderResourceGroupLayoutDescriptor.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAssetCreator.h>
#include <AtomCore/Serialization/Json/JsonUtils.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/IO/FileIO.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <Atom/RPI.Reflect/Shader/ShaderResourceGroupAsset.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzslData.h>
#include <AzslCompiler.h>
#include <CommonFiles/Preprocessor.h>
#include <CommonFiles/GlobalBuildOptions.h>
#include <SrgLayoutBuilder.h>
#include <ShaderPlatformInterfaceRequest.h>
#include <ShaderBuilderUtility.h>
namespace AZ
{
namespace ShaderBuilder
{
AZ::Uuid SrgLayoutBuilder::GetUUID()
{
return AZ::Uuid::CreateString("{ABC78905-B3FC-497A-916A-217D1460E52F}");
}
void SrgLayoutBuilder::Reflect([[maybe_unused]] AZ::ReflectContext* context)
{
}
SrgLayoutBuilder::SrgLayoutBuilder()
{
}
SrgLayoutBuilder::~SrgLayoutBuilder()
{
}
void SrgLayoutBuilder::Activate()
{
}
void SrgLayoutBuilder::Deactivate()
{
}
void SrgLayoutBuilder::ShutDown()
{
}
RHI::ShaderInputBufferType ToShaderInputBufferType(BufferType bufferType)
{
switch (bufferType)
{
case BufferType::Buffer:
case BufferType::RwBuffer:
case BufferType::RasterizerOrderedBuffer:
return RHI::ShaderInputBufferType::Typed;
case BufferType::AppendStructuredBuffer:
case BufferType::ConsumeStructuredBuffer:
case BufferType::RasterizerOrderedStructuredBuffer:
case BufferType::RwStructuredBuffer:
case BufferType::StructuredBuffer:
return RHI::ShaderInputBufferType::Structured;
case BufferType::RasterizerOrderedByteAddressBuffer:
case BufferType::ByteAddressBuffer:
case BufferType::RwByteAddressBuffer:
return RHI::ShaderInputBufferType::Raw;
case BufferType::RaytracingAccelerationStructure:
return RHI::ShaderInputBufferType::AccelerationStructure;
default:
AZ_Assert(false, "Unhandled BufferType");
return RHI::ShaderInputBufferType::Unknown;
}
}
RHI::ShaderInputBufferAccess ToShaderInputBufferAccess(BufferType bufferType)
{
switch (bufferType)
{
case BufferType::Buffer:
case BufferType::ByteAddressBuffer:
case BufferType::ConsumeStructuredBuffer:
case BufferType::StructuredBuffer:
return RHI::ShaderInputBufferAccess::Read;
case BufferType::AppendStructuredBuffer:
case BufferType::RasterizerOrderedStructuredBuffer:
case BufferType::RasterizerOrderedByteAddressBuffer:
case BufferType::RasterizerOrderedBuffer:
case BufferType::RwByteAddressBuffer:
case BufferType::RwStructuredBuffer:
case BufferType::RwBuffer:
return RHI::ShaderInputBufferAccess::ReadWrite;
default:
AZ_Assert(false, "Unhandled BufferType");
return RHI::ShaderInputBufferAccess::Read;
}
}
RHI::ShaderInputImageType ToShaderInputImageType(TextureType textureType)
{
switch (textureType)
{
case TextureType::Texture1D: return RHI::ShaderInputImageType::Image1D;
case TextureType::Texture1DArray: return RHI::ShaderInputImageType::Image1DArray;
case TextureType::Texture2D: return RHI::ShaderInputImageType::Image2D;
case TextureType::Texture2DArray: return RHI::ShaderInputImageType::Image2DArray;
case TextureType::Texture2DMS: return RHI::ShaderInputImageType::Image2DMultisample;
case TextureType::Texture2DMSArray: return RHI::ShaderInputImageType::Image2DMultisampleArray;
case TextureType::Texture3D: return RHI::ShaderInputImageType::Image3D;
case TextureType::TextureCube: return RHI::ShaderInputImageType::ImageCube;
case TextureType::RwTexture1D: return RHI::ShaderInputImageType::Image1D;
case TextureType::RwTexture1DArray: return RHI::ShaderInputImageType::Image1DArray;
case TextureType::RwTexture2D: return RHI::ShaderInputImageType::Image2D;
case TextureType::RwTexture2DArray: return RHI::ShaderInputImageType::Image2DArray;
case TextureType::RwTexture3D: return RHI::ShaderInputImageType::Image3D;
case TextureType::RasterizerOrderedTexture1D: return RHI::ShaderInputImageType::Image1D;
case TextureType::RasterizerOrderedTexture1DArray: return RHI::ShaderInputImageType::Image1DArray;
case TextureType::RasterizerOrderedTexture2D: return RHI::ShaderInputImageType::Image2D;
case TextureType::RasterizerOrderedTexture2DArray: return RHI::ShaderInputImageType::Image2DArray;
case TextureType::RasterizerOrderedTexture3D: return RHI::ShaderInputImageType::Image3D;
case TextureType::SubpassInput: return RHI::ShaderInputImageType::SubpassInput;
default:
AZ_Assert(false, "Unhandled TextureType");
return RHI::ShaderInputImageType::Unknown;
}
}
RHI::ShaderInputImageAccess ToShaderInputImageAccess(TextureType textureType)
{
switch (textureType)
{
case TextureType::Texture1D:
case TextureType::Texture1DArray:
case TextureType::Texture2D:
case TextureType::Texture2DArray:
case TextureType::Texture2DMS:
case TextureType::Texture2DMSArray:
case TextureType::Texture3D:
case TextureType::TextureCube:
return RHI::ShaderInputImageAccess::Read;
case TextureType::RwTexture1D:
case TextureType::RwTexture1DArray:
case TextureType::RwTexture2D:
case TextureType::RwTexture2DArray:
case TextureType::RwTexture3D:
case TextureType::RasterizerOrderedTexture1D:
case TextureType::RasterizerOrderedTexture1DArray:
case TextureType::RasterizerOrderedTexture2D:
case TextureType::RasterizerOrderedTexture2DArray:
case TextureType::RasterizerOrderedTexture3D:
return RHI::ShaderInputImageAccess::ReadWrite;
default:
AZ_Assert(false, "Unhandled TextureType");
return RHI::ShaderInputImageAccess::Read;
}
}
void SrgLayoutBuilder::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const
{
AZStd::string fullPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, false);
AzFramework::StringFunc::Path::Normalize(fullPath);
AZ_TracePrintf(SrgLayoutBuilderName, "CreateJobs for Srg Layouts \"%s\"\n", fullPath.data());
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
AssetBuilderSDK::JobDescriptor jobDescriptor;
jobDescriptor.m_priority = 2;
// [GFX TODO][ATOM-2830] Set 'm_critical' back to 'false' once proper fix for Atom startup issues are in
jobDescriptor.m_critical = true;
jobDescriptor.m_jobKey = SrgLayoutBuilderJobKey;
jobDescriptor.SetPlatformIdentifier(info.m_identifier.data());
// Get the platform interfaces to be able to access the prepend file
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces = ShaderBuilderUtility::DiscoverValidShaderPlatformInterfaces(info);
// queue up AzslBuilder dependencies:
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
AddAzslBuilderJobDependency(jobDescriptor, info.m_identifier, shaderPlatformInterface->GetAPIName().GetCStr(), fullPath);
}
response.m_createJobOutputs.push_back(jobDescriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
}
void SrgLayoutBuilder::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const
{
AZStd::string sourcePath;
AzFramework::StringFunc::Path::ConstructFull(request.m_watchFolder.c_str(), request.m_sourceFile.c_str(), sourcePath, false);
AzFramework::StringFunc::Path::Normalize(sourcePath);
if (!AzFramework::StringFunc::Path::IsExtension(sourcePath.c_str(), MergedPartialSrgsExtension)) // not .srgi
{
auto skipCheck = ShaderBuilderUtility::ShouldSkipFileForSrgProcessing(SrgLayoutBuilderName, sourcePath);
if (skipCheck != ShaderBuilderUtility::SrgSkipFileResult::ContinueProcess)
{
response.m_resultCode = skipCheck == ShaderBuilderUtility::SrgSkipFileResult::Error ?
AssetBuilderSDK::ProcessJobResult_Failed : AssetBuilderSDK::ProcessJobResult_Success;
return;
}
}
AZ_TracePrintf(SrgLayoutBuilderName, "Processing Shader Resource Group \"%s\".\n", sourcePath.c_str());
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
// Note this will update response.m_resultCode
CreateSRGAsset(sourcePath, request, response, jobCancelListener);
AZ_TracePrintf(SrgLayoutBuilderName, "Finished processing %s\n", sourcePath.c_str());
}
void SrgLayoutBuilder::CreateSRGAsset(
AZStd::string fullSourcePath,
const AssetBuilderSDK::ProcessJobRequest& request,
AssetBuilderSDK::ProcessJobResponse& response,
AssetBuilderSDK::JobCancelListener& jobCancelListener)
{
// Request the list of valid shader platform interfaces for the target platform.
AZStd::vector<RHI::ShaderPlatformInterface*> platformInterfaces;
ShaderPlatformInterfaceRequestBus::BroadcastResult(platformInterfaces, &ShaderPlatformInterfaceRequest::GetShaderPlatformInterface, request.m_platformInfo);
if (platformInterfaces.empty())
{
AZ_Error(SrgLayoutBuilderName, false, "No ShaderPlatformInterfaces found.");
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
using SrgDataEntry = AZStd::pair<RHI::ShaderPlatformInterface*, SrgData>;
// List with all SRGs that need to be processed.
AZStd::unordered_map<AZStd::string, AZStd::vector<SrgDataEntry>> srgsToProcess;
// Emit all SRGs per each of the platform interfaces.
for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces)
{
if (!shaderPlatformInterface)
{
AZ_Error(SrgLayoutBuilderName, false, "ShaderPlatformInterface for [%s] is not registered, can't compile [%s]", request.m_platformInfo.m_identifier.c_str(), request.m_sourceFile.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
SrgDataContainer srgDataContainer;
auto azslArtifactsOutcome = ShaderBuilderUtility::ObtainBuildArtifactsFromAzslBuilder(SrgLayoutBuilderName, fullSourcePath, shaderPlatformInterface->GetAPIType(), request.m_platformInfo.m_identifier);
if (!azslArtifactsOutcome.IsSuccess())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
// create an AzslCompiler instance to use it's json parsing facilities, but we won't call any emit facility. So the compiler actually never runs.
AzslCompiler azslc(azslArtifactsOutcome.GetValue()[ShaderBuilderUtility::AzslSubProducts::azslin]); // set the input file for eventual error messages.
AZ::Outcome<rapidjson::Document, AZStd::string> outcome;
outcome = JsonSerializationUtils::ReadJsonFile(azslArtifactsOutcome.GetValue()[ShaderBuilderUtility::AzslSubProducts::srg]);
if (!outcome.IsSuccess())
{
AZ_Error(SrgLayoutBuilderName, false, "%s", outcome.GetError().c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (!azslc.ParseSrgPopulateSrgData(outcome.GetValue(), srgDataContainer))
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
for (const SrgData& srgData : srgDataContainer)
{
// Ignore the SRGs included from other files.
AZStd::string normalizedContainer = srgData.m_containingFileName;
AzFramework::StringFunc::Path::Normalize(normalizedContainer);
if (normalizedContainer != fullSourcePath)
{
AZ_TracePrintf(SrgLayoutBuilderName, "SRG [%s] found in [%s] but is foreign to [%s]. skipped.",
srgData.m_name.c_str(), normalizedContainer.c_str(), fullSourcePath.c_str());
continue;
}
AZ_TracePrintf(SrgLayoutBuilderName, "SRG [%s] found in [%s] (native to this file). added.",
srgData.m_name.c_str(), normalizedContainer.c_str());
srgsToProcess[srgData.m_name].push_back(SrgDataEntry(shaderPlatformInterface, AZStd::move(srgData)));
}
}
AZStd::string fileNameOnly;
AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), fileNameOnly);
if (srgsToProcess.empty())
{
AZ_TracePrintf(SrgLayoutBuilderName, "No ShaderResourceGroups found in '%s'.", fullSourcePath.c_str());
}
// Process all SRGs that were emitted.
for (const auto& entry : srgsToProcess)
{
const auto& srgName = entry.first;
if (jobCancelListener.IsCancelled())
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
AZStd::string fullFileName = fileNameOnly + "_" + srgName;
AZStd::string shaderResourceGroupAssetPath;
AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), fullFileName.c_str(), shaderResourceGroupAssetPath, true);
AzFramework::StringFunc::Path::ReplaceExtension(shaderResourceGroupAssetPath, "azsrg");
RPI::ShaderResourceGroupAssetCreator srgAssetCreator;
srgAssetCreator.Begin(Uuid::CreateRandom(), Name{ srgName });
bool success = true;
// Process the SRG per each shader platform interface.
for(const SrgDataEntry& srgDataEntry : entry.second)
{
RHI::ShaderPlatformInterface* shaderPlatformInterface = srgDataEntry.first;
// The register number only makes sense if the platform uses "spaces",
// since the register Id of the resource will not change even if the pipeline layout changes.
// We can pass in a default ShaderCompilerArguments because all we care about is whether the shaderPlatformInterface
// appends the
// "--use-spaces" flag.
AZStd::string azslCompilerParameters =
shaderPlatformInterface->GetAzslCompilerParameters(RHI::ShaderCompilerArguments{});
bool useRegisterId = (AzFramework::StringFunc::Find(azslCompilerParameters, "--use-spaces") != AZStd::string::npos);
const SrgData& srgData = srgDataEntry.second;
srgAssetCreator.BeginAPI(shaderPlatformInterface->GetAPIType());
srgAssetCreator.SetBindingSlot(srgData.m_bindingSlot.m_index);
// Samplers
for (const SamplerSrgData& samplerData : srgData.m_samplers)
{
if (samplerData.m_isDynamic)
{
srgAssetCreator.AddShaderInput({
samplerData.m_nameId,
samplerData.m_count,
useRegisterId ? samplerData.m_registerId : RHI::UndefinedRegisterSlot });
}
else
{
srgAssetCreator.AddStaticSampler({
samplerData.m_nameId,
samplerData.m_descriptor,
useRegisterId ? samplerData.m_registerId : RHI::UndefinedRegisterSlot });
}
}
// Images
for (const TextureSrgData& textureData : srgData.m_textures)
{
const RHI::ShaderInputImageAccess imageAccess =
textureData.m_isReadOnlyType ?
RHI::ShaderInputImageAccess::Read :
RHI::ShaderInputImageAccess::ReadWrite;
const RHI::ShaderInputImageType imageType = ToShaderInputImageType(textureData.m_type);
if (imageType != RHI::ShaderInputImageType::Unknown)
{
if (textureData.m_count != aznumeric_cast<uint32_t>(-1))
{
srgAssetCreator.AddShaderInput({
textureData.m_nameId,
imageAccess,
imageType,
textureData.m_count,
useRegisterId ? textureData.m_registerId : RHI::UndefinedRegisterSlot });
}
else
{
// unbounded array
srgAssetCreator.AddShaderInput({
textureData.m_nameId,
imageAccess,
imageType,
useRegisterId ? textureData.m_registerId : RHI::UndefinedRegisterSlot });
}
}
else
{
AZ_Error(SrgLayoutBuilderName, false, "Failed to build Shader Resource Group Asset: Image %s has an unknown type.", textureData.m_nameId.GetCStr());
success = false;
}
}
// Buffers
{
for (const ConstantBufferData& cbData : srgData.m_constantBuffers)
{
srgAssetCreator.AddShaderInput({
cbData.m_nameId,
RHI::ShaderInputBufferAccess::Constant,
RHI::ShaderInputBufferType::Constant,
cbData.m_count,
cbData.m_strideSize,
useRegisterId ? cbData.m_registerId : RHI::UndefinedRegisterSlot });
}
for (const BufferSrgData& bufferData : srgData.m_buffers)
{
const RHI::ShaderInputBufferAccess bufferAccess =
bufferData.m_isReadOnlyType ?
RHI::ShaderInputBufferAccess::Read :
RHI::ShaderInputBufferAccess::ReadWrite;
const RHI::ShaderInputBufferType bufferType = ToShaderInputBufferType(bufferData.m_type);
if (bufferType != RHI::ShaderInputBufferType::Unknown)
{
if (bufferData.m_count != aznumeric_cast<uint32_t>(-1))
{
srgAssetCreator.AddShaderInput({
bufferData.m_nameId,
bufferAccess,
bufferType,
bufferData.m_count,
bufferData.m_strideSize,
useRegisterId ? bufferData.m_registerId : RHI::UndefinedRegisterSlot });
}
else
{
// unbounded array
srgAssetCreator.AddShaderInput({
bufferData.m_nameId,
bufferAccess,
bufferType,
bufferData.m_strideSize,
useRegisterId ? bufferData.m_registerId : RHI::UndefinedRegisterSlot });
}
}
else
{
AZ_Error(SrgLayoutBuilderName, false, "Failed to build Shader Resource Group Asset: Buffer %s has un unknown type.", bufferData.m_nameId.GetCStr());
success = false;
}
}
}
// SRG Constants
uint32_t constantDataRegisterId = useRegisterId ? srgData.m_srgConstantDataRegisterId : RHI::UndefinedRegisterSlot;
for (const SrgConstantData& srgConstants : srgData.m_srgConstantData)
{
srgAssetCreator.AddShaderInput({
srgConstants.m_nameId,
srgConstants.m_constantByteOffset,
srgConstants.m_constantByteSize,
constantDataRegisterId });
}
// Shader Variant Key fallback
if (srgData.m_fallbackSize > 0)
{
// Designates this SRG as a ShaderVariantKey fallback
srgAssetCreator.SetShaderVariantKeyFallback(srgData.m_fallbackName, srgData.m_fallbackSize);
}
if (!srgAssetCreator.EndAPI())
{
AZ_Error(SrgLayoutBuilderName, false, "Failed to End API.");
success = false;
}
if (!success)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
}
Data::Asset<RPI::ShaderResourceGroupAsset> shaderResourceGroupAsset;
if (!srgAssetCreator.End(shaderResourceGroupAsset))
{
AZ_Error(SrgLayoutBuilderName, false, "Failed to build Shader Resource Group Asset");
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
if (AZ::IO::FileIOBase::GetInstance()->Exists(shaderResourceGroupAssetPath.c_str()))
{
// This would indicate a problem above, making sure each product SRG asset file path is unique.
AZ_Error(SrgLayoutBuilderName, false, "Cannot overwrite existing file [%s]. This likely indicates conflicting SRG names.", shaderResourceGroupAssetPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
success = AZ::Utils::SaveObjectToFile(shaderResourceGroupAssetPath, AZ::DataStream::ST_JSON, shaderResourceGroupAsset.Get());
if (!success)
{
AZ_Error(SrgLayoutBuilderName, false, "Failed to save Shader Resource Group Asset to \"%s\"", shaderResourceGroupAssetPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
return;
}
AssetBuilderSDK::JobProduct srgAssetProduct;
srgAssetProduct.m_productSubID = static_cast<uint32_t>(AZStd::hash<AZStd::string>()(srgName) & 0xFFFFFFFF);
srgAssetProduct.m_productFileName = shaderResourceGroupAssetPath;
srgAssetProduct.m_productAssetType = azrtti_typeid<RPI::ShaderResourceGroupAsset>();
srgAssetProduct.m_dependenciesHandled = true; // This builder has no dependencies to output
response.m_outputProducts.push_back(srgAssetProduct);
AZ_TracePrintf(SrgLayoutBuilderName, "Shader Resource Group Asset compiled successfully.\n");
}
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
} // namespace ShaderBuilder
} // namespace AZ
@@ -1,83 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <CommonFiles/CommonTypes.h>
#include <AzslData.h>
#include "AzslBuilder.h"
namespace AZ
{
namespace Data
{
class AssetHandler;
}
namespace ShaderBuilder
{
struct SrgData;
struct TypeIdPair;
class SrgLayoutBuilder
: public AssetBuilderSDK::AssetBuilderCommandBus::Handler
{
public:
SrgLayoutBuilder();
~SrgLayoutBuilder();
// An *.srgi file is nothing more than a regular azsl file that simply includes a set of srg/azsli
// files, and in turn each one of those included files define "partial ShaderResourceGroup"s, which are
// merged into a single ShaderResourceGroup by the shader compiler.
// So, *.srgi are supposed to include files that only define "partial" SRGs.
// And any file that defines a ShaderResourceGroup it should not be "partial" unless it is supposed
// to be included by a *.srgi file.
static constexpr const char* MergedPartialSrgsExtension = AzslBuilder::SrgIncludeExtension;
static constexpr const char* SrgLayoutBuilderName = "SrgLayoutBuilder";
static constexpr const char* SrgLayoutBuilderJobKey = "Shader Resource Group Layout";
// Asset Builder Callback Functions
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) const;
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) const;
//////////////////////////////////////////////////////////////////////////
// AssetBuilderSDK::AssetBuilderCommandBus interface
void ShutDown() override;
//////////////////////////////////////////////////////////////////////////
void Activate();
void Deactivate();
static AZ::Uuid GetUUID();
static void Reflect(AZ::ReflectContext* context);
private:
AZ_DISABLE_COPY_MOVE(SrgLayoutBuilder);
static void CreateSRGAsset(
AZStd::string fullSourcePath,
const AssetBuilderSDK::ProcessJobRequest& request,
AssetBuilderSDK::ProcessJobResponse& response,
AssetBuilderSDK::JobCancelListener& jobCancelListener);
};
} // ShaderBuilder
} // AZ
@@ -109,6 +109,7 @@ namespace AZ
{
RHI::Ptr<RHI::ShaderResourceGroupLayout> newSrgLayout = RHI::ShaderResourceGroupLayout::Create();
newSrgLayout->SetName(AZ::Name{srgData.m_name.c_str()});
newSrgLayout->SetUniqueId(srgData.m_containingFileName);
newSrgLayout->SetBindingSlot(srgData.m_bindingSlot.m_index);
// Samplers
@@ -15,7 +15,7 @@
#include <AzCore/base.h>
#include "CommonFiles/CommonTypes.h"
#include <Atom/RPI.Reflect/Shader/ShaderAsset2.h>
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include "ShaderBuilderUtility.h"
namespace AZ
@@ -21,27 +21,19 @@ set(FILES
Source/Editor/AzslData.h
Source/Editor/AzslShaderBuilderSystemComponent.cpp
Source/Editor/AzslShaderBuilderSystemComponent.h
Source/Editor/AzslBuilder.cpp
Source/Editor/AzslBuilder.h
Source/Editor/ShaderAssetBuilder.cpp
Source/Editor/ShaderAssetBuilder.h
Source/Editor/ShaderBuilderUtility.cpp
Source/Editor/ShaderBuilderUtility.h
Source/Editor/ShaderPlatformInterfaceRequest.h
Source/Editor/SrgLayoutBuilder.cpp
Source/Editor/SrgLayoutBuilder.h
Source/Editor/AzslCompiler.cpp
Source/Editor/AzslCompiler.h
Source/Editor/ShaderVariantAssetBuilder.cpp
Source/Editor/ShaderVariantAssetBuilder.h
Source/Editor/ShaderVariantAssetBuilder2.cpp
Source/Editor/ShaderVariantAssetBuilder2.h
Source/Editor/AtomShaderConfig.cpp
Source/Editor/AtomShaderConfig.h
Source/Editor/PrecompiledShaderBuilder.cpp
Source/Editor/PrecompiledShaderBuilder.h
Source/Editor/ShaderAssetBuilder2.cpp
Source/Editor/ShaderAssetBuilder2.h
Source/Editor/SrgLayoutUtility.cpp
Source/Editor/SrgLayoutUtility.h
)
@@ -206,8 +206,8 @@
"$type": "RasterPassData",
"DrawListTag": "forward",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "Shaders/ForwardPassSrg.shader"
}
}
},
@@ -158,8 +158,8 @@
"$type": "RasterPassData",
"DrawListTag": "lowEndForward",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "Shaders/ForwardPassSrg.shader"
}
}
},
@@ -122,8 +122,8 @@
"$type": "RasterPassData",
"DrawListTag": "forward",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "Shaders/ForwardPassSrg.shader"
}
}
},
@@ -222,8 +222,8 @@
"$type": "RasterPassData",
"DrawListTag": "forwardWithSubsurfaceOutput",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "Shaders/ForwardPassSrg.shader"
}
}
},
@@ -89,8 +89,8 @@
"$type": "RasterPassData",
"DrawListTag": "reflectionprobeblendweight",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaders/reflections/reflectionprobeblendweight.azsl:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "shaders/reflections/reflectionprobeblendweight.shader"
}
}
},
@@ -203,8 +203,8 @@
"$type": "RasterPassData",
"DrawListTag": "reflectionproberenderouter",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaders/reflections/reflectionproberenderouter.azsl:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "shaders/reflections/reflectionproberenderouter.shader"
}
}
},
@@ -253,8 +253,8 @@
"$type": "RasterPassData",
"DrawListTag": "reflectionproberenderinner",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaders/reflections/reflectionproberenderinner.azsl:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "shaders/reflections/reflectionproberenderinner.shader"
}
}
},
@@ -84,8 +84,8 @@
"$type": "RasterPassData",
"DrawListTag": "reflectionprobeblendweight",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaders/reflections/reflectionprobeblendweight.azsl:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "shaders/reflections/reflectionprobeblendweight.shader"
}
}
},
@@ -191,8 +191,8 @@
"$type": "RasterPassData",
"DrawListTag": "reflectionproberenderouter",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaders/reflections/reflectionproberenderouter.azsl:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "shaders/reflections/reflectionproberenderouter.shader"
}
}
},
@@ -241,8 +241,8 @@
"$type": "RasterPassData",
"DrawListTag": "reflectionproberenderinner",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaders/reflections/reflectionproberenderinner.azsl:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "shaders/reflections/reflectionproberenderinner.shader"
}
}
},
@@ -123,8 +123,8 @@
"DrawListTag": "transparent",
"DrawListSortType": "KeyThenReverseDepth",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg"
"PassSrgShaderAsset": {
"FilePath": "Shaders/ForwardPassSrg.shader"
}
}
}
@@ -0,0 +1,19 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// This shader is not used for rendering.
// The only purpose of this shader is to have a shader asset that can be used
// at runtime to find the RayTracingSceneSrg and RayTracingMaterialSrg layouts.
#include <Atom/Features/RayTracing/RayTracingSceneSrg.azsli>
#include <Atom/Features/RayTracing/RayTracingMaterialSrg.azsli>
#include <Atom/RPI/DummyEntryFunctions.azsli>
@@ -0,0 +1,44 @@
{
"Source" : "RayTracingSrgs.azsl",
"CompilerHints":
{
"DxcAdditionalFreeArguments" : "-fspv-target-env=vulkan1.2"
},
"DepthStencilState" :
{
"Depth" :
{
"Enable" : false
},
"Stencil" :
{
"Enable" : false
}
},
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
},
"Supervariants":
[
{
"Name": "",
"PlusArguments": "",
"MinusArguments": "--strip-unused-srgs"
}
]
}
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridblenddistance_passsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridblendirradiance_passsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridborderupdate_passsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridborderupdate_passsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridclassification_passsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridraytracingcommon_raytracingglobalsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridraytracingcommon_raytracingglobalsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridraytracingcommon_raytracingglobalsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,10 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridrelocation_passsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -9,11 +9,6 @@
[
"pc"
],
"ShaderResourceGroupAssets":
[
"diffuseprobegridrender_passsrg.azsrg",
"diffuseprobegridrender_objectsrg.azsrg"
],
"RootShaderVariantAssets":
[
{
@@ -0,0 +1,18 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// This shader is not used for rendering.
// The only purpose of this shader is to have a shader asset that can be used
// at runtime to find the PassSrg required by most Passes.
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
#include <Atom/RPI/DummyEntryFunctions.azsli>
@@ -0,0 +1,39 @@
{
"Source" : "ForwardPassSrg.azsl",
"DepthStencilState" :
{
"Depth" :
{
"Enable" : false
},
"Stencil" :
{
"Enable" : false
}
},
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
},
"Supervariants":
[
{
"Name": "",
"PlusArguments": "",
"MinusArguments": "--strip-unused-srgs"
}
]
}
@@ -20,7 +20,7 @@
#include <scenesrg.srgi>
#include <viewsrg.srgi>
ShaderResourceGroup PassSrg : SRG_PerDraw
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
Texture2D<float4> m_colorAndDofFactorTexture;
float m_radiusMin;
@@ -16,7 +16,7 @@
#include <viewsrg.srgi>
ShaderResourceGroup PassSrg : SRG_PerDraw
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
Texture2D<float4> m_InputColor;
Texture2D<float4> m_InputDepth;
@@ -18,7 +18,7 @@
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
#include "EyeAdaptationUtil.azsli"
ShaderResourceGroup PassSrg : SRG_PerDraw
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
Texture2D<float4> m_framebuffer;
Sampler LinearSampler
@@ -17,7 +17,7 @@
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
#include <Atom/Features/PostProcessing/Aces.azsli>
ShaderResourceGroup PassSrg : SRG_PerDraw
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
Texture2D<float4> m_framebuffer;
Sampler LinearSampler
@@ -25,7 +25,7 @@ struct VSOutputBlendingWeightCalculation
float4 m_offset[3] : TEXCOORD2;
};
ShaderResourceGroup PassSrg : SRG_PerDraw
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
Texture2D<float4> m_framebuffer;
Texture2D<float4> m_areaTexture;
@@ -24,7 +24,7 @@ struct VSOutputSMAAEdgeDetection
float4 m_offset[3] : TEXCOORD1;
};
ShaderResourceGroup PassSrg : SRG_PerDraw
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
Texture2D<float4> m_framebuffer;
Texture2D<float4> m_depthTexture;
@@ -25,7 +25,7 @@ struct VSOutputNeighborhoodBlending
float4 m_offset : TEXCOORD1;
};
ShaderResourceGroup PassSrg : SRG_PerDraw
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
{
Texture2D<float4> m_framebuffer;
Texture2D<float4> m_framebufferPassThrough;

Some files were not shown because too many files have changed in this diff Show More