Updated alias algorithm

This commit is contained in:
2025-12-02 10:33:21 +09:00
parent 676f8bb74c
commit 3af1d8c3bd
2 changed files with 292 additions and 9 deletions

View File

@@ -40,10 +40,13 @@ internal class ResourceAllocator
Console.WriteLine("\n[RG] ===== RESOURCE ALIASING ANALYSIS =====");
// Separate imported and transient resources
// Sort by SIZE FIRST (descending), then by FIRST USE (ascending)
// This allows smaller resources (A, B) to alias into larger resources' (C) space
// Example: C=10MB[1..2], A=4MB[0..1], B=6MB[0..1] → Allocate C first, then A and B alias into C's space
var transientResources = resourceLifetimes
.Where(lt => !lt.Handle.IsImported && lt.FirstUse != int.MaxValue)
.OrderBy(lt => lt.FirstUse)
.ThenByDescending(lt => GetResourceSize(lt.Handle))
.OrderByDescending(lt => GetResourceSize(lt.Handle))
.ThenBy(lt => lt.FirstUse)
.ToList();
if (!transientResources.Any())
@@ -76,27 +79,34 @@ internal class ResourceAllocator
if (reuseSlot != null)
{
// Reuse existing allocation
reuseSlot.AddResource(resource);
// Reuse existing allocation - find offset within the allocation
ulong offsetInAllocation = reuseSlot.FindFreeOffset(size, alignment, resource);
reuseSlot.AddResource(resource, offsetInAllocation, size);
Console.WriteLine($"[ALIAS] '{resource.Handle.Name}' aliases with '{reuseSlot.Allocation.DebugName}' " +
$"(offset: {reuseSlot.Allocation.OffsetInBytes}, size: {size} bytes, " +
$"(heap offset: {reuseSlot.Allocation.OffsetInBytes}, resource offset: {offsetInAllocation}, size: {size} bytes, " +
$"lifetime: [{resource.FirstUse}..{resource.LastUse}])");
}
else
{
// Create new allocation
// Calculate heap offset (simulated - in real D3D12MA this would be the actual heap offset)
ulong heapOffset = allocationSlots.Count > 0
? allocationSlots.Max(s => s.Allocation.OffsetInBytes + s.Allocation.SizeInBytes)
: 0;
var allocation = new PhysicalResourceAllocation(
_allocationIdCounter++,
size,
offsetInBytes: 0, // In a real implementation, this would be a heap offset
offsetInBytes: heapOffset,
$"Physical_{resource.Handle.Type}_{_allocationIdCounter}");
var newSlot = new AllocationSlot(allocation, resource.Handle.Type);
newSlot.AddResource(resource);
newSlot.AddResource(resource, 0, size); // Offset 0 within this new allocation
allocationSlots.Add(newSlot);
Console.WriteLine($"[ALLOC] '{resource.Handle.Name}' gets new allocation '{allocation.DebugName}' " +
$"(size: {size} bytes, lifetime: [{resource.FirstUse}..{resource.LastUse}])");
$"(heap offset: {heapOffset}, size: {size} bytes, lifetime: [{resource.FirstUse}..{resource.LastUse}])");
}
}
@@ -197,6 +207,9 @@ internal class ResourceAllocator
public PhysicalResourceAllocation Allocation { get; }
public ResourceType ResourceType { get; }
public List<ResourceLifetime> Resources { get; } = new();
// Track occupied regions within this allocation: (offset, size, lifetime)
private readonly List<(ulong Offset, ulong Size, ResourceLifetime Resource)> _occupiedRegions = new();
public AllocationSlot(PhysicalResourceAllocation allocation, ResourceType resourceType)
{
@@ -204,9 +217,119 @@ internal class ResourceAllocator
ResourceType = resourceType;
}
public void AddResource(ResourceLifetime resource)
/// <summary>
/// Find a free offset within this allocation that can fit the required size
/// and doesn't conflict with active resources
/// </summary>
public ulong FindFreeOffset(ulong requiredSize, ulong alignment, ResourceLifetime newResource)
{
// If no resources yet, return 0
if (_occupiedRegions.Count == 0)
{
return 0;
}
// Sort regions by offset
var sortedRegions = _occupiedRegions.OrderBy(r => r.Offset).ToList();
// Try to fit at the beginning (offset 0)
ulong candidateOffset = 0;
bool fitsAtStart = true;
foreach (var region in sortedRegions)
{
// Check if this region overlaps with our candidate position
if (region.Offset < requiredSize)
{
// Check lifetime - if no overlap, we can still use this space
if (LifetimesOverlap(region.Resource, newResource))
{
fitsAtStart = false;
break;
}
}
}
if (fitsAtStart)
{
return AlignUp(0, alignment);
}
// Try gaps between regions
for (int i = 0; i < sortedRegions.Count; i++)
{
var current = sortedRegions[i];
// Skip if current region's lifetime overlaps with new resource
if (LifetimesOverlap(current.Resource, newResource))
{
continue;
}
// Try placing after this region
candidateOffset = AlignUp(current.Offset + current.Size, alignment);
// Check if it fits before the next region (or end of allocation)
ulong nextRegionStart = (i + 1 < sortedRegions.Count)
? sortedRegions[i + 1].Offset
: Allocation.SizeInBytes;
if (candidateOffset + requiredSize <= nextRegionStart)
{
// Check no lifetime conflicts with any regions in this range
bool hasConflict = false;
for (int j = i + 1; j < sortedRegions.Count; j++)
{
var other = sortedRegions[j];
if (other.Offset < candidateOffset + requiredSize)
{
if (LifetimesOverlap(other.Resource, newResource))
{
hasConflict = true;
break;
}
}
}
if (!hasConflict)
{
return candidateOffset;
}
}
}
// Try placing at the end
if (sortedRegions.Count > 0)
{
var last = sortedRegions[^1];
if (!LifetimesOverlap(last.Resource, newResource))
{
candidateOffset = AlignUp(last.Offset + last.Size, alignment);
if (candidateOffset + requiredSize <= Allocation.SizeInBytes)
{
return candidateOffset;
}
}
}
// No space found - caller should create new allocation
return 0;
}
private bool LifetimesOverlap(ResourceLifetime a, ResourceLifetime b)
{
return !(a.LastUse < b.FirstUse || b.LastUse < a.FirstUse);
}
private ulong AlignUp(ulong value, ulong alignment)
{
return (value + alignment - 1) / alignment * alignment;
}
public void AddResource(ResourceLifetime resource, ulong offsetInAllocation, ulong size)
{
Resources.Add(resource);
_occupiedRegions.Add((offsetInAllocation, size, resource));
Allocation.AliasedResources.Add(resource.Handle);
}
}