Explorar o código

Added an alignment argument to the allocating buffer constructor, so that one can manually use larger SIMD than the default alignment would allow.

David Piuva %!s(int64=2) %!d(string=hai) anos
pai
achega
50ce57b3c0
Modificáronse 2 ficheiros con 44 adicións e 20 borrados
  1. 33 17
      Source/DFPSR/api/bufferAPI.cpp
  2. 11 3
      Source/DFPSR/api/bufferAPI.h

+ 33 - 17
Source/DFPSR/api/bufferAPI.cpp

@@ -37,13 +37,14 @@ public:
 	// A Buffer cannot have a name, because each String contains a buffer
 	// A Buffer cannot have a name, because each String contains a buffer
 	const int64_t size; // The actually used data
 	const int64_t size; // The actually used data
 	const int64_t bufferSize; // The accessible data
 	const int64_t bufferSize; // The accessible data
+	const int alignment;
 	uint8_t *data;
 	uint8_t *data;
 	std::function<void(uint8_t *)> destructor;
 	std::function<void(uint8_t *)> destructor;
 public:
 public:
 	// Create head without data.
 	// Create head without data.
 	BufferImpl();
 	BufferImpl();
 	// Create head with newly allocated data.
 	// Create head with newly allocated data.
-	explicit BufferImpl(int64_t newSize);
+	explicit BufferImpl(int64_t newSize, int alignment);
 	// Create head with inherited data.
 	// Create head with inherited data.
 	BufferImpl(int64_t newSize, uint8_t *newData);
 	BufferImpl(int64_t newSize, uint8_t *newData);
 	~BufferImpl();
 	~BufferImpl();
@@ -55,23 +56,31 @@ public:
 
 
 // Internal methods
 // Internal methods
 
 
-// Buffers are aligned and padded for the deafult SIMD vector size, so that vectorization can be efficient.
-static const int buffer_alignment = DSR_DEFAULT_ALIGNMENT;
+// Get the largest alignment and confirm that it is a power of two.
+static int getFinalAlignment(int requestedAlignment) {
+	// Find any power of two alignment divisible by both requestedAlignment and DSR_DEFAULT_ALIGNMENT
+	int largestAlignment = max(requestedAlignment, DSR_DEFAULT_ALIGNMENT);
+	for (uint32_t e = 0; e < 32; e++) {
+		uint32_t requestedAlignment = 1 << e;
+		if (1 << e == largestAlignment) return largestAlignment;
+	}
+	return -1;
+}
 
 
 // If this C++ version additionally includes the C11 features then we may assume that aligned_alloc is available
 // If this C++ version additionally includes the C11 features then we may assume that aligned_alloc is available
 #ifdef _ISOC11_SOURCE
 #ifdef _ISOC11_SOURCE
 	// Allocate data of newSize and write the corresponding destructor function to targetDestructor
 	// Allocate data of newSize and write the corresponding destructor function to targetDestructor
-	static uint8_t* buffer_allocate(int64_t newSize, std::function<void(uint8_t *)>& targetDestructor) {
-		uint8_t* allocation = (uint8_t*)aligned_alloc(buffer_alignment, newSize);
+	static uint8_t* buffer_allocate(int64_t newSize, int alignment, std::function<void(uint8_t *)>& targetDestructor) {
+		uint8_t* allocation = (uint8_t*)aligned_alloc(alignment, newSize);
 		targetDestructor = [](uint8_t *data) { free(data); };
 		targetDestructor = [](uint8_t *data) { free(data); };
 		return allocation;
 		return allocation;
 	}
 	}
 #else
 #else
-	static const uintptr_t buffer_alignment_mask = ~((uintptr_t)(buffer_alignment - 1));
 	// Allocate data of newSize and write the corresponding destructor function to targetDestructor
 	// Allocate data of newSize and write the corresponding destructor function to targetDestructor
-	static uint8_t* buffer_allocate(int64_t newSize, std::function<void(uint8_t *)>& targetDestructor) {
-		uintptr_t padding = buffer_alignment - 1;
+	static uint8_t* buffer_allocate(int64_t newSize, int alignment, std::function<void(uint8_t *)>& targetDestructor) {
+		uintptr_t padding = alignment - 1;
 		uint8_t* allocation = (uint8_t*)malloc(newSize + padding);
 		uint8_t* allocation = (uint8_t*)malloc(newSize + padding);
+		uintptr_t buffer_alignment_mask = ~((uintptr_t)(alignment - 1));
 		uint8_t* aligned = (uint8_t*)(((uintptr_t)allocation + padding) & buffer_alignment_mask);
 		uint8_t* aligned = (uint8_t*)(((uintptr_t)allocation + padding) & buffer_alignment_mask);
 		uintptr_t offset = allocation - aligned;
 		uintptr_t offset = allocation - aligned;
 		targetDestructor = [offset](uint8_t *data) { free(data - offset); };
 		targetDestructor = [offset](uint8_t *data) { free(data - offset); };
@@ -79,12 +88,13 @@ static const int buffer_alignment = DSR_DEFAULT_ALIGNMENT;
 	}
 	}
 #endif
 #endif
 
 
-BufferImpl::BufferImpl() : size(0), bufferSize(0), data(nullptr) {}
+BufferImpl::BufferImpl() : size(0), bufferSize(0), alignment(DSR_DEFAULT_ALIGNMENT), data(nullptr) {}
 
 
-BufferImpl::BufferImpl(int64_t newSize) :
+BufferImpl::BufferImpl(int64_t newSize, int alignment) :
   size(newSize),
   size(newSize),
-  bufferSize(roundUp(newSize, buffer_alignment)) {
-	this->data = buffer_allocate(this->bufferSize, this->destructor);
+  bufferSize(roundUp(newSize, alignment)),
+  alignment(alignment) {
+	this->data = buffer_allocate(this->bufferSize, alignment, this->destructor);
 	if (this->data == nullptr) {
 	if (this->data == nullptr) {
 		throwError(U"Failed to allocate buffer of ", newSize, " bytes!\n");
 		throwError(U"Failed to allocate buffer of ", newSize, " bytes!\n");
 	}
 	}
@@ -92,7 +102,7 @@ BufferImpl::BufferImpl(int64_t newSize) :
 }
 }
 
 
 BufferImpl::BufferImpl(int64_t newSize, uint8_t *newData)
 BufferImpl::BufferImpl(int64_t newSize, uint8_t *newData)
-: size(newSize), bufferSize(newSize), data(newData), destructor([](uint8_t *data) { free(data); }) {}
+: size(newSize), bufferSize(newSize), alignment(1), data(newData), destructor([](uint8_t *data) { free(data); }) {}
 
 
 BufferImpl::~BufferImpl() {
 BufferImpl::~BufferImpl() {
 	if (this->data) {
 	if (this->data) {
@@ -111,22 +121,28 @@ Buffer buffer_clone(const Buffer &buffer) {
 			// No need to clone when there is no shared data.
 			// No need to clone when there is no shared data.
 			return buffer;
 			return buffer;
 		} else {
 		} else {
-			// Clone the data so that the allocations can be modified individually.
-			Buffer newBuffer = std::make_shared<BufferImpl>(buffer->size);
+			// Clone the data so that content of the allocations can be modified individually without affecting each other.
+			Buffer newBuffer = std::make_shared<BufferImpl>(buffer->size, max(buffer->alignment, DSR_DEFAULT_ALIGNMENT));
 			memcpy(newBuffer->data, buffer->data, buffer->size);
 			memcpy(newBuffer->data, buffer->data, buffer->size);
 			return newBuffer;
 			return newBuffer;
 		}
 		}
 	}
 	}
 }
 }
 
 
-Buffer buffer_create(int64_t newSize) {
+Buffer buffer_create(int64_t newSize, int minimumAlignment) {
 	if (newSize < 0) newSize = 0;
 	if (newSize < 0) newSize = 0;
 	if (newSize == 0) {
 	if (newSize == 0) {
 		// Allocate empty head to indicate that an empty buffer exists.
 		// Allocate empty head to indicate that an empty buffer exists.
 		return std::make_shared<BufferImpl>();
 		return std::make_shared<BufferImpl>();
 	} else {
 	} else {
 		// Allocate head and data.
 		// Allocate head and data.
-		return std::make_shared<BufferImpl>(newSize);
+		int finalAlignment = getFinalAlignment(minimumAlignment);
+		if (finalAlignment != -1) {
+			return std::make_shared<BufferImpl>(newSize, finalAlignment);
+		} else {
+			throwError(U"buffer_create: Minimum alignment ", minimumAlignment, " is not a power of two!\n");
+			return Buffer(); // Invalid alignment argument was not a power of two.
+		}
 	}
 	}
 }
 }
 
 

+ 11 - 3
Source/DFPSR/api/bufferAPI.h

@@ -55,15 +55,21 @@ namespace dsr {
 	class BufferImpl;
 	class BufferImpl;
 	using Buffer = std::shared_ptr<BufferImpl>;
 	using Buffer = std::shared_ptr<BufferImpl>;
 
 
+	// Pre-condition:
+	//   minimumAlignment must be a power of two, otherwise an exception will be thrown.
 	// Side-effect: Creates a new buffer head regardless of newSize, but only allocates a zeroed data allocation if newSize > 0.
 	// Side-effect: Creates a new buffer head regardless of newSize, but only allocates a zeroed data allocation if newSize > 0.
 	// Post-condition: Returns a handle to the new buffer.
 	// Post-condition: Returns a handle to the new buffer.
 	// Creating a buffer without a size will only allocate the buffer's head referring to null data with size zero.
 	// Creating a buffer without a size will only allocate the buffer's head referring to null data with size zero.
-	Buffer buffer_create(int64_t newSize);
+	// The minimumAlignment argument represents the number of bytes the allocation must be aligned with in memory when DSR_DEFAULT_ALIGNMENT is not enough.
+	//   Useful for when using a longer SIMD vector that only exists for a certain type (such as AVX without AVX2) or you use a signal processor.
+	//   Any minimumAlignment smaller than DSR_DEFAULT_ALIGNMENT will be ignored, because then DSR_DEFAULT_ALIGNMENT is larger than the minimum.
+	Buffer buffer_create(int64_t newSize, int minimumAlignment = 1);
 
 
+	// Pre-conditions:
+	//   newSize may not be larger than the size of newData in bytes.
+	//     Breaking this pre-condition may cause crashes, so only provide a newData pointer if you know what you are doing.
 	// Side-effect: Creates a new buffer of newSize bytes inheriting ownership of newData.
 	// Side-effect: Creates a new buffer of newSize bytes inheriting ownership of newData.
 	//   If the given data cannot be freed as a C allocation, replaceDestructor must be called with the special destructor.
 	//   If the given data cannot be freed as a C allocation, replaceDestructor must be called with the special destructor.
-	// Pre-condition: newSize may not be larger than the size of newData in bytes.
-	//   Breaking this pre-condition may cause crashes, so only provide a newData pointer if you know what you are doing.
 	// Post-condition: Returns a handle to the manually constructed buffer.
 	// Post-condition: Returns a handle to the manually constructed buffer.
 	Buffer buffer_create(int64_t newSize, uint8_t *newData);
 	Buffer buffer_create(int64_t newSize, uint8_t *newData);
 
 
@@ -79,6 +85,8 @@ namespace dsr {
 
 
 	// Returns a clone of the buffer.
 	// Returns a clone of the buffer.
 	// Giving an empty handle returns an empty handle.
 	// Giving an empty handle returns an empty handle.
+	// If the old buffer's alignment exceeds DSR_DEFAULT_ALIGNMENT, the alignment will be inherited.
+	// The resulting buffer will always be aligned by at least DSR_DEFAULT_ALIGNMENT, even if the old buffer had no alignment.
 	Buffer buffer_clone(const Buffer &buffer);
 	Buffer buffer_clone(const Buffer &buffer);
 
 
 	// Returns the buffer's size in bytes, as given when allocating it excluding allocation padding.
 	// Returns the buffer's size in bytes, as given when allocating it excluding allocation padding.