Animation WIP merge #321
|
@ -0,0 +1,8 @@
|
|||
- VertexShader: 47911992
|
||||
FragmentShader: 46377769
|
||||
SubPass: G-Buffer Write
|
||||
Properties:
|
||||
data.color: {x: 1, y: 1, z: 1, w: 1}
|
||||
data.textureIndex: 58303057
|
||||
data.alpha: 0
|
||||
data.beta: {x: 1, y: 1, z: 1}
|
|
@ -0,0 +1,3 @@
|
|||
Name: AnimatedBag
|
||||
ID: 117923942
|
||||
Type: 7
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -67,7 +67,7 @@ namespace SHADE
|
|||
/*---------------------------------------------------------------------------------*/
|
||||
const std::vector<Channel>& GetChannels() const noexcept { return channels; }
|
||||
int GetTicksPerSecond() const noexcept { return ticksPerSecond; }
|
||||
float GetTotalTime() const noexcept { return totalTime; }
|
||||
float GetTotalTime() const noexcept { return totalTime/(float)ticksPerSecond; }
|
||||
|
||||
private:
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
|
|
|
@ -7,25 +7,7 @@ namespace SHADE
|
|||
{
|
||||
SHRigAsset::~SHRigAsset()
|
||||
{
|
||||
if (root == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::queue<SHRigNode*> nodeQueue;
|
||||
nodeQueue.push(root);
|
||||
|
||||
while(!nodeQueue.empty())
|
||||
{
|
||||
auto curr = nodeQueue.front();
|
||||
nodeQueue.pop();
|
||||
|
||||
for (auto child : curr->children)
|
||||
{
|
||||
nodeQueue.push(child);
|
||||
}
|
||||
|
||||
delete curr;
|
||||
}
|
||||
if (root != nullptr)
|
||||
delete[] root;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,8 +176,8 @@ namespace SHADE
|
|||
std::queue<std::pair<SHRigNode*, NodeTemp*>> nodeQueue;
|
||||
nodeQueue.emplace(std::make_pair(nodePool, dst));
|
||||
|
||||
auto depthPtr = nodePool + 1;
|
||||
auto depthTempPtr = dst + 1;
|
||||
SHRigNode* depthPtr = nodePool + 1;
|
||||
NodeTemp* depthTempPtr = dst + 1;
|
||||
|
||||
while(!nodeQueue.empty())
|
||||
{
|
||||
|
@ -239,16 +239,19 @@ namespace SHADE
|
|||
bone.name.resize(info.charCount);
|
||||
file.read(bone.name.data(), info.charCount);
|
||||
file.read(reinterpret_cast<char*>(&bone.offset), sizeof(SHMatrix));
|
||||
|
||||
uint32_t weightCount;
|
||||
file.read(reinterpret_cast<char*>(&weightCount), sizeof(uint32_t));
|
||||
bone.weights.resize(weightCount);
|
||||
file.read(reinterpret_cast<char*>(bone.weights.data()), sizeof(BoneWeight) * weightCount);
|
||||
|
||||
bone.weights.resize(info.weightCount);
|
||||
file.read(reinterpret_cast<char*>(bone.weights.data()), sizeof(BoneWeight) * info.weightCount);
|
||||
}
|
||||
|
||||
data.VertexBoneIndices.resize(header.vertexCount);
|
||||
data.VertexBoneWeights.resize(header.vertexCount);
|
||||
|
||||
//for (auto& weight : data.VertexBoneWeights)
|
||||
//{
|
||||
// weight = { -0.1f };
|
||||
//}
|
||||
|
||||
for (uint32_t boneIndex{0}; boneIndex < bones.size(); ++boneIndex)
|
||||
{
|
||||
auto const& bone = bones[boneIndex];
|
||||
|
@ -263,6 +266,7 @@ namespace SHADE
|
|||
{
|
||||
boneIndices[j] = boneIndex;
|
||||
boneWeight[j] = weight.weight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,40 +37,50 @@ namespace SHADE
|
|||
/* SHBatch - Constructors/Destructors */
|
||||
/*-----------------------------------------------------------------------------------*/
|
||||
SHBatch::SHBatch(Handle<SHVkPipeline> pipeline)
|
||||
: pipeline{ pipeline }
|
||||
: pipeline{ pipeline }
|
||||
{
|
||||
if (!pipeline)
|
||||
throw std::invalid_argument("Attempted to create a SHBatch with an invalid SHPipeline!");
|
||||
|
||||
// Check the pipeline and flag it depending on whether or not it is animated
|
||||
isAnimated = checkIfIsAnimatedPipeline(pipeline);
|
||||
|
||||
// Mark all as dirty
|
||||
setAllDirtyFlags();
|
||||
}
|
||||
|
||||
SHBatch::SHBatch(SHBatch&& rhs)
|
||||
: device { rhs.device }
|
||||
, pipeline { rhs.pipeline }
|
||||
, referencedMatInstances { std::move(rhs.referencedMatInstances) }
|
||||
, matBufferDirty { std::move(rhs.matBufferDirty) }
|
||||
, subBatches { std::move(rhs.subBatches) }
|
||||
, isDirty { std::move(rhs.isDirty) }
|
||||
, drawData { std::move(rhs.drawData) }
|
||||
, transformData { std::move(rhs.transformData) }
|
||||
, instancedIntegerData { std::move(rhs.instancedIntegerData) }
|
||||
, matPropsData { std::move(rhs.matPropsData) }
|
||||
, matPropsDataSize { rhs.matPropsDataSize }
|
||||
, singleMatPropAlignedSize { rhs.singleMatPropAlignedSize }
|
||||
, singleMatPropSize { rhs.singleMatPropSize }
|
||||
, isCPUBuffersDirty { rhs.isCPUBuffersDirty }
|
||||
, drawDataBuffer { rhs.drawDataBuffer }
|
||||
, transformDataBuffer { rhs.transformDataBuffer }
|
||||
, instancedIntegerBuffer { rhs.instancedIntegerBuffer }
|
||||
, matPropsBuffer { rhs.matPropsBuffer }
|
||||
, instanceDataDescSet { rhs.instanceDataDescSet }
|
||||
: device { rhs.device }
|
||||
, isAnimated { rhs.isAnimated }
|
||||
, pipeline { rhs.pipeline }
|
||||
, referencedMatInstances { std::move(rhs.referencedMatInstances) }
|
||||
, matBufferDirty { std::move(rhs.matBufferDirty) }
|
||||
, subBatches { std::move(rhs.subBatches) }
|
||||
, isDirty { std::move(rhs.isDirty) }
|
||||
, drawData { std::move(rhs.drawData) }
|
||||
, transformData { std::move(rhs.transformData) }
|
||||
, instancedIntegerData { std::move(rhs.instancedIntegerData) }
|
||||
, matPropsData { std::move(rhs.matPropsData) }
|
||||
, matPropsDataSize { rhs.matPropsDataSize }
|
||||
, singleMatPropAlignedSize { rhs.singleMatPropAlignedSize }
|
||||
, singleMatPropSize { rhs.singleMatPropSize }
|
||||
, boneMatrixData { std::move(rhs.boneMatrixData) }
|
||||
, boneMatrixIndices { std::move(rhs.boneMatrixIndices) }
|
||||
, isCPUBuffersDirty { rhs.isCPUBuffersDirty }
|
||||
, drawDataBuffer { rhs.drawDataBuffer }
|
||||
, transformDataBuffer { rhs.transformDataBuffer }
|
||||
, instancedIntegerBuffer { rhs.instancedIntegerBuffer }
|
||||
, matPropsBuffer { rhs.matPropsBuffer }
|
||||
, boneMatrixBuffer { rhs.boneMatrixBuffer }
|
||||
, boneMatrixFirstIndexBuffer { rhs.boneMatrixFirstIndexBuffer }
|
||||
, instanceDataDescSet { rhs.instanceDataDescSet }
|
||||
{
|
||||
rhs.drawDataBuffer = {};
|
||||
rhs.transformDataBuffer = {};
|
||||
rhs.instancedIntegerBuffer = {};
|
||||
rhs.matPropsBuffer = {};
|
||||
rhs.drawDataBuffer = {};
|
||||
rhs.transformDataBuffer = {};
|
||||
rhs.instancedIntegerBuffer = {};
|
||||
rhs.matPropsBuffer = {};
|
||||
rhs.boneMatrixBuffer = {};
|
||||
rhs.boneMatrixFirstIndexBuffer = {};
|
||||
rhs.instanceDataDescSet = {};
|
||||
}
|
||||
|
||||
|
@ -79,31 +89,38 @@ namespace SHADE
|
|||
if (this == &rhs)
|
||||
return *this;
|
||||
|
||||
device = rhs.device ;
|
||||
pipeline = rhs.pipeline ;
|
||||
referencedMatInstances = std::move(rhs.referencedMatInstances);
|
||||
matBufferDirty = std::move(rhs.matBufferDirty) ;
|
||||
subBatches = std::move(rhs.subBatches) ;
|
||||
isDirty = std::move(rhs.isDirty) ;
|
||||
drawData = std::move(rhs.drawData) ;
|
||||
transformData = std::move(rhs.transformData) ;
|
||||
instancedIntegerData = std::move(rhs.instancedIntegerData) ;
|
||||
matPropsData = std::move(rhs.matPropsData) ;
|
||||
matPropsDataSize = rhs.matPropsDataSize ;
|
||||
singleMatPropAlignedSize = rhs.singleMatPropAlignedSize ;
|
||||
singleMatPropSize = rhs.singleMatPropSize ;
|
||||
isCPUBuffersDirty = rhs.isCPUBuffersDirty ;
|
||||
drawDataBuffer = rhs.drawDataBuffer ;
|
||||
transformDataBuffer = rhs.transformDataBuffer ;
|
||||
instancedIntegerBuffer = rhs.instancedIntegerBuffer ;
|
||||
matPropsBuffer = rhs.matPropsBuffer ;
|
||||
instanceDataDescSet = rhs.instanceDataDescSet ;
|
||||
device = rhs.device ;
|
||||
isAnimated = rhs.isAnimated ;
|
||||
pipeline = rhs.pipeline ;
|
||||
referencedMatInstances = std::move(rhs.referencedMatInstances);
|
||||
matBufferDirty = std::move(rhs.matBufferDirty) ;
|
||||
subBatches = std::move(rhs.subBatches) ;
|
||||
isDirty = std::move(rhs.isDirty) ;
|
||||
drawData = std::move(rhs.drawData) ;
|
||||
transformData = std::move(rhs.transformData) ;
|
||||
instancedIntegerData = std::move(rhs.instancedIntegerData) ;
|
||||
matPropsData = std::move(rhs.matPropsData) ;
|
||||
matPropsDataSize = rhs.matPropsDataSize ;
|
||||
singleMatPropAlignedSize = rhs.singleMatPropAlignedSize ;
|
||||
singleMatPropSize = rhs.singleMatPropSize ;
|
||||
boneMatrixData = std::move(rhs.boneMatrixData) ;
|
||||
boneMatrixIndices = std::move(rhs.boneMatrixIndices) ;
|
||||
isCPUBuffersDirty = rhs.isCPUBuffersDirty ;
|
||||
drawDataBuffer = rhs.drawDataBuffer ;
|
||||
transformDataBuffer = rhs.transformDataBuffer ;
|
||||
instancedIntegerBuffer = rhs.instancedIntegerBuffer ;
|
||||
matPropsBuffer = rhs.matPropsBuffer ;
|
||||
boneMatrixBuffer = rhs.boneMatrixBuffer ;
|
||||
boneMatrixFirstIndexBuffer = rhs.boneMatrixFirstIndexBuffer ;
|
||||
instanceDataDescSet = rhs.instanceDataDescSet ;
|
||||
|
||||
// Unset values
|
||||
rhs.drawDataBuffer = {};
|
||||
rhs.transformDataBuffer = {};
|
||||
rhs.instancedIntegerBuffer = {};
|
||||
rhs.matPropsBuffer = {};
|
||||
rhs.drawDataBuffer = {};
|
||||
rhs.transformDataBuffer = {};
|
||||
rhs.instancedIntegerBuffer = {};
|
||||
rhs.matPropsBuffer = {};
|
||||
rhs.boneMatrixBuffer = {};
|
||||
rhs.boneMatrixFirstIndexBuffer = {};
|
||||
rhs.instanceDataDescSet = {};
|
||||
|
||||
return *this;
|
||||
|
@ -363,25 +380,27 @@ namespace SHADE
|
|||
|
||||
// Populate on the CPU
|
||||
for (auto& subBatch : subBatches)
|
||||
for (auto rendId : subBatch.Renderables)
|
||||
for (auto rendId : subBatch.Renderables)
|
||||
{
|
||||
auto* renderable = SHComponentManager::GetComponent<SHRenderable>(rendId);
|
||||
instancedIntegerData.emplace_back(SHInstancedIntegerData
|
||||
{
|
||||
auto* renderable = SHComponentManager::GetComponent<SHRenderable>(rendId);
|
||||
instancedIntegerData.emplace_back(SHInstancedIntegerData
|
||||
{
|
||||
rendId,
|
||||
renderable->GetLightLayer()
|
||||
}
|
||||
);
|
||||
}
|
||||
rendId,
|
||||
renderable->GetLightLayer()
|
||||
});
|
||||
}
|
||||
|
||||
// Transfer to GPU
|
||||
if (instancedIntegerBuffer[frameIndex] && !drawData.empty())
|
||||
instancedIntegerBuffer[frameIndex]->WriteToMemory(instancedIntegerData.data(), static_cast<uint32_t>(instancedIntegerData.size() * sizeof(SHInstancedIntegerData)), 0, 0);
|
||||
|
||||
}
|
||||
|
||||
void SHBatch::UpdateAnimationBuffer(uint32_t frameIndex)
|
||||
{
|
||||
// Ignore if not animated batch
|
||||
if (!isAnimated)
|
||||
return;
|
||||
|
||||
// Frame Index check
|
||||
if (frameIndex >= SHGraphicsConstants::NUM_FRAME_BUFFERS)
|
||||
{
|
||||
|
@ -392,6 +411,9 @@ namespace SHADE
|
|||
// Reset Animation Matrix Data
|
||||
boneMatrixData.clear();
|
||||
|
||||
// Add the first identity matrix into the bone matrix data
|
||||
boneMatrixData.emplace_back(SHMatrix::Identity); // This kills the GPU
|
||||
|
||||
// Populate on the CPU
|
||||
for (auto& subBatch : subBatches)
|
||||
for (auto rendId : subBatch.Renderables)
|
||||
|
@ -402,6 +424,7 @@ namespace SHADE
|
|||
const auto& MATRICES = animator->GetBoneMatrices();
|
||||
boneMatrixData.insert(boneMatrixData.end(), MATRICES.cbegin(), MATRICES.cend());
|
||||
}
|
||||
// We don't have to account for missing animators or reset indices as the renderable list are not updated at this point
|
||||
}
|
||||
|
||||
// Update GPU Buffers
|
||||
|
@ -446,10 +469,19 @@ namespace SHADE
|
|||
// - EID data
|
||||
instancedIntegerData.reserve(numTotalElements);
|
||||
instancedIntegerData.clear();
|
||||
// - Bone Data
|
||||
if (isAnimated)
|
||||
{
|
||||
boneMatrixData.clear();
|
||||
boneMatrixIndices.clear();
|
||||
boneMatrixIndices.reserve(numTotalElements);
|
||||
|
||||
auto const& descMappings = SHGraphicsPredefinedData::GetMappings(SHGraphicsPredefinedData::SystemType::BATCHING);
|
||||
// Add the first identity matrix into the bone matrix data
|
||||
boneMatrixData.emplace_back(SHMatrix::Identity);
|
||||
}
|
||||
|
||||
// - Material Properties Data
|
||||
auto const& descMappings = SHGraphicsPredefinedData::GetMappings(SHGraphicsPredefinedData::SystemType::BATCHING);
|
||||
const Handle<SHShaderBlockInterface> SHADER_INFO = pipeline->GetPipelineLayout()->GetShaderBlockInterface
|
||||
(
|
||||
descMappings.at(SHPredefinedDescriptorTypes::PER_INSTANCE_BATCH),
|
||||
|
@ -469,10 +501,6 @@ namespace SHADE
|
|||
matPropsDataSize = matPropTotalBytes;
|
||||
}
|
||||
}
|
||||
// - Bone Data
|
||||
boneMatrixData.clear();
|
||||
boneMatrixIndices.clear();
|
||||
boneMatrixIndices.reserve(numTotalElements);
|
||||
|
||||
// Build Sub Batches
|
||||
uint32_t nextInstanceIndex = 0;
|
||||
|
@ -548,12 +576,20 @@ namespace SHADE
|
|||
}
|
||||
|
||||
// Bone Data
|
||||
auto animator = SHComponentManager::GetComponent_s<SHAnimatorComponent>(rendId);
|
||||
if (animator)
|
||||
if (isAnimated)
|
||||
{
|
||||
boneMatrixIndices.emplace_back(static_cast<uint32_t>(boneMatrixData.size()));
|
||||
const auto& BONE_MATRICES = animator->GetBoneMatrices();
|
||||
boneMatrixData.insert(boneMatrixData.end(), BONE_MATRICES.cbegin(), BONE_MATRICES.cend());
|
||||
auto animator = SHComponentManager::GetComponent_s<SHAnimatorComponent>(rendId);
|
||||
if (animator)
|
||||
{
|
||||
boneMatrixIndices.emplace_back(static_cast<uint32_t>(boneMatrixData.size()));
|
||||
const auto& BONE_MATRICES = animator->GetBoneMatrices();
|
||||
boneMatrixData.insert(boneMatrixData.end(), BONE_MATRICES.cbegin(), BONE_MATRICES.cend());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Take the first matrix which is always identity
|
||||
boneMatrixIndices.emplace_back(static_cast<uint32_t>(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -590,7 +626,7 @@ namespace SHADE
|
|||
"Batch Instance Data Buffer"
|
||||
);
|
||||
// - Bone Matrix Indices
|
||||
if (!boneMatrixIndices.empty())
|
||||
if (isAnimated && !boneMatrixIndices.empty())
|
||||
{
|
||||
const uint32_t BMI_DATA_BYTES = static_cast<uint32_t>(boneMatrixIndices.size() * sizeof(uint32_t));
|
||||
SHVkUtil::EnsureBufferAndCopyHostVisibleData
|
||||
|
@ -625,7 +661,7 @@ namespace SHADE
|
|||
|
||||
// Bind all required objects before drawing
|
||||
std::vector<uint32_t> dynamicOffset{ 0 };
|
||||
if (!boneMatrixData.empty())
|
||||
if (isAnimated && !boneMatrixData.empty())
|
||||
{
|
||||
dynamicOffset.emplace_back(0);
|
||||
}
|
||||
|
@ -633,7 +669,7 @@ namespace SHADE
|
|||
cmdBuffer->BindPipeline(pipeline);
|
||||
cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::TRANSFORM, transformDataBuffer[frameIndex], 0);
|
||||
cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::INTEGER_DATA, instancedIntegerBuffer[frameIndex], 0);
|
||||
if (boneMatrixFirstIndexBuffer[frameIndex])
|
||||
if (isAnimated && boneMatrixFirstIndexBuffer[frameIndex])
|
||||
{
|
||||
cmdBuffer->BindVertexBuffer(SHGraphicsConstants::VertexBufferBindings::BONE_MATRIX_FIRST_INDEX, boneMatrixFirstIndexBuffer[frameIndex], 0);
|
||||
}
|
||||
|
@ -706,7 +742,9 @@ namespace SHADE
|
|||
layoutTypes = PreDefDescLayoutType::MATERIAL_AND_BONES;
|
||||
}
|
||||
|
||||
if (matPropsData || !boneMatrixData.empty())
|
||||
const bool MUST_BUILD_BONE_DESC = isAnimated && !boneMatrixData.empty();
|
||||
|
||||
if (matPropsData || MUST_BUILD_BONE_DESC)
|
||||
{
|
||||
// Make sure that we have a descriptor set if we don't already have one
|
||||
if (!instanceDataDescSet[frameIndex])
|
||||
|
@ -756,7 +794,7 @@ namespace SHADE
|
|||
}
|
||||
|
||||
/* Animation Bone Data */
|
||||
if (!boneMatrixData.empty())
|
||||
if (MUST_BUILD_BONE_DESC)
|
||||
{
|
||||
// Update GPU Buffers
|
||||
const uint32_t BONE_MTX_DATA_BYTES = static_cast<uint32_t>(boneMatrixData.size() * sizeof(SHMatrix));
|
||||
|
@ -786,4 +824,22 @@ namespace SHADE
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool SHBatch::checkIfIsAnimatedPipeline(Handle<SHVkPipeline> pipeline)
|
||||
{
|
||||
if (!pipeline || !pipeline->GetPipelineLayout())
|
||||
return false;
|
||||
|
||||
// Grab the pipeline descriptor set layouts
|
||||
auto pipelineDescLayouts = pipeline->GetPipelineLayout()->GetDescriptorSetLayoutsPipeline();
|
||||
|
||||
// Check if they contain the material and bones layout, that indicates it is
|
||||
using GfxPreDef = SHGraphicsPredefinedData;
|
||||
using GfxPreDefType = GfxPreDef::PredefinedDescSetLayoutTypes;
|
||||
const Handle<SHVkDescriptorSetLayout> BONE_DESC_SET_LAYOUT = GfxPreDef::GetPredefinedDescSetLayouts(GfxPreDefType::MATERIAL_AND_BONES)[0];
|
||||
return std::find_if(pipelineDescLayouts.begin(), pipelineDescLayouts.end(), [BONE_DESC_SET_LAYOUT](Handle<SHVkDescriptorSetLayout> layout)
|
||||
{
|
||||
return BONE_DESC_SET_LAYOUT == layout;
|
||||
}) != pipelineDescLayouts.end();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ namespace SHADE
|
|||
bool IsEmpty() const noexcept { return subBatches.empty(); }
|
||||
Handle<SHVkBuffer> GetTransformBuffer(uint32_t frameIndex) const noexcept;
|
||||
Handle<SHVkBuffer> GetMDIBuffer(uint32_t frameIndex) const noexcept;
|
||||
bool IsAnimated() const noexcept { return isAnimated; }
|
||||
|
||||
private:
|
||||
/*---------------------------------------------------------------------------------*/
|
||||
|
@ -111,6 +112,8 @@ namespace SHADE
|
|||
/*---------------------------------------------------------------------------------*/
|
||||
// Resources
|
||||
Handle<SHVkLogicalDevice> device;
|
||||
// Config
|
||||
bool isAnimated; // Whether the material supports animation
|
||||
// Batch Properties
|
||||
Handle<SHVkPipeline> pipeline;
|
||||
std::unordered_set<Handle<SHMaterialInstance>> referencedMatInstances;
|
||||
|
@ -126,7 +129,7 @@ namespace SHADE
|
|||
Byte matPropsDataSize = 0;
|
||||
Byte singleMatPropAlignedSize = 0;
|
||||
Byte singleMatPropSize = 0;
|
||||
std::vector<SHMatrix> boneMatrixData;
|
||||
std::vector<SHMatrix> boneMatrixData; // 0th element is always an identity matrix
|
||||
std::vector<uint32_t> boneMatrixIndices;
|
||||
bool isCPUBuffersDirty = true;
|
||||
// GPU Buffers
|
||||
|
@ -143,5 +146,6 @@ namespace SHADE
|
|||
/*-----------------------------------------------------------------------------*/
|
||||
void setAllDirtyFlags();
|
||||
void rebuildDescriptorSetBuffers(uint32_t frameIndex, Handle<SHVkDescriptorPool> descPool);
|
||||
static bool checkIfIsAnimatedPipeline(Handle<SHVkPipeline> pipeline);
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue