Photon Engine 2.0.0-beta
A physically based renderer.
Loading...
Searching...
No Matches
IndexedUIntBuffer.h
Go to the documentation of this file.
1#pragma once
2
3#include "Math/math.h"
4#include "Utility/utility.h"
5
6#include <Common/assertion.h>
7#include <Common/primitive_type.h>
8#include <Common/utility.h>
9
10#include <cstddef>
11#include <climits>
12#include <memory>
13#include <concepts>
14#include <limits>
15#include <stdexcept>
16#include <format>
17#include <cstring>
18#include <algorithm>
19#include <type_traits>
20
21namespace ph
22{
23
27{
28 static_assert(sizeof(std::byte)* CHAR_BIT == 8,
29 "The buffer explicitly depends on the fact that std::byte contains 8 bits.");
30
31public:
33
34 // TODO: aligned memory allocation?
35
36 void declareUIntFormat(std::size_t numBitsPerUInt);
37 void declareUIntFormatByMaxValue(uint64 maxValue);
38
39 template<std::integral IntegerType>
40 void declareUIntFormat();
41
42 void allocate(std::size_t numUInts);
43
46 template<std::integral IntegerType>
47 void setUInt(std::size_t index, IntegerType value);
48
54 template<typename ValueType>
55 void setUInts(const ValueType* values, std::size_t numValues, std::size_t dstBeginValueIndex = 0);
56
57 void setUInts(const std::byte* srcBytes, std::size_t numBytes, std::size_t dstOffset = 0);
58
59 // TODO: templatize
60 uint64 getUInt(std::size_t index) const;
61
62 std::size_t numUInts() const;
63 std::size_t memoryUsage() const;
64 bool isAllocated() const;
65
69 uint64 getMaxAllowedValue() const;
70
74 std::byte* getData();
75 const std::byte* getData() const;
77
78private:
79 static uint64 maxAllowedValue(uint8 numBitsPerUInt);
80
81 std::unique_ptr<std::byte[]> m_byteBuffer;
82 std::size_t m_byteBufferSize;
83 uint8 m_numBitsPerUInt;
84};
85
86// In-header Implementations:
87
88template<std::integral IntegerType>
90{
91 declareUIntFormat(sizeof_in_bits<IntegerType>());
92}
93
94inline std::size_t IndexedUIntBuffer::memoryUsage() const
95{
96 return sizeof(*this) + m_byteBufferSize;
97}
98
100{
101 return m_byteBuffer != nullptr;
102}
103
105{
106 return maxAllowedValue(m_numBitsPerUInt);
107}
108
109inline std::size_t IndexedUIntBuffer::numUInts() const
110{
111 return m_numBitsPerUInt > 0 ? m_byteBufferSize * CHAR_BIT / m_numBitsPerUInt : 0;
112}
113
114inline uint64 IndexedUIntBuffer::maxAllowedValue(const uint8 numBitsPerUInt)
115{
116 return numBitsPerUInt < 64
117 ? (static_cast<uint64>(1) << numBitsPerUInt) - 1
118 : std::numeric_limits<uint64>::max();
119}
120
121template<std::integral IntegerType>
122inline void IndexedUIntBuffer::setUInt(const std::size_t index, const IntegerType value)
123{
124 PH_ASSERT(isAllocated());
125
126 const auto uint64Value = lossless_integer_cast<uint64>(value);
127 if(uint64Value > getMaxAllowedValue())
128 {
129 throw std::invalid_argument(std::format(
130 "Integer value {} cannot be stored using {} bits.", value, m_numBitsPerUInt));
131 }
132
133 const std::size_t firstByteIndex = index * m_numBitsPerUInt / CHAR_BIT;
134 const std::size_t firstByteBitOffset = index * m_numBitsPerUInt - firstByteIndex * CHAR_BIT;
135 const std::size_t numStraddledBytes = (firstByteBitOffset + m_numBitsPerUInt + (CHAR_BIT - 1)) / CHAR_BIT;
136
137 PH_ASSERT_LT(firstByteBitOffset, CHAR_BIT);
138 PH_ASSERT_LE(numStraddledBytes, 8 + 1);
139 PH_ASSERT_LE(firstByteIndex + numStraddledBytes, m_byteBufferSize);
140
141 // Potentially read straddled previous & next values' bits
142 uint64 rawBits = 0;
143 std::memcpy(&rawBits, &m_byteBuffer[firstByteIndex], std::min<std::size_t>(numStraddledBytes, 8));
144
145 // Store value between the back of previous value and the head of next value
146 rawBits = math::clear_bits_in_range(rawBits, firstByteBitOffset, firstByteBitOffset + m_numBitsPerUInt);
147 rawBits |= (uint64Value << firstByteBitOffset);
148 std::memcpy(&m_byteBuffer[firstByteIndex], &rawBits, std::min<std::size_t>(numStraddledBytes, 8));
149
150 // Handle situations where the value needs the 9-th byte (straddles next byte), this can happen
151 // since we support any number of bits per index
152 if(numStraddledBytes > 8)
153 {
154 uint8 remainingRawBits;
155 std::memcpy(&remainingRawBits, &m_byteBuffer[firstByteIndex + 8], 1);
156
157 // Store the remaining value before next value
158 remainingRawBits = math::clear_bits_in_range(remainingRawBits, static_cast<std::size_t>(0), firstByteBitOffset + m_numBitsPerUInt - 64);
159 remainingRawBits |= static_cast<uint8>(uint64Value >> (64 - firstByteBitOffset));
160 std::memcpy(&m_byteBuffer[firstByteIndex + 8], &remainingRawBits, 1);
161 }
162}
163
164template<typename ValueType>
166 const ValueType* const values,
167 const std::size_t numValues,
168 const std::size_t dstBeginValueIndex)
169{
170 PH_ASSERT(values);
171
172 // Directly copy the value buffer if the formats matched
173 if(std::is_unsigned_v<ValueType> && sizeof_in_bits<ValueType>() == m_numBitsPerUInt)
174 {
175 setUInts(
176 reinterpret_cast<const std::byte*>(values),
177 numValues * sizeof(ValueType),
178 dstBeginValueIndex * sizeof(ValueType));
179 }
180 // Resort to copy one by one
181 else
182 {
183 for(std::size_t uintIdx = dstBeginValueIndex, valueIdx = 0; valueIdx < numValues; ++uintIdx, ++valueIdx)
184 {
185 if constexpr(std::is_integral_v<ValueType>)
186 {
187 setUInt(uintIdx, values[valueIdx]);
188 }
189 else
190 {
191 setUInt(uintIdx, static_cast<uint64>(values[valueIdx]));
192 }
193 }
194 }
195}
196
197inline uint64 IndexedUIntBuffer::getUInt(const std::size_t index) const
198{
199 PH_ASSERT(isAllocated());
200
201 const std::size_t firstByteIndex = index * m_numBitsPerUInt / CHAR_BIT;
202 const std::size_t firstByteBitOffset = index * m_numBitsPerUInt - firstByteIndex * CHAR_BIT;
203 const std::size_t numStraddledBytes = (firstByteBitOffset + m_numBitsPerUInt + (CHAR_BIT - 1)) / CHAR_BIT;
204
205 PH_ASSERT_LT(firstByteBitOffset, CHAR_BIT);
206 PH_ASSERT_LE(numStraddledBytes, 8 + 1);
207 PH_ASSERT_LE(firstByteIndex + numStraddledBytes, m_byteBufferSize);
208
209 // Read current value's bits (first 8 bytes, at most)
210 uint64 rawBits = 0;
211 std::memcpy(&rawBits, &m_byteBuffer[firstByteIndex], std::min<std::size_t>(numStraddledBytes, 8));
212
213 // Clear previous and next values' bits if any, then get the value
214 const auto bitMask = math::set_bits_in_range<uint64>(0, firstByteBitOffset, firstByteBitOffset + m_numBitsPerUInt);
215 uint64 value = (rawBits & bitMask) >> firstByteBitOffset;
216
217 // Handle situations where the value needs the 9-th byte (straddles next byte), this can happen
218 // since we support any number of bits per index
219 if(numStraddledBytes > 8)
220 {
221 uint8 remainingRawBits;
222 std::memcpy(&remainingRawBits, &m_byteBuffer[firstByteIndex + 8], 1);
223
224 // Extract the remaining value and clear next value's bits if any
225 const auto remainingBitsBitMask = math::set_bits_in_range<uint8>(0, static_cast<std::size_t>(0), firstByteBitOffset + m_numBitsPerUInt - 64);
226 remainingRawBits &= remainingBitsBitMask;
227
228 // Add the remaining bits to the value
229 value |= (static_cast<uint64>(remainingRawBits) << (64 - firstByteBitOffset));
230 }
231
232 return value;
233}
234
235inline std::byte* IndexedUIntBuffer::getData()
236{
237 PH_ASSERT(isAllocated());
238 return m_byteBuffer.get();
239}
240
241inline const std::byte* IndexedUIntBuffer::getData() const
242{
243 PH_ASSERT(isAllocated());
244 return m_byteBuffer.get();
245}
246
247}// end namespace ph
A general unsigned integer buffer for integers with any number of bits.
Definition IndexedUIntBuffer.h:27
uint64 getUInt(std::size_t index) const
Definition IndexedUIntBuffer.h:197
std::size_t numUInts() const
Definition IndexedUIntBuffer.h:109
void declareUIntFormat()
Definition IndexedUIntBuffer.h:89
IndexedUIntBuffer()
Definition IndexedUIntBuffer.cpp:10
void allocate(std::size_t numUInts)
Definition IndexedUIntBuffer.cpp:37
std::size_t memoryUsage() const
Definition IndexedUIntBuffer.h:94
void declareUIntFormatByMaxValue(uint64 maxValue)
Definition IndexedUIntBuffer.cpp:21
void setUInts(const ValueType *values, std::size_t numValues, std::size_t dstBeginValueIndex=0)
Set integers to a range of indices. This method handles all types, including both integer and floatin...
Definition IndexedUIntBuffer.h:165
bool isAllocated() const
Definition IndexedUIntBuffer.h:99
uint64 getMaxAllowedValue() const
Definition IndexedUIntBuffer.h:104
std::byte * getData()
Access to the underlying raw byte buffer.
Definition IndexedUIntBuffer.h:235
void setUInt(std::size_t index, IntegerType value)
Set a single integer.
Definition IndexedUIntBuffer.h:122
Miscellaneous math utilities.
UIntType set_bits_in_range(const UIntType bits, const RangeType beginBitIdx, const RangeType endBitIdx)
Set bits in the range to 1. The bits in [beginBitIdx, endBitIdx) will be set to 1,...
Definition math.h:497
UIntType clear_bits_in_range(const UIntType bits, const RangeType beginBitIdx, const RangeType endBitIdx)
Set bits in the range to 0. The bits in [beginBitIdx, endBitIdx) will be set to 0,...
Definition math.h:518
The root for all renderer implementations.
Definition EEngineProject.h:6