Implicit surface rendering via ray tracing
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

553 lines
13 KiB

/*
* Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <algorithm>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <NvFoundation.h> // for NV_X86 and NV_X64
#if(defined(NV_X86) || defined(NV_X64)) && defined(_MSC_VER)
#include <intrin.h>
#endif
namespace nvh {
/**
\class nvh::TRangeAllocator
The nvh::TRangeAllocator<GRANULARITY> template allows to sub-allocate ranges from a fixed
maximum size. Ranges are allocated at GRANULARITY and are merged back on freeing.
Its primary use is within allocators that sub-allocate from fixed-size blocks.
The implementation is based on [MakeID by Emil Persson](http://www.humus.name/3D/MakeID.h).
Example :
\code{.cpp}
TRangeAllocator<256> range;
// initialize to a certain range
range.init(range.alignedSize(128 * 1024 * 1024));
...
// allocate a sub range
// example
uint32_t size = vertexBufferSize;
uint32_t alignment = vertexAlignment;
uint32_t allocOffset;
uint32_t allocSize;
uint32_t alignedOffset;
if (range.subAllocate(size, alignment, allocOffset, alignedOffset, allocSize)) {
... use the allocation space
// [alignedOffset + size] is guaranteed to be within [allocOffset + allocSize]
}
// give back the memory range for re-use
range.subFree(allocOffset, allocSize);
...
// at the end cleanup
range.deinit();
\endcode
*/
// GRANULARITY must be power of two
template <uint32_t GRANULARITY = 256>
class TRangeAllocator
{
private:
uint32_t m_size;
uint32_t m_used;
public:
TRangeAllocator()
: m_size(0)
, m_used(0)
{
}
TRangeAllocator(uint32_t size) { init(size); }
~TRangeAllocator() { deinit(); }
static uint32_t alignedSize(uint32_t size) { return (size + GRANULARITY - 1) & (~(GRANULARITY - 1)); }
void init(uint32_t size)
{
assert(size % GRANULARITY == 0 && "managed total size must be aligned to GRANULARITY");
uint32_t pages = ((size + GRANULARITY - 1) / GRANULARITY);
rangeInit(pages - 1);
m_used = 0;
m_size = size;
}
void deinit() { rangeDeinit(); }
bool isEmpty() const { return m_used == 0; }
bool isAvailable(uint32_t size, uint32_t align) const
{
uint32_t alignRest = align - 1;
uint32_t sizeReserved = size;
if(m_used >= m_size)
{
return false;
}
if(m_used != 0 && align > GRANULARITY)
{
sizeReserved += alignRest;
}
uint32_t countReserved = (sizeReserved + GRANULARITY - 1) / GRANULARITY;
return isRangeAvailable(countReserved);
}
bool subAllocate(uint32_t size, uint32_t align, uint32_t& outOffset, uint32_t& outAligned, uint32_t& outSize)
{
if(align == 0)
{
align = 1;
}
uint32_t alignRest = align - 1;
uint32_t sizeReserved = size;
#if(defined(NV_X86) || defined(NV_X64)) && defined(_MSC_VER)
bool alignIsPOT = __popcnt(align) == 1;
#else
bool alignIsPOT = __builtin_popcount(align) == 1;
#endif
if(m_used >= m_size)
{
outSize = 0;
outOffset = 0;
outAligned = 0;
return false;
}
if(m_used != 0 && (alignIsPOT ? (align > GRANULARITY) : ((alignRest + size) > GRANULARITY)))
{
sizeReserved += alignRest;
}
uint32_t countReserved = (sizeReserved + GRANULARITY - 1) / GRANULARITY;
uint32_t startID;
if(createRangeID(startID, countReserved))
{
outOffset = startID * GRANULARITY;
outAligned = ((outOffset + alignRest) / align) * align;
// due to custom alignment, we may be able to give
// pages back that we over-allocated
//
// reserved: [ | | | ] (GRANULARITY spacing)
// used: [ ] (custom alignment/size)
// corrected: [ | ] (GRANULARITY spacing)
// correct start (warning could yield more fragmentation)
uint32_t skipFront = (outAligned - outOffset) / GRANULARITY;
if(skipFront)
{
destroyRangeID(startID, skipFront);
outOffset += skipFront * GRANULARITY;
startID += skipFront;
countReserved -= skipFront;
}
assert(outOffset <= outAligned);
// correct end
uint32_t outLast = alignedSize(outAligned + size);
outSize = outLast - outOffset;
uint32_t usedCount = outSize / GRANULARITY;
assert(usedCount <= countReserved);
if(usedCount < countReserved)
{
destroyRangeID(startID + usedCount, countReserved - usedCount);
}
assert((outAligned + size) <= (outOffset + outSize));
m_used += outSize;
//checkRanges();
return true;
}
else
{
outSize = 0;
outOffset = 0;
outAligned = 0;
return false;
}
}
void subFree(uint32_t offset, uint32_t size)
{
assert(offset % GRANULARITY == 0);
assert(size % GRANULARITY == 0);
m_used -= size;
destroyRangeID(offset / GRANULARITY, size / GRANULARITY);
//checkRanges();
}
TRangeAllocator& operator=(const TRangeAllocator& other)
{
m_size = other.m_size;
m_used = other.m_used;
m_Ranges = other.m_Ranges;
m_Count = other.m_Count;
m_Capacity = other.m_Capacity;
m_MaxID = other.m_MaxID;
if(m_Ranges)
{
m_Ranges = static_cast<Range*>(::malloc(m_Capacity * sizeof(Range)));
memcpy(m_Ranges, other.m_Ranges, m_Capacity * sizeof(Range));
}
return *this;
}
TRangeAllocator(const TRangeAllocator& other)
{
m_size = other.m_size;
m_used = other.m_used;
m_Ranges = other.m_Ranges;
m_Count = other.m_Count;
m_Capacity = other.m_Capacity;
m_MaxID = other.m_MaxID;
if(m_Ranges)
{
m_Ranges = static_cast<Range*>(::malloc(m_Capacity * sizeof(Range)));
assert(m_Ranges); // Make sure allocation succeeded
memcpy(m_Ranges, other.m_Ranges, m_Capacity * sizeof(Range));
}
}
TRangeAllocator& operator=(TRangeAllocator&& other)
{
m_size = other.m_size;
m_used = other.m_used;
m_Ranges = other.m_Ranges;
m_Count = other.m_Count;
m_Capacity = other.m_Capacity;
m_MaxID = other.m_MaxID;
other.m_Ranges = nullptr;
return *this;
}
TRangeAllocator(TRangeAllocator&& other)
{
m_size = other.m_size;
m_used = other.m_used;
m_Ranges = other.m_Ranges;
m_Count = other.m_Count;
m_Capacity = other.m_Capacity;
m_MaxID = other.m_MaxID;
other.m_Ranges = nullptr;
}
private:
//////////////////////////////////////////////////////////////////////////
// most of the following code is taken from Emil Persson's MakeID
// http://www.humus.name/3D/MakeID.h (v1.02)
struct Range
{
uint32_t m_First;
uint32_t m_Last;
};
Range* m_Ranges = nullptr; // Sorted array of ranges of free IDs
uint32_t m_Count = 0; // Number of ranges in list
uint32_t m_Capacity = 0; // Total capacity of range list
uint32_t m_MaxID = 0;
public:
void rangeInit(const uint32_t max_id)
{
// Start with a single range, from 0 to max allowed ID (specified)
m_Ranges = static_cast<Range*>(::malloc(sizeof(Range)));
assert(m_Ranges != nullptr); // Make sure allocation succeeded
m_Ranges[0].m_First = 0;
m_Ranges[0].m_Last = max_id;
m_Count = 1;
m_Capacity = 1;
m_MaxID = max_id;
}
void rangeDeinit()
{
if(m_Ranges)
{
::free(m_Ranges);
m_Ranges = nullptr;
}
}
bool createID(uint32_t& id)
{
if(m_Ranges[0].m_First <= m_Ranges[0].m_Last)
{
id = m_Ranges[0].m_First;
// If current range is full and there is another one, that will become the new current range
if(m_Ranges[0].m_First == m_Ranges[0].m_Last && m_Count > 1)
{
destroyRange(0);
}
else
{
++m_Ranges[0].m_First;
}
return true;
}
// No availble ID left
return false;
}
bool createRangeID(uint32_t& id, const uint32_t count)
{
uint32_t i = 0;
do
{
const uint32_t range_count = 1 + m_Ranges[i].m_Last - m_Ranges[i].m_First;
if(count <= range_count)
{
id = m_Ranges[i].m_First;
// If current range is full and there is another one, that will become the new current range
if(count == range_count && i + 1 < m_Count)
{
destroyRange(i);
}
else
{
m_Ranges[i].m_First += count;
}
return true;
}
++i;
} while(i < m_Count);
// No range of free IDs was large enough to create the requested continuous ID sequence
return false;
}
bool destroyID(const uint32_t id) { return destroyRangeID(id, 1); }
bool destroyRangeID(const uint32_t id, const uint32_t count)
{
const uint32_t end_id = id + count;
assert(end_id <= m_MaxID + 1);
// Binary search of the range list
uint32_t i0 = 0;
uint32_t i1 = m_Count - 1;
for(;;)
{
const uint32_t i = (i0 + i1) / 2;
if(id < m_Ranges[i].m_First)
{
// Before current range, check if neighboring
if(end_id >= m_Ranges[i].m_First)
{
if(end_id != m_Ranges[i].m_First)
return false; // Overlaps a range of free IDs, thus (at least partially) invalid IDs
// Neighbor id, check if neighboring previous range too
if(i > i0 && id - 1 == m_Ranges[i - 1].m_Last)
{
// Merge with previous range
m_Ranges[i - 1].m_Last = m_Ranges[i].m_Last;
destroyRange(i);
}
else
{
// Just grow range
m_Ranges[i].m_First = id;
}
return true;
}
else
{
// Non-neighbor id
if(i != i0)
{
// Cull upper half of list
i1 = i - 1;
}
else
{
// Found our position in the list, insert the deleted range here
insertRange(i);
m_Ranges[i].m_First = id;
m_Ranges[i].m_Last = end_id - 1;
return true;
}
}
}
else if(id > m_Ranges[i].m_Last)
{
// After current range, check if neighboring
if(id - 1 == m_Ranges[i].m_Last)
{
// Neighbor id, check if neighboring next range too
if(i < i1 && end_id == m_Ranges[i + 1].m_First)
{
// Merge with next range
m_Ranges[i].m_Last = m_Ranges[i + 1].m_Last;
destroyRange(i + 1);
}
else
{
// Just grow range
m_Ranges[i].m_Last += count;
}
return true;
}
else
{
// Non-neighbor id
if(i != i1)
{
// Cull bottom half of list
i0 = i + 1;
}
else
{
// Found our position in the list, insert the deleted range here
insertRange(i + 1);
m_Ranges[i + 1].m_First = id;
m_Ranges[i + 1].m_Last = end_id - 1;
return true;
}
}
}
else
{
// Inside a free block, not a valid ID
return false;
}
}
}
bool isRangeAvailable(uint32_t searchCount) const
{
uint32_t i = 0;
do
{
uint32_t count = m_Ranges[i].m_Last - m_Ranges[i].m_First + 1;
if(count >= searchCount)
return true;
++i;
} while(i < m_Count);
return false;
}
void printRanges() const
{
uint32_t i = 0;
for(;;)
{
if(m_Ranges[i].m_First < m_Ranges[i].m_Last)
printf("%u-%u", m_Ranges[i].m_First, m_Ranges[i].m_Last);
else if(m_Ranges[i].m_First == m_Ranges[i].m_Last)
printf("%u", m_Ranges[i].m_First);
else
printf("-");
++i;
if(i >= m_Count)
{
printf("\n");
return;
}
printf(", ");
}
}
void checkRanges() const
{
for(uint32_t i = 0; i < m_Count; i++)
{
assert(m_Ranges[i].m_Last <= m_MaxID);
if(m_Ranges[i].m_First == m_Ranges[i].m_Last + 1)
{
continue;
}
assert(m_Ranges[i].m_First <= m_Ranges[i].m_Last);
assert(m_Ranges[i].m_First <= m_MaxID);
}
}
void insertRange(const uint32_t index)
{
if(m_Count >= m_Capacity)
{
m_Capacity += m_Capacity;
m_Ranges = (Range*)realloc(m_Ranges, m_Capacity * sizeof(Range));
assert(m_Ranges); // Make sure reallocation succeeded
}
::memmove(m_Ranges + index + 1, m_Ranges + index, (m_Count - index) * sizeof(Range));
++m_Count;
}
void destroyRange(const uint32_t index)
{
--m_Count;
::memmove(m_Ranges + index, m_Ranges + index + 1, (m_Count - index) * sizeof(Range));
}
};
} // namespace nvh