246 lines
7.7 KiB
C++
246 lines
7.7 KiB
C++
// Copyright (c) Wojciech Figat. All rights reserved.
|
|
|
|
#include "GPUShader.h"
|
|
#include "GPUConstantBuffer.h"
|
|
#include "Engine/Core/Log.h"
|
|
#include "Engine/Core/Math/Math.h"
|
|
#include "Engine/Core/Types/Span.h"
|
|
#include "Engine/Graphics/GPUDevice.h"
|
|
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
|
#include "Engine/Serialization/MemoryReadStream.h"
|
|
#include "Engine/Profiler/ProfilerMemory.h"
|
|
|
|
static FORCE_INLINE uint32 HashPermutation(const StringAnsiView& name, int32 permutationIndex)
|
|
{
|
|
return GetHash(name) * 37 + permutationIndex;
|
|
}
|
|
|
|
void GPUShaderProgram::Init(const GPUShaderProgramInitializer& initializer)
|
|
{
|
|
_name = initializer.Name;
|
|
_bindings = initializer.Bindings;
|
|
_flags = initializer.Flags;
|
|
#if !BUILD_RELEASE
|
|
_owner = initializer.Owner;
|
|
#endif
|
|
}
|
|
|
|
#if !BUILD_RELEASE
|
|
|
|
void GPUShaderProgram::GetDebugName(DebugName& name) const
|
|
{
|
|
StringView ownerName = StringUtils::GetFileNameWithoutExtension(_owner->GetName());
|
|
name.AddUninitialized(ownerName.Length() + _name.Length() + 2);
|
|
char* dst = name.Get();
|
|
for (int32 i = 0; i < ownerName.Length(); i++)
|
|
dst[i] = (char)ownerName.Get()[i];
|
|
dst += ownerName.Length();
|
|
*dst = ':';
|
|
dst++;
|
|
for (int32 i = 0; i < _name.Length(); i++)
|
|
dst[i] = _name.Get()[i];
|
|
dst[_name.Length()] = 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
GPUShader::GPUShader()
|
|
: GPUResource(SpawnParams(Guid::New(), TypeInitializer))
|
|
{
|
|
Platform::MemoryClear(_constantBuffers, sizeof(_constantBuffers));
|
|
}
|
|
|
|
bool GPUShader::Create(MemoryReadStream& stream)
|
|
{
|
|
ReleaseGPU();
|
|
_memoryUsage = sizeof(GPUShader);
|
|
|
|
// Version
|
|
int32 version;
|
|
stream.ReadInt32(&version);
|
|
if (version != GPU_SHADER_CACHE_VERSION)
|
|
{
|
|
LOG(Warning, "Unsupported shader version {0}. The current version is {1}.", version, GPU_SHADER_CACHE_VERSION);
|
|
return true;
|
|
}
|
|
|
|
// Additional data start
|
|
int32 additionalDataStart;
|
|
stream.ReadInt32(&additionalDataStart);
|
|
|
|
// Shaders count
|
|
int32 shadersCount;
|
|
stream.ReadInt32(&shadersCount);
|
|
GPUShaderProgramInitializer initializer;
|
|
#if !BUILD_RELEASE
|
|
initializer.Owner = this;
|
|
const StringView name = GetName();
|
|
#else
|
|
const StringView name;
|
|
#endif
|
|
const bool hasCompute = GPUDevice::Instance->Limits.HasCompute;
|
|
for (int32 i = 0; i < shadersCount; i++)
|
|
{
|
|
const ShaderStage type = static_cast<ShaderStage>(stream.ReadByte());
|
|
const int32 permutationsCount = stream.ReadByte();
|
|
ASSERT(Math::IsInRange(permutationsCount, 1, SHADER_PERMUTATIONS_MAX_COUNT));
|
|
|
|
// Load shader name
|
|
stream.Read(initializer.Name, 11);
|
|
ASSERT(initializer.Name.HasChars());
|
|
|
|
// Load shader flags
|
|
stream.ReadUint32((uint32*)&initializer.Flags);
|
|
|
|
for (int32 permutationIndex = 0; permutationIndex < permutationsCount; permutationIndex++)
|
|
{
|
|
// Load bytecode
|
|
uint32 bytecodeSize;
|
|
stream.ReadUint32(&bytecodeSize);
|
|
if (bytecodeSize > stream.GetLength() - stream.GetPosition())
|
|
{
|
|
LOG(Warning, "Invalid shader bytecode size.");
|
|
return true;
|
|
}
|
|
byte* bytecode = stream.Move<byte>(bytecodeSize);
|
|
|
|
// Read bindings
|
|
stream.ReadBytes(&initializer.Bindings, sizeof(ShaderBindings));
|
|
|
|
// Create shader program
|
|
if (type == ShaderStage::Compute && !hasCompute)
|
|
{
|
|
LOG(Warning, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
|
|
continue;
|
|
}
|
|
GPUShaderProgram* shader = CreateGPUShaderProgram(type, initializer, Span<byte>(bytecode, bytecodeSize), stream);
|
|
if (shader == nullptr)
|
|
{
|
|
#if !GPU_ALLOW_TESSELLATION_SHADERS
|
|
if (type == ShaderStage::Hull || type == ShaderStage::Domain)
|
|
continue;
|
|
#endif
|
|
#if !GPU_ALLOW_GEOMETRY_SHADERS
|
|
if (type == ShaderStage::Geometry)
|
|
continue;
|
|
#endif
|
|
LOG(Error, "Failed to create {} Shader program '{}' ({}).", ::ToString(type), String(initializer.Name), name);
|
|
return true;
|
|
}
|
|
|
|
// Add to the collection
|
|
const uint32 hash = HashPermutation(shader->GetName(), permutationIndex);
|
|
ASSERT_LOW_LAYER(!_shaders.ContainsKey(hash));
|
|
_shaders.Add(hash, shader);
|
|
_memoryUsage += sizeof(GPUShaderProgram) + bytecodeSize;
|
|
}
|
|
}
|
|
|
|
// Constant Buffers
|
|
const byte constantBuffersCount = stream.ReadByte();
|
|
for (int32 i = 0; i < constantBuffersCount; i++)
|
|
{
|
|
// Load info
|
|
const byte slotIndex = stream.ReadByte();
|
|
if (slotIndex >= GPU_MAX_CB_BINDED)
|
|
{
|
|
LOG(Warning, "Failed to create shader constant buffer.");
|
|
return true;
|
|
}
|
|
uint32 size;
|
|
stream.ReadUint32(&size);
|
|
|
|
// Create CB
|
|
#if GPU_ENABLE_RESOURCE_NAMING
|
|
String cbName = String::Format(TEXT("{}.CB{}"), ToString(), i);
|
|
#else
|
|
String cbName;
|
|
#endif
|
|
ASSERT(_constantBuffers[slotIndex] == nullptr);
|
|
const auto cb = GPUDevice::Instance->CreateConstantBuffer(size, cbName);
|
|
if (cb == nullptr)
|
|
{
|
|
LOG(Warning, "Failed to create shader constant buffer.");
|
|
return true;
|
|
}
|
|
_constantBuffers[slotIndex] = cb;
|
|
_memoryUsage += sizeof(GPUConstantBuffer);
|
|
}
|
|
|
|
// Don't read additional data
|
|
|
|
PROFILE_MEM_INC(GraphicsShaders, _memoryUsage);
|
|
return false;
|
|
}
|
|
|
|
bool GPUShader::HasShader(const StringAnsiView& name, int32 permutationIndex) const
|
|
{
|
|
const uint32 hash = HashPermutation(name, permutationIndex);
|
|
return _shaders.ContainsKey(hash);
|
|
}
|
|
|
|
GPUShaderProgram* GPUShader::GetShader(ShaderStage stage, const StringAnsiView& name, int32 permutationIndex) const
|
|
{
|
|
GPUShaderProgram* shader = nullptr;
|
|
const uint32 hash = HashPermutation(name, permutationIndex);
|
|
_shaders.TryGet(hash, shader);
|
|
#if BUILD_RELEASE
|
|
// Release build is more critical on that
|
|
ASSERT(shader != nullptr && shader->GetStage() == stage);
|
|
#else
|
|
if (shader == nullptr)
|
|
{
|
|
LOG(Error, "Missing {0} shader \'{1}\'[{2}]. Object: {3}.", ::ToString(stage), String(name), permutationIndex, ToString());
|
|
}
|
|
else if (shader->GetStage() != stage)
|
|
{
|
|
LOG(Error, "Invalid shader stage \'{1}\'[{2}]. Expected: {0}. Actual: {4}. Object: {3}.", ::ToString(stage), String(name), permutationIndex, ToString(), ::ToString(shader->GetStage()));
|
|
}
|
|
#endif
|
|
return shader;
|
|
}
|
|
|
|
void GPUShader::ReadVertexLayout(MemoryReadStream& stream, GPUVertexLayout*& inputLayout, GPUVertexLayout*& vertexLayout)
|
|
{
|
|
inputLayout = vertexLayout = nullptr;
|
|
|
|
// Read input layout (based on shader reflection)
|
|
GPUVertexLayout::Elements elements;
|
|
stream.Read(elements);
|
|
inputLayout = GPUVertexLayout::Get(elements);
|
|
|
|
// [Deprecated in v1.10]
|
|
byte inputLayoutSize;
|
|
stream.ReadByte(&inputLayoutSize);
|
|
if (inputLayoutSize == 0)
|
|
return;
|
|
void* elementsData = stream.Move(sizeof(VertexElement) * inputLayoutSize);
|
|
if (inputLayoutSize > GPU_MAX_VS_ELEMENTS)
|
|
{
|
|
LOG(Error, "Incorrect input layout size.");
|
|
return;
|
|
}
|
|
elements.Set((VertexElement*)elementsData, inputLayoutSize);
|
|
vertexLayout = GPUVertexLayout::Get(elements);
|
|
}
|
|
|
|
GPUResourceType GPUShader::GetResourceType() const
|
|
{
|
|
return GPUResourceType::Shader;
|
|
}
|
|
|
|
void GPUShader::OnReleaseGPU()
|
|
{
|
|
PROFILE_MEM_DEC(GraphicsShaders, _memoryUsage);
|
|
for (GPUConstantBuffer*& cb : _constantBuffers)
|
|
{
|
|
if (cb)
|
|
{
|
|
SAFE_DELETE_GPU_RESOURCE(cb);
|
|
cb = nullptr;
|
|
}
|
|
}
|
|
_memoryUsage = 0;
|
|
_shaders.ClearDelete();
|
|
}
|