浏览代码

Created an arena allocator for getting heap memory.

David Piuva 11 月之前
父节点
当前提交
0bbabf38c5

+ 1 - 1
Doc/Buffers.html

@@ -26,7 +26,7 @@ A:active { color: #FFFFFF; background: #444444; }
 </P><P>
 </P><P>
 </P><H1> Buffers</H1><P>Every file that is saved or loaded in the framework will pass through a Buffer.
 </P><H1> Buffers</H1><P>Every file that is saved or loaded in the framework will pass through a Buffer.
 Buffers can not refer to each other in cycles and are automatically reference counted and deleted, so that you don't have to worry about memory leaks from them unless you explicitly call buffer_replaceDestructor.
 Buffers can not refer to each other in cycles and are automatically reference counted and deleted, so that you don't have to worry about memory leaks from them unless you explicitly call buffer_replaceDestructor.
-They store a fixed size allocation of 128-bit padded and aligned memory to work well with 128-bit SIMD intrinsics.
+They store a fixed size allocation of memory padded and aligned with DSR_MAXIMUM_ALIGNMENT bytes to work well with the largest SIMD vectors without false sharing of cache lines between threads.
 
 
 </P><P>
 </P><P>
 </P><IMG SRC="Images/Border.png"><P>
 </P><IMG SRC="Images/Border.png"><P>

+ 1 - 1
Doc/Generator/Input/Buffers.txt

@@ -3,7 +3,7 @@
 Title: Buffers
 Title: Buffers
 Every file that is saved or loaded in the framework will pass through a Buffer.
 Every file that is saved or loaded in the framework will pass through a Buffer.
 Buffers can not refer to each other in cycles and are automatically reference counted and deleted, so that you don't have to worry about memory leaks from them unless you explicitly call buffer_replaceDestructor.
 Buffers can not refer to each other in cycles and are automatically reference counted and deleted, so that you don't have to worry about memory leaks from them unless you explicitly call buffer_replaceDestructor.
-They store a fixed size allocation of 128-bit padded and aligned memory to work well with 128-bit SIMD intrinsics.
+They store a fixed size allocation of memory padded and aligned with DSR_MAXIMUM_ALIGNMENT bytes to work well with the largest SIMD vectors without false sharing of cache lines between threads.
 
 
 ---
 ---
 Title2: Construction
 Title2: Construction

+ 2 - 2
Doc/Generator/Input/ImageProcessing.txt

@@ -249,8 +249,8 @@ Then you might want to take advantage of 256-bit SIMD vectors, but don't want to
 For functions working directly on values without reading nor writing, you can use templates to have multiple vector lengths supported at the same time.
 For functions working directly on values without reading nor writing, you can use templates to have multiple vector lengths supported at the same time.
 For a filter however, you only need to generate the code for the biggest available vector size, so we use U8xX and laneCountX_8Bit for processing 8-bit monochrome images using type aliases.
 For a filter however, you only need to generate the code for the biggest available vector size, so we use U8xX and laneCountX_8Bit for processing 8-bit monochrome images using type aliases.
 When building with AVX2 (-mavx2 for g++), the X vector types (F32xX, I32xX, U32xX, U16xX, U8xX) change size from 128 bits to 256 bits and their lane counts (laneCountX_32Bit, laneCountX_16Bit, laneCountX_8Bit) also double.
 When building with AVX2 (-mavx2 for g++), the X vector types (F32xX, I32xX, U32xX, U16xX, U8xX) change size from 128 bits to 256 bits and their lane counts (laneCountX_32Bit, laneCountX_16Bit, laneCountX_8Bit) also double.
-If you do not have AVX2 on your computer for testing this, you can force the X vector to be at least 256 bits by defining the macro EMULATE_256BIT_X_SIMD globally.
-The aligned image types and buffers allocated by the library are always aligned with at least the X vector's DSR_DEFAULT_ALIGNMENT, so you can safely use the X vector on any aligned image and most of the buffers.
+If you do not have AVX2 on your computer for testing this, you can force the X vector to be at least 256 bits by defining the macro EMULATE_256BIT_X_SIMD globally or in settings.h.
+The aligned image types and buffers allocated by the library are always aligned with DSR_MAXIMUM_ALIGNMENT from settings.h, so you can safely use the X and F vectors on any buffer or aligned image.
 
 
 Replaced <B>U8x16</B> with <B>U8xX</B> and <B>16</B> with <B>laneCountX_8Bit</B> to work with any future SIMD vector length:
 Replaced <B>U8x16</B> with <B>U8xX</B> and <B>16</B> with <B>laneCountX_8Bit</B> to work with any future SIMD vector length:
 CodeStart:
 CodeStart:

+ 1 - 1
Doc/Generator/build.sh

@@ -1,7 +1,7 @@
 #!/bin/bash
 #!/bin/bash
 
 
 LIBRARY_PATH=../../Source/DFPSR
 LIBRARY_PATH=../../Source/DFPSR
-DEPENDENCIES="${LIBRARY_PATH}/collection/collections.cpp ${LIBRARY_PATH}/api/fileAPI.cpp ${LIBRARY_PATH}/api/bufferAPI.cpp ${LIBRARY_PATH}/api/stringAPI.cpp ${LIBRARY_PATH}/base/SafePointer.cpp"
+DEPENDENCIES="${LIBRARY_PATH}/collection/collections.cpp ${LIBRARY_PATH}/api/fileAPI.cpp ${LIBRARY_PATH}/api/bufferAPI.cpp ${LIBRARY_PATH}/api/stringAPI.cpp ${LIBRARY_PATH}/base/SafePointer.cpp ${LIBRARY_PATH}/base/virtualStack.cpp ${LIBRARY_PATH}/base/heap.cpp"
 
 
 g++ main.cpp -o generator ${DEPENDENCIES} -std=c++14 -lm
 g++ main.cpp -o generator ${DEPENDENCIES} -std=c++14 -lm
 
 

+ 2 - 2
Doc/ImageProcessing.html

@@ -312,8 +312,8 @@ Then you might want to take advantage of 256-bit SIMD vectors, but don't want to
 For functions working directly on values without reading nor writing, you can use templates to have multiple vector lengths supported at the same time.
 For functions working directly on values without reading nor writing, you can use templates to have multiple vector lengths supported at the same time.
 For a filter however, you only need to generate the code for the biggest available vector size, so we use U8xX and laneCountX_8Bit for processing 8-bit monochrome images using type aliases.
 For a filter however, you only need to generate the code for the biggest available vector size, so we use U8xX and laneCountX_8Bit for processing 8-bit monochrome images using type aliases.
 When building with AVX2 (-mavx2 for g++), the X vector types (F32xX, I32xX, U32xX, U16xX, U8xX) change size from 128 bits to 256 bits and their lane counts (laneCountX_32Bit, laneCountX_16Bit, laneCountX_8Bit) also double.
 When building with AVX2 (-mavx2 for g++), the X vector types (F32xX, I32xX, U32xX, U16xX, U8xX) change size from 128 bits to 256 bits and their lane counts (laneCountX_32Bit, laneCountX_16Bit, laneCountX_8Bit) also double.
-If you do not have AVX2 on your computer for testing this, you can force the X vector to be at least 256 bits by defining the macro EMULATE_256BIT_X_SIMD globally.
-The aligned image types and buffers allocated by the library are always aligned with at least the X vector's DSR_DEFAULT_ALIGNMENT, so you can safely use the X vector on any aligned image and most of the buffers.
+If you do not have AVX2 on your computer for testing this, you can force the X vector to be at least 256 bits by defining the macro EMULATE_256BIT_X_SIMD globally or in settings.h.
+The aligned image types and buffers allocated by the library are always aligned with DSR_MAXIMUM_ALIGNMENT from settings.h, so you can safely use the X and F vectors on any buffer or aligned image.
 
 
 </P><P>
 </P><P>
 Replaced <B>U8x16</B> with <B>U8xX</B> and <B>16</B> with <B>laneCountX_8Bit</B> to work with any future SIMD vector length:
 Replaced <B>U8x16</B> with <B>U8xX</B> and <B>16</B> with <B>laneCountX_8Bit</B> to work with any future SIMD vector length:

+ 1 - 1
Doc/Strings.html

@@ -37,7 +37,7 @@ Another option would be to use std:u32string, which does have a fixed format, bu
 Then you might as well just use a custom string type and get both a standard representation and modern optimizations.
 Then you might as well just use a custom string type and get both a standard representation and modern optimizations.
 dsr::String will save you heap allocations when passed by value, because the buffer is reference counted and reused until actually modified by appending data to it.
 dsr::String will save you heap allocations when passed by value, because the buffer is reference counted and reused until actually modified by appending data to it.
 It will behave just as if the content was cloned every time you pass it by value, but it automatically reuses allocations when doing so will save time and memory.
 It will behave just as if the content was cloned every time you pass it by value, but it automatically reuses allocations when doing so will save time and memory.
-Because dsr::ReadableString stores the length as an integer per view instead of writing a null terminator into the character data, splitting a string will create many views to the same allocation instead of creating lots of small heap allocations.
+Because dsr::ReadableString stores the length as an integer per view instead of writing a null terminator into the character data, splitting a string will create many views to the same allocation instead of creating lots of small heap allocations just to insert null terminators.
 </P><IMG SRC="Images/Border.png"><P>
 </P><IMG SRC="Images/Border.png"><P>
 </P><H2> Encoding</H2><P>Both dsr::String and dsr::ReadableString are encoded in the UTF-32 format using only line-feed for line-breaks.
 </P><H2> Encoding</H2><P>Both dsr::String and dsr::ReadableString are encoded in the UTF-32 format using only line-feed for line-breaks.
 This takes more memory but guarantees that each character is one element which makes algorithms a lot easier to implement when you cannot get corrupted characters or line-breaks by mistake.
 This takes more memory but guarantees that each character is one element which makes algorithms a lot easier to implement when you cannot get corrupted characters or line-breaks by mistake.

+ 27 - 47
Source/DFPSR/api/bufferAPI.cpp

@@ -22,11 +22,10 @@
 //    distribution.
 //    distribution.
 
 
 #include <fstream>
 #include <fstream>
-#include <cstdlib>
 #include "bufferAPI.h"
 #include "bufferAPI.h"
 #include "stringAPI.h"
 #include "stringAPI.h"
 #include "../math/scalar.h"
 #include "../math/scalar.h"
-#include "../base/simd.h"
+#include "../base/heap.h"
 
 
 namespace dsr {
 namespace dsr {
 
 
@@ -37,14 +36,13 @@ 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, int alignment);
+	explicit BufferImpl(int64_t newSize);
 	// 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();
@@ -56,44 +54,18 @@ public:
 
 
 // Internal methods
 // Internal methods
 
 
-// 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++) {
-		if (1 << e == largestAlignment) return largestAlignment;
-	}
-	return -1;
+static uint8_t* buffer_allocate(int64_t newSize, std::function<void(uint8_t *)>& targetDestructor) {
+	uint8_t* allocation = heap_allocate(newSize).data;
+	targetDestructor = [](uint8_t *data) { heap_free(data); };
+	return allocation;
 }
 }
 
 
-// If this C++ version additionally includes the C11 features then we may assume that aligned_alloc is available
-#ifdef _ISOC11_SOURCE
-	// Allocate data of newSize and write the corresponding destructor function to targetDestructor
-	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); };
-		return allocation;
-	}
-#else
-	// Allocate data of newSize and write the corresponding destructor function to targetDestructor
-	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);
-		uintptr_t buffer_alignment_mask = ~((uintptr_t)(alignment - 1));
-		uint8_t* aligned = (uint8_t*)(((uintptr_t)allocation + padding) & buffer_alignment_mask);
-		uintptr_t offset = allocation - aligned;
-		targetDestructor = [offset](uint8_t *data) { free(data - offset); };
-		return aligned;
-	}
-#endif
-
-BufferImpl::BufferImpl() : size(0), bufferSize(0), alignment(DSR_DEFAULT_ALIGNMENT), data(nullptr) {}
+BufferImpl::BufferImpl() : size(0), bufferSize(0), data(nullptr) {}
 
 
-BufferImpl::BufferImpl(int64_t newSize, int alignment) :
+BufferImpl::BufferImpl(int64_t newSize) :
   size(newSize),
   size(newSize),
-  bufferSize(roundUp(newSize, alignment)),
-  alignment(alignment) {
-	this->data = buffer_allocate(this->bufferSize, alignment, this->destructor);
+  bufferSize(roundUp(newSize, DSR_MAXIMUM_ALIGNMENT)) {
+	this->data = buffer_allocate(this->bufferSize, 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");
 	}
 	}
@@ -101,7 +73,7 @@ BufferImpl::BufferImpl(int64_t newSize, int alignment) :
 }
 }
 
 
 BufferImpl::BufferImpl(int64_t newSize, uint8_t *newData)
 BufferImpl::BufferImpl(int64_t newSize, uint8_t *newData)
-: size(newSize), bufferSize(newSize), alignment(1), data(newData), destructor([](uint8_t *data) { free(data); }) {}
+: size(newSize), bufferSize(newSize), data(newData), destructor([](uint8_t *data) { heap_free(data); }) {}
 
 
 BufferImpl::~BufferImpl() {
 BufferImpl::~BufferImpl() {
 	if (this->data) {
 	if (this->data) {
@@ -121,27 +93,35 @@ Buffer buffer_clone(const Buffer &buffer) {
 			return buffer;
 			return buffer;
 		} else {
 		} else {
 			// Clone the data so that content of the allocations can be modified individually without affecting each other.
 			// 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));
+			Buffer newBuffer = std::make_shared<BufferImpl>(buffer->size);
 			memcpy(newBuffer->data, buffer->data, buffer->size);
 			memcpy(newBuffer->data, buffer->data, buffer->size);
 			return newBuffer;
 			return newBuffer;
 		}
 		}
 	}
 	}
 }
 }
 
 
+Buffer buffer_create(int64_t newSize) {
+	if (newSize < 0) newSize = 0;
+	if (newSize == 0) {
+		// Allocate empty head to indicate that an empty buffer exists.
+		return std::make_shared<BufferImpl>();
+	} else {
+		// Allocate head and data.
+		return std::make_shared<BufferImpl>(newSize);
+	}
+}
+
 Buffer buffer_create(int64_t newSize, int minimumAlignment) {
 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 if (minimumAlignment > DSR_MAXIMUM_ALIGNMENT) {
+		throwError(U"Maximum alignment exceeded when creating a buffer!\n");
+		return Buffer();
 	} else {
 	} else {
 		// Allocate head and data.
 		// Allocate head and data.
-		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.
-		}
+		return std::make_shared<BufferImpl>(newSize);
 	}
 	}
 }
 }
 
 

+ 5 - 6
Source/DFPSR/api/bufferAPI.h

@@ -28,6 +28,7 @@
 #include <memory>
 #include <memory>
 #include <functional>
 #include <functional>
 #include "../base/SafePointer.h"
 #include "../base/SafePointer.h"
+#include "../settings.h"
 
 
 // The types of buffer handles to consider when designing algorithms:
 // The types of buffer handles to consider when designing algorithms:
 // * Null handle suggesting that there is nothing, such as when loading a file failed.
 // * Null handle suggesting that there is nothing, such as when loading a file failed.
@@ -55,17 +56,15 @@ 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.
-	// 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);
+	Buffer buffer_create(int64_t newSize);
+	// The buffer always allocate with DSR_MAXIMUM_ALIGNMENT, but you can check that your requested alignment is not too much.
+	Buffer buffer_create(int64_t newSize, int minimumAlignment);
 
 
 	// Pre-conditions:
 	// Pre-conditions:
+	//   newData must be padded and aligned by DSR_MAXIMUM_ALIGNMENT from settings.h if you plan to use it for SIMD or multi-threading.
 	//   newSize may not be larger than the size of newData in bytes.
 	//   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.
 	//     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.

+ 5 - 5
Source/DFPSR/api/imageAPI.cpp

@@ -39,19 +39,19 @@ using namespace dsr;
 
 
 // Constructors
 // Constructors
 AlignedImageU8 dsr::image_create_U8(int32_t width, int32_t height) {
 AlignedImageU8 dsr::image_create_U8(int32_t width, int32_t height) {
-	return AlignedImageU8(std::make_shared<ImageU8Impl>(width, height, DSR_DEFAULT_ALIGNMENT));
+	return AlignedImageU8(std::make_shared<ImageU8Impl>(width, height));
 }
 }
 AlignedImageU16 dsr::image_create_U16(int32_t width, int32_t height) {
 AlignedImageU16 dsr::image_create_U16(int32_t width, int32_t height) {
-	return AlignedImageU16(std::make_shared<ImageU16Impl>(width, height, DSR_DEFAULT_ALIGNMENT));
+	return AlignedImageU16(std::make_shared<ImageU16Impl>(width, height));
 }
 }
 AlignedImageF32 dsr::image_create_F32(int32_t width, int32_t height) {
 AlignedImageF32 dsr::image_create_F32(int32_t width, int32_t height) {
-	return AlignedImageF32(std::make_shared<ImageF32Impl>(width, height, DSR_FLOAT_ALIGNMENT));
+	return AlignedImageF32(std::make_shared<ImageF32Impl>(width, height));
 }
 }
 OrderedImageRgbaU8 dsr::image_create_RgbaU8(int32_t width, int32_t height) {
 OrderedImageRgbaU8 dsr::image_create_RgbaU8(int32_t width, int32_t height) {
-	return OrderedImageRgbaU8(std::make_shared<ImageRgbaU8Impl>(width, height, DSR_DEFAULT_ALIGNMENT));
+	return OrderedImageRgbaU8(std::make_shared<ImageRgbaU8Impl>(width, height));
 }
 }
 AlignedImageRgbaU8 dsr::image_create_RgbaU8_native(int32_t width, int32_t height, PackOrderIndex packOrderIndex) {
 AlignedImageRgbaU8 dsr::image_create_RgbaU8_native(int32_t width, int32_t height, PackOrderIndex packOrderIndex) {
-	return AlignedImageRgbaU8(std::make_shared<ImageRgbaU8Impl>(width, height, packOrderIndex, DSR_DEFAULT_ALIGNMENT));
+	return AlignedImageRgbaU8(std::make_shared<ImageRgbaU8Impl>(width, height, packOrderIndex));
 }
 }
 
 
 // Loading from data pointer
 // Loading from data pointer

+ 3 - 1
Source/DFPSR/base/endian.h

@@ -21,6 +21,9 @@
 //    3. This notice may not be removed or altered from any source
 //    3. This notice may not be removed or altered from any source
 //    distribution.
 //    distribution.
 
 
+// Get settings from here.
+#include "../settings.h"
+
 // Endianness abstraction layer for manipulating byte arrays within unsigned integers
 // Endianness abstraction layer for manipulating byte arrays within unsigned integers
 //   ENDIAN_POS_ADDR
 //   ENDIAN_POS_ADDR
 //     Bit-shift in the positive direction of addresses
 //     Bit-shift in the positive direction of addresses
@@ -37,7 +40,6 @@
 #ifndef DFPSR_ENDIAN
 #ifndef DFPSR_ENDIAN
 #define DFPSR_ENDIAN
 #define DFPSR_ENDIAN
 	#include <cstdint>
 	#include <cstdint>
-	// TODO: Detect endianness automatically
 	#ifdef DSR_BIG_ENDIAN
 	#ifdef DSR_BIG_ENDIAN
 		// TODO: Not yet tested on a big-endian machine!
 		// TODO: Not yet tested on a big-endian machine!
 		#define ENDIAN_POS_ADDR(VALUE,OFFSET) ((VALUE) >> (OFFSET))
 		#define ENDIAN_POS_ADDR(VALUE,OFFSET) ((VALUE) >> (OFFSET))

+ 229 - 0
Source/DFPSR/base/heap.cpp

@@ -0,0 +1,229 @@
+// zlib open source license
+//
+// Copyright (c) 2024 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+#include "heap.h"
+#include <mutex>
+#include <thread>
+
+// TODO: Avoid dynamic memory allocation in error messages from failed memory allocation.
+#include "../api/stringAPI.h"
+
+// Get settings from here.
+#include "../settings.h"
+
+namespace dsr {
+	// The framework's maximum memory alignment is either the largest float SIMD vector or the thread safe alignment.
+	static const uintptr_t heapAlignment = DSR_MAXIMUM_ALIGNMENT;
+	static const uintptr_t heapAlignmentAndMask = memory_createAlignmentAndMask(heapAlignment);
+
+	// Calculates the largest power of two allocation size that does not overflow a pointer on the target platform.
+	constexpr int calculateBinCount() {
+		int largestBinIndex = 0;
+		intptr_t p = 0;
+		while (true) {
+			uintptr_t result = ((uintptr_t)1 << p) * heapAlignment;
+			// Once the value overflows in uintptr_t we have found the index that can not be used, which is also the bin count when including index zero.
+			if (result == 0) {
+				return p;
+			}
+			p++;
+		}
+	}
+	static const int MAX_BIN_COUNT = calculateBinCount();
+
+	static int32_t getBinIndex(uintptr_t minimumSize) {
+		for (intptr_t p = 0; p < MAX_BIN_COUNT; p++) {
+			uintptr_t result = ((uintptr_t)1 << p) * heapAlignment;
+			if (result >= minimumSize) {
+				return p;
+			}
+		}
+		// Failed to match any bin.
+		return -1;
+	}
+
+	static const uint16_t heapFlag_recycled = 1 << 0;
+	struct HeapHeader : public AllocationHeader {
+		HeapHeader *nextRecycled = nullptr;
+		//uint64_t useCount;
+		uint16_t flags = 0;
+		uint8_t binIndex;
+		HeapHeader(uintptr_t totalSize, uint8_t binIndex)
+		: AllocationHeader(totalSize, false), binIndex(binIndex) {}
+	};
+	static const uintptr_t heapHeaderPaddedSize = memory_getPaddedSize(sizeof(HeapHeader), heapAlignment);
+
+	inline HeapHeader *headerFromAllocation(uint8_t* allocation) {
+		return (HeapHeader*)(allocation - heapHeaderPaddedSize);
+	}
+
+	inline uint8_t *allocationFromHeader(HeapHeader* header) {
+		return (uint8_t*)header + heapHeaderPaddedSize;
+	}
+
+	uint64_t heap_getAllocationSize(uint8_t* allocation) {
+		return headerFromAllocation(allocation)->totalSize - heapHeaderPaddedSize;
+	}
+
+	// A block of memory where heap data can be allocated.
+	struct HeapMemory {
+		// TODO: Recycle memory using groups of allocations in power of two bytes.
+		HeapMemory *prevHeap = nullptr;
+		uint8_t *top = nullptr; // The start of the arena, where the allocation pointer is when full.
+		uint8_t *allocationPointer = nullptr; // The allocation pointer that moves from bottom to top when filling the arena.
+		uint8_t *bottom = nullptr; // The end of the arena, where the allocation pointer is when empty.
+		HeapMemory(uintptr_t size) {
+			this->top = (uint8_t*)malloc(size);
+			this->bottom = this->top + size;
+			this->allocationPointer = this->bottom;
+		}
+		~HeapMemory() {
+			if (this->top != nullptr) {
+				free(this->top);
+				this->top = nullptr;
+			}
+			this->allocationPointer = nullptr;
+			this->bottom = nullptr;
+		}
+	};
+
+	// The heap can have memory freed after its own destruction by telling the remaining allocations to clean up after themselves.
+	struct HeapPool {
+		std::mutex poolLock;
+		HeapMemory *lastHeap = nullptr;
+		HeapHeader *recyclingBin[MAX_BIN_COUNT] = {};
+		bool terminating = false;
+		HeapPool() {}
+		~HeapPool() {
+			this->poolLock.lock();
+				this->terminating = true;
+				HeapMemory *nextHeap = this->lastHeap;
+				while (nextHeap != nullptr) {
+					HeapMemory *currentHeap = nextHeap;
+					nextHeap = currentHeap->prevHeap;
+					delete currentHeap;
+				}
+			this->poolLock.unlock();
+		}
+	};
+
+	static UnsafeAllocation tryToAllocate(HeapMemory &heap, uintptr_t paddedSize, uintptr_t alignmentAndMask, uint8_t binIndex) {
+		UnsafeAllocation result(nullptr, nullptr);
+			uint8_t *dataPointer = (uint8_t*)(((uintptr_t)(heap.allocationPointer) - paddedSize) & alignmentAndMask);
+			AllocationHeader *headerPointer = (AllocationHeader*)(dataPointer - heapHeaderPaddedSize);
+			if ((uint8_t*)headerPointer >= heap.top) {
+				// There is enough space, so confirm the allocation.
+				result = UnsafeAllocation(dataPointer, headerPointer);
+				// Write data to the header.
+				*headerPointer = HeapHeader((uintptr_t)heap.allocationPointer - (uintptr_t)headerPointer, binIndex);
+				// Reserve the data in the heap by moving the allocation pointer.
+				heap.allocationPointer = (uint8_t*)headerPointer;
+			}
+		return result;
+	}
+
+	static UnsafeAllocation tryToAllocate(HeapPool &pool, uintptr_t paddedSize, uintptr_t alignmentAndMask, uint8_t binIndex) {
+		// Start with the most recent heap, which is most likely to have enough space.
+		HeapMemory *currentHeap = pool.lastHeap;
+		while (currentHeap != nullptr) {
+			UnsafeAllocation result = tryToAllocate(*currentHeap, paddedSize, heapAlignmentAndMask, binIndex);
+			if (result.data != nullptr) {
+				return result;
+			}
+			currentHeap = currentHeap->prevHeap;
+		}
+		// If no free space could be found, allocate a new memory block.
+		uintptr_t allocationSize = 16777216;
+		uintptr_t usefulSize = paddedSize << 4; // TODO: Handle integer overflow.
+		if (usefulSize > allocationSize) allocationSize = usefulSize;
+		// Append to the linked list of memory.
+		HeapMemory *previousHeap = pool.lastHeap;
+		pool.lastHeap = new HeapMemory(allocationSize);
+		pool.lastHeap->prevHeap = previousHeap;
+		// Make one last attempt at allocating the memory.
+		return tryToAllocate(*(pool.lastHeap), paddedSize, heapAlignmentAndMask, binIndex);
+	}
+
+	static HeapPool defaultHeap;
+
+	UnsafeAllocation heap_allocate(uintptr_t minimumSize) {
+		int32_t binIndex = getBinIndex(minimumSize);
+		UnsafeAllocation result(nullptr, nullptr);
+		if (binIndex == -1) {
+			// If the requested allocation is so big that there is no power of two that can contain it without overflowing the address space, then it can not be allocated.
+			throwError(U"Exceeded the maximum size when trying to allocate ", minimumSize, U" bytes in heap_allocate!.\n");
+		} else {
+			uintptr_t paddedSize = ((uintptr_t)1 << binIndex) * heapAlignment;
+			defaultHeap.poolLock.lock();
+			if (!(defaultHeap.terminating)) {
+				// Look for pre-existing allocations in the recycling bins.
+				HeapHeader *binHeader = defaultHeap.recyclingBin[binIndex];
+				if (binHeader != nullptr) {
+					// Make the recycled allocation's tail into the new head.
+					defaultHeap.recyclingBin[binIndex] = binHeader->nextRecycled;
+					// Mark the allocation as not recycled. (assume that it was recycled when found in the bin)
+					binHeader->flags &= ~heapFlag_recycled;
+					result = UnsafeAllocation(allocationFromHeader(binHeader), binHeader);
+				} else {
+					// Look for a heap with enough space for a new allocation.
+					result = tryToAllocate(defaultHeap, paddedSize, heapAlignmentAndMask, binIndex);
+					if (result.data == nullptr) {
+						throwError(U"Failed to allocate ", minimumSize, U" bytes of data with heap_allocate!\n");
+					}
+				}
+			}
+			defaultHeap.poolLock.unlock();
+		}
+		return result;
+	}
+
+	void heap_free(uint8_t *allocation) {
+		defaultHeap.poolLock.lock();
+		if (!(defaultHeap.terminating)) {
+			HeapHeader *header = (HeapHeader*)(allocation - heapHeaderPaddedSize);
+			// Get the recycled allocation's header and its bin index.
+			HeapHeader *newHeader = headerFromAllocation(allocation);
+			if (newHeader->flags & heapFlag_recycled) {
+				throwError(U"A heap allocation was freed twice!\n");
+				//#ifdef SAFE_POINTER_CHECKS
+				// TODO: Print more information when possible.
+				//#endif
+			} else {
+				int binIndex = header->binIndex;
+				if (binIndex >= MAX_BIN_COUNT) {
+					throwError(U"Out of bound recycling bin index in corrupted head of freed allocation!\n");
+				} else {
+					// Make any previous head from the bin into the new tail.
+					HeapHeader *oldHeader = defaultHeap.recyclingBin[binIndex];
+					newHeader->nextRecycled = oldHeader;
+					// Mark the allocation as recycled.
+					newHeader->flags |= heapFlag_recycled;
+					// Store the newly recycled allocation in the bin.
+					defaultHeap.recyclingBin[binIndex] = newHeader;
+					newHeader->nextRecycled = oldHeader;
+				}
+			}
+		}
+		defaultHeap.poolLock.unlock();
+	}
+}

+ 77 - 0
Source/DFPSR/base/heap.h

@@ -0,0 +1,77 @@
+// zlib open source license
+//
+// Copyright (c) 2024 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+#ifndef DFPSR_HEAP
+#define DFPSR_HEAP
+
+#include "SafePointer.h"
+
+namespace dsr {
+	// TODO: Implement reference counting and connect a thread-safe handle type to replace std::shared_ptr.
+	//       This should build on top of a fixed size allocator that pre-computes which recycling bin to use.
+	//       Handles could be able to contain multiple elements with the same reference counter, which is useful for giving buffers value heads in images for faster access.
+	// TODO: Return the allocation size in UnsafeAllocation, to avoid extra calls to heap_getAllocationSize?
+	// TODO: Create a faster and simpler allocator for things that never live past a function call or frame.
+	//       Then you just free the whole thing when done with the allocations.
+	//       Good for small images and fixed size heads, but bad for lists being reallocated many times that should recycle memory instead.
+	//       A temporary allocator can then only allocate one thing at a time or free all allocations.
+	//       A fixed size can guarantee that there is no heap allocation done at runtime.
+	//       A dynamic size can have more memory added when needed.
+	//       In debug mode, each allocation will be properly destructed.
+	//       In release mode, everything is just left as it is.
+	//       The caller allocating memory decides if the memory should be cleared or not.
+	//       A thread local version would not need any locks, but should still align with cache lines for consistency with regular heap allocations.
+	// TODO: Allow reserving heap allocations for a specific thread to prevent accidental sharing.
+	//       This can have a separate memory pool in thread local memory to avoid using a mutex.
+	//       The thread local storage can be used for small allocations, while larger allocations can be placed among the shared memory.
+
+	// Allocate memory in the heap.
+	//   The size argument is the minimum number of bytes to allocate, but the result may give you more than you asked for.
+	// Post-condition: Returns pointers to the payload and header.
+	UnsafeAllocation heap_allocate(uint64_t minimumSize);
+
+	// Pre-condition: The allocation pointer must point to the start of a payload allocated using heap_allocate, no offsets nor other allocators allowed.
+	// Post-condition: Returns the number of available bytes in the allocation.
+	//                 You may not read a single byte outside of it, because it might include padding that ends at uneven addresses.
+	//                 To use more memory than requested, you must round it down to whole elements.
+	//                 If the element's size is a power of two, you can pre-compute a bit mask using memory_createAlignmentAndMask for rounding down.
+	uint64_t heap_getAllocationSize(uint8_t* allocation);
+
+	// Only a pointer is needed, so that it can be sent as a function pointer to X11.
+	// TODO: Use the allocation head's alignment as the minimum alignment by combining the masks in compile time.
+	//       Then it is possible to place the padded allocation header for heap memory at a fixed offset from the allocation start, so that the head can be accessed.
+	//       No extra offsets are allowed on the pointer used to free the memory.
+	// TODO: Have a global variable containing the default memory pool.
+	//       When it is destructed, all allocations that are empty will be freed and a termination flag will be enabled so that any more allocations being freed after it will free the memory themselves.
+	void heap_free(uint8_t *allocation);
+
+	/*
+	// TODO: Apply additional safety checks when freeing the allocation using SafePointer, making sure that the correct allocation is freed.
+	template <typename T>
+	void heap_free(SafePointer<T> allocation) {
+		
+	}
+	*/
+}
+
+#endif

+ 7 - 4
Source/DFPSR/base/memory.h

@@ -59,13 +59,16 @@ namespace dsr {
 		#endif
 		#endif
 	};
 	};
 
 
+	// Post-condition: Returns size rounded up by alignment.
+	constexpr uint64_t memory_getPaddedSize(uint64_t size, uint64_t alignment) {
+		// Round up with unsigned integers.
+		return size + (alignment - 1) - ((size - 1) % alignment);
+	}
+
 	// Post-condition: Returns the size of T rounded up by T's own alignment, which becomes the stride between elements in a memory aligned array.
 	// Post-condition: Returns the size of T rounded up by T's own alignment, which becomes the stride between elements in a memory aligned array.
 	template <typename T>
 	template <typename T>
 	constexpr uint64_t memory_getPaddedSize() {
 	constexpr uint64_t memory_getPaddedSize() {
-		uint64_t size = (uint64_t)sizeof(T);
-		uint64_t alignment = (uint64_t)alignof(T);
-		// Round up with unsigned integers.
-		return size + (alignment - 1) - ((size - 1) % alignment);
+		return memory_getPaddedSize((uint64_t)sizeof(T), (uint64_t)alignof(T));
 	}
 	}
 
 
 	// Create a mask for aligning memory in descending address space.
 	// Create a mask for aligning memory in descending address space.

+ 2 - 61
Source/DFPSR/base/simd.h

@@ -88,62 +88,8 @@
 	#include "../math/IVector.h"
 	#include "../math/IVector.h"
 	#include "../math/UVector.h"
 	#include "../math/UVector.h"
 
 
-	// Determine which SIMD extensions to use when compiling this executable
-	// Use the standard compiler flags for enabling SIMD extensions and having them as a minimum system requirement for the specific build.
-	#if defined(__SSE2__)
-		#define USE_SSE2 // Comment out this line to test without SSE2
-		#ifdef USE_SSE2
-			#ifdef __SSSE3__
-				#define USE_SSSE3 // Comment out this line to test without SSSE3
-			#endif
-			#ifdef __AVX__
-				#define USE_AVX // Comment out this line to test without AVX
-				#ifdef USE_AVX
-					#ifdef __AVX2__
-						#define USE_AVX2 // Comment out this line to test without AVX2
-					#endif
-				#endif
-			#endif
-		#endif
-	#elif defined(__ARM_NEON)
-		#define USE_NEON // Comment out this line to test without NEON
-	#endif
-
-	// Enable the EMULATE_X_256BIT_SIMD macro to force use of 256-bit vectors even when there is no hardware instructions supporting it.
-	//   F32xX will then be an alias for F32x8, laneCountX_32Bit will be 8 for iterating your algorithms in larger steps, buffers and image rows will padd and align for 256 bits, et cetera...
-	//   This will be slower if only compiling with 128-bit SIMD instructions enabled, but can show if the algorithm is using variable lane count correctly before profiling on a processor that has the extension.
-	//   Useful for testing algorithms when the computer used for programming does not have the hardware instructions.
-	//   To get real 256-bit SIMD on an Intel processor with AVX2 hardware instructions, enable the AVX2 compiler flag for the library and your project (which is -mavx2 in GCC).
-	//#define EMULATE_256BIT_X_SIMD
-
-	// Enable the EMULATE_F_256BIT_SIMD macro to force use of 256-bit float vectors even when there is no hardware instructions supporting it.
-	//   F32xF will then be an alias for F32x8, laneCountF will be 8 for iterating your float algorithms in larger steps.
-	//   Buffers are however default aligned based on X, so using the F vector length require aligning your buffers using DSR_FLOAT_ALIGNMENT instead of defaulting to DSR_DEFAULT_ALIGNMENT.
-	//   Useful for testing algorithms when the computer used for programming does not have the hardware instructions.
-	//   To get real 256-bit float SIMD on an Intel processor with AVX hardware instructions, enable the AVX compiler flag for the library and your project (which is -mavx in GCC).
-	//#define EMULATE_256BIT_F_SIMD
-
-	// A platform independent summary of which features are enabled.
-	#ifdef USE_SSE2
-		// We have hardware support for 128-bit SIMD, which is enough to make memory bandwidth the bottleneck for light computation.
-		#define USE_BASIC_SIMD
-		#ifdef USE_AVX
-			// We have hardware support for 256-bit float SIMD, so that we get a performance boost from using F32x8 or its alias F32xF instead of F32x4
-			#define USE_256BIT_F_SIMD
-			#ifdef USE_AVX2
-				// We also have hardware support for the other 256-bit SIMD types, pushing the size of an X vector and default alignment to 256 bits.
-				//   F32xX will now refer to F32x8
-				//   I32xX will now refer to I32x8
-				//   U32xX will now refer to U32x8
-				//   U16xX will now refer to U16x16
-				//   U8xX will now refer to U8x32
-				#define USE_256BIT_X_SIMD
-			#endif
-		#endif
-	#endif
-	#ifdef USE_NEON
-		#define USE_BASIC_SIMD
-	#endif
+	// Get settings from here.
+	#include "../settings.h"
 
 
 	// Alignment in bytes
 	// Alignment in bytes
 	#define ALIGN_BYTES(SIZE)  __attribute__((aligned(SIZE)))
 	#define ALIGN_BYTES(SIZE)  __attribute__((aligned(SIZE)))
@@ -3544,11 +3490,6 @@
 	// The F vector using the longest SIMD length that is efficient to use when only processing float values, even if no integer types are available in the same size.
 	// The F vector using the longest SIMD length that is efficient to use when only processing float values, even if no integer types are available in the same size.
 	//   Used when you know that your algorithm is only going to work with float types and you need the extra performance.
 	//   Used when you know that your algorithm is only going to work with float types and you need the extra performance.
 	//     Some processors have AVX but not AVX2, meaning that it has 256-bit SIMD for floats, but only 128-bit SIMD for integers.
 	//     Some processors have AVX but not AVX2, meaning that it has 256-bit SIMD for floats, but only 128-bit SIMD for integers.
-	//   To use the F vector size correctly, only use it with buffers allocated with DSR_FLOAT_ALIGNMENT or a larger alignment.
-	//     Images are allocated using the X vector size's default alignment from DSR_DEFAULT_ALIGNMENT, because images are impossible to use without integer types.
-	//   DSR_FLOAT_ALIGNMENT
-	//     The number of bytes memory should be aligned with manually when creating buffers for processing with the F vector.
-	//     If this sounds too advanced, you can just stick with the X vectors and not release builds with partial support of a SIMD length.
 	//   F32xF
 	//   F32xF
 	//     The longest available SIMD vector for storing 32-bit float values. Iterating laneCountF_32Bit floats at a time.
 	//     The longest available SIMD vector for storing 32-bit float values. Iterating laneCountF_32Bit floats at a time.
 	#if defined USE_256BIT_F_SIMD || defined EMULATE_256BIT_F_SIMD
 	#if defined USE_256BIT_F_SIMD || defined EMULATE_256BIT_F_SIMD

+ 0 - 36
Source/DFPSR/image/Image.cpp

@@ -1,36 +0,0 @@
-// zlib open source license
-//
-// Copyright (c) 2017 to 2019 David Forsgren Piuva
-// 
-// This software is provided 'as-is', without any express or implied
-// warranty. In no event will the authors be held liable for any damages
-// arising from the use of this software.
-// 
-// Permission is granted to anyone to use this software for any purpose,
-// including commercial applications, and to alter it and redistribute it
-// freely, subject to the following restrictions:
-// 
-//    1. The origin of this software must not be misrepresented; you must not
-//    claim that you wrote the original software. If you use this software
-//    in a product, an acknowledgment in the product documentation would be
-//    appreciated but is not required.
-// 
-//    2. Altered source versions must be plainly marked as such, and must not be
-//    misrepresented as being the original software.
-// 
-//    3. This notice may not be removed or altered from any source
-//    distribution.
-
-#include "Image.h"
-
-using namespace dsr;
-
-ImageImpl::ImageImpl(int32_t width, int32_t height, int32_t stride, int32_t pixelSize, Buffer buffer, intptr_t startOffset) :
-  width(width), height(height), stride(stride), pixelSize(pixelSize), buffer(buffer), startOffset(startOffset), isSubImage(true) {
-	this->validate();
-}
-
-ImageImpl::ImageImpl(int32_t width, int32_t height, int32_t stride, int32_t pixelSize, int alignment) :
-  width(width), height(height), stride(stride), pixelSize(pixelSize), buffer(buffer_create(stride * height, alignment)), startOffset(0), isSubImage(false) {
-	this->validate();
-}

+ 4 - 10
Source/DFPSR/image/Image.h

@@ -42,19 +42,13 @@ public:
 	Buffer buffer; // Content
 	Buffer buffer; // Content
 	intptr_t startOffset; // Byte offset of the first pixel
 	intptr_t startOffset; // Byte offset of the first pixel
 	bool isSubImage = false;
 	bool isSubImage = false;
-private:
-	void validate() {
-		// Preconditions:
-		assert(this->width > 0);
-		assert(this->height > 0);
-		assert(this->stride >= this->width * this->pixelSize);
-		assert(this->pixelSize > 0);
-	}
 public:
 public:
 	// Sub-images
 	// Sub-images
-	ImageImpl(int32_t width, int32_t height, int32_t stride, int32_t pixelSize, Buffer buffer, intptr_t startOffset);
+	ImageImpl(int32_t width, int32_t height, int32_t stride, int32_t pixelSize, Buffer buffer, intptr_t startOffset) :
+	  width(width), height(height), stride(stride), pixelSize(pixelSize), buffer(buffer), startOffset(startOffset), isSubImage(true) {}
 	// New images
 	// New images
-	ImageImpl(int32_t width, int32_t height, int32_t stride, int32_t pixelSize, int alignment);
+	ImageImpl(int32_t width, int32_t height, int32_t stride, int32_t pixelSize) :
+	  width(width), height(height), stride(stride), pixelSize(pixelSize), buffer(buffer_create(stride * height)), startOffset(0), isSubImage(false) {}
 };
 };
 
 
 #define IMAGE_DECLARATION(IMAGE_TYPE,CHANNELS,COLOR_TYPE,ELEMENT_TYPE) \
 #define IMAGE_DECLARATION(IMAGE_TYPE,CHANNELS,COLOR_TYPE,ELEMENT_TYPE) \

+ 2 - 2
Source/DFPSR/image/ImageF32.cpp

@@ -32,8 +32,8 @@ ImageF32Impl::ImageF32Impl(int32_t newWidth, int32_t newHeight, int32_t newStrid
 	assert(buffer_getSize(buffer) - startOffset >= imageInternal::getUsedBytes(this));
 	assert(buffer_getSize(buffer) - startOffset >= imageInternal::getUsedBytes(this));
 }
 }
 
 
-ImageF32Impl::ImageF32Impl(int32_t newWidth, int32_t newHeight, int32_t alignment) :
-  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(float), alignment), sizeof(float), alignment) {
+ImageF32Impl::ImageF32Impl(int32_t newWidth, int32_t newHeight) :
+  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(float), DSR_MAXIMUM_ALIGNMENT), sizeof(float)) {
 }
 }
 
 
 IMAGE_DEFINITION(ImageF32Impl, 1, float, float);
 IMAGE_DEFINITION(ImageF32Impl, 1, float, float);

+ 2 - 2
Source/DFPSR/image/ImageF32.h

@@ -31,11 +31,11 @@ namespace dsr {
 class ImageF32Impl : public ImageImpl {
 class ImageF32Impl : public ImageImpl {
 public:
 public:
 	static const int32_t channelCount = 1;
 	static const int32_t channelCount = 1;
-	static const int32_t pixelSize = 4;
+	//static const int32_t pixelSize = 4;
 	// Inherit constructors
 	// Inherit constructors
 	using ImageImpl::ImageImpl;
 	using ImageImpl::ImageImpl;
 	ImageF32Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset);
 	ImageF32Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset);
-	ImageF32Impl(int32_t newWidth, int32_t newHeight, int32_t alignment);
+	ImageF32Impl(int32_t newWidth, int32_t newHeight);
 	// Macro defined functions
 	// Macro defined functions
 	IMAGE_DECLARATION(ImageF32Impl, 1, float, float);
 	IMAGE_DECLARATION(ImageF32Impl, 1, float, float);
 };
 };

+ 7 - 7
Source/DFPSR/image/ImageRgbaU8.cpp

@@ -40,14 +40,14 @@ ImageRgbaU8Impl::ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, int32_t ne
 	this->initializeRgbaImage();
 	this->initializeRgbaImage();
 }
 }
 
 
-ImageRgbaU8Impl::ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, int32_t alignment) :
-  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(Color4xU8), alignment), sizeof(Color4xU8), alignment) {
+ImageRgbaU8Impl::ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight) :
+  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(Color4xU8), DSR_MAXIMUM_ALIGNMENT), sizeof(Color4xU8)) {
 	this->initializeRgbaImage();
 	this->initializeRgbaImage();
 }
 }
 
 
 // Native canvas constructor
 // Native canvas constructor
-ImageRgbaU8Impl::ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, PackOrderIndex packOrderIndex, int32_t alignment) :
-  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(Color4xU8), 16), sizeof(Color4xU8), alignment) {
+ImageRgbaU8Impl::ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, PackOrderIndex packOrderIndex) :
+  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(Color4xU8), DSR_MAXIMUM_ALIGNMENT), sizeof(Color4xU8)) {
 	this->packOrder = PackOrder::getPackOrder(packOrderIndex);
 	this->packOrder = PackOrder::getPackOrder(packOrderIndex);
 	this->initializeRgbaImage();
 	this->initializeRgbaImage();
 }
 }
@@ -66,7 +66,7 @@ ImageRgbaU8Impl ImageRgbaU8Impl::getWithoutPadding() const {
 		return *this;
 		return *this;
 	} else {
 	} else {
 		// Copy each row without padding
 		// Copy each row without padding
-		ImageRgbaU8Impl result = ImageRgbaU8Impl(this->width, this->height, this->packOrder.packOrderIndex, DSR_DEFAULT_ALIGNMENT);
+		ImageRgbaU8Impl result = ImageRgbaU8Impl(this->width, this->height, this->packOrder.packOrderIndex);
 		const SafePointer<uint8_t> sourceRow = imageInternal::getSafeData<uint8_t>(*this);
 		const SafePointer<uint8_t> sourceRow = imageInternal::getSafeData<uint8_t>(*this);
 		int32_t sourceStride = this->stride;
 		int32_t sourceStride = this->stride;
 		SafePointer<uint8_t> targetRow = imageInternal::getSafeData<uint8_t>(result);
 		SafePointer<uint8_t> targetRow = imageInternal::getSafeData<uint8_t>(result);
@@ -102,7 +102,7 @@ ImageU8Impl ImageRgbaU8Impl::getChannel(int32_t channelIndex) const {
 	// Safety for release mode
 	// Safety for release mode
 	if (channelIndex < 0) { channelIndex = 0; }
 	if (channelIndex < 0) { channelIndex = 0; }
 	if (channelIndex > channelCount) { channelIndex = channelCount; }
 	if (channelIndex > channelCount) { channelIndex = channelCount; }
-	ImageU8Impl result(this->width, this->height, DSR_DEFAULT_ALIGNMENT);
+	ImageU8Impl result(this->width, this->height);
 	extractChannel(imageInternal::getSafeData<uint8_t>(result), result.stride, imageInternal::getSafeData<uint8_t>(*this), this->stride, channelCount, channelIndex, this->width, this->height);
 	extractChannel(imageInternal::getSafeData<uint8_t>(result), result.stride, imageInternal::getSafeData<uint8_t>(*this), this->stride, channelCount, channelIndex, this->width, this->height);
 	return result;
 	return result;
 }
 }
@@ -282,7 +282,7 @@ void ImageRgbaU8Impl::makeIntoTexture() {
 		int newWidth = roundSize(this->width);
 		int newWidth = roundSize(this->width);
 		int newHeight = roundSize(this->height);
 		int newHeight = roundSize(this->height);
 		// Create a new image with the correct dimensions.
 		// Create a new image with the correct dimensions.
-		ImageRgbaU8Impl result = ImageRgbaU8Impl(newWidth, newHeight, DSR_DEFAULT_ALIGNMENT);
+		ImageRgbaU8Impl result = ImageRgbaU8Impl(newWidth, newHeight);
 		// Resize the image content with bi-linear interpolation.
 		// Resize the image content with bi-linear interpolation.
 		imageImpl_resizeToTarget(result, *this, true);
 		imageImpl_resizeToTarget(result, *this, true);
 		// Take over the new image's content.
 		// Take over the new image's content.

+ 3 - 3
Source/DFPSR/image/ImageRgbaU8.h

@@ -93,15 +93,15 @@ struct TextureRgba {
 class ImageRgbaU8Impl : public ImageImpl {
 class ImageRgbaU8Impl : public ImageImpl {
 public:
 public:
 	static const int32_t channelCount = 4;
 	static const int32_t channelCount = 4;
-	static const int32_t pixelSize = channelCount;
+	//static const int32_t pixelSize = channelCount;
 	PackOrder packOrder;
 	PackOrder packOrder;
 	// Macro defined functions
 	// Macro defined functions
 	IMAGE_DECLARATION(ImageRgbaU8Impl, 4, Color4xU8, uint8_t);
 	IMAGE_DECLARATION(ImageRgbaU8Impl, 4, Color4xU8, uint8_t);
 	// Constructors
 	// Constructors
 	ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset, const PackOrder &packOrder);
 	ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset, const PackOrder &packOrder);
-	ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, int32_t alignment);
+	ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight);
 	// Native canvas constructor
 	// Native canvas constructor
-	ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, PackOrderIndex packOrderIndex, int32_t alignment);
+	ImageRgbaU8Impl(int32_t newWidth, int32_t newHeight, PackOrderIndex packOrderIndex);
 	// The texture view for fast reading
 	// The texture view for fast reading
 	TextureRgba texture;
 	TextureRgba texture;
 	// Points to level 0 from all bins to allow rendering
 	// Points to level 0 from all bins to allow rendering

+ 2 - 2
Source/DFPSR/image/ImageU16.cpp

@@ -32,8 +32,8 @@ ImageU16Impl::ImageU16Impl(int32_t newWidth, int32_t newHeight, int32_t newStrid
 	assert(buffer_getSize(buffer) - startOffset >= imageInternal::getUsedBytes(this));
 	assert(buffer_getSize(buffer) - startOffset >= imageInternal::getUsedBytes(this));
 }
 }
 
 
-ImageU16Impl::ImageU16Impl(int32_t newWidth, int32_t newHeight, int32_t alignment) :
-  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(uint16_t), alignment), sizeof(uint16_t), alignment) {
+ImageU16Impl::ImageU16Impl(int32_t newWidth, int32_t newHeight) :
+  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(uint16_t), DSR_MAXIMUM_ALIGNMENT), sizeof(uint16_t)) {
 }
 }
 
 
 IMAGE_DEFINITION(ImageU16Impl, 1, uint16_t, uint16_t);
 IMAGE_DEFINITION(ImageU16Impl, 1, uint16_t, uint16_t);

+ 2 - 2
Source/DFPSR/image/ImageU16.h

@@ -32,11 +32,11 @@ namespace dsr {
 class ImageU16Impl : public ImageImpl {
 class ImageU16Impl : public ImageImpl {
 public:
 public:
 	static const int32_t channelCount = 1;
 	static const int32_t channelCount = 1;
-	static const int32_t pixelSize = 2;
+	//static const int32_t pixelSize = 2;
 	// Inherit constructors
 	// Inherit constructors
 	using ImageImpl::ImageImpl;
 	using ImageImpl::ImageImpl;
 	ImageU16Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset);
 	ImageU16Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset);
-	ImageU16Impl(int32_t newWidth, int32_t newHeight, int32_t alignment);
+	ImageU16Impl(int32_t newWidth, int32_t newHeight);
 	// Macro defined functions
 	// Macro defined functions
 	IMAGE_DECLARATION(ImageU16Impl, 1, uint16_t, uint16_t);
 	IMAGE_DECLARATION(ImageU16Impl, 1, uint16_t, uint16_t);
 };
 };

+ 2 - 2
Source/DFPSR/image/ImageU8.cpp

@@ -32,8 +32,8 @@ ImageU8Impl::ImageU8Impl(int32_t newWidth, int32_t newHeight, int32_t newStride,
 	assert(buffer_getSize(buffer) - startOffset >= imageInternal::getUsedBytes(this));
 	assert(buffer_getSize(buffer) - startOffset >= imageInternal::getUsedBytes(this));
 }
 }
 
 
-ImageU8Impl::ImageU8Impl(int32_t newWidth, int32_t newHeight, int32_t alignment) :
-  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(uint8_t), alignment), sizeof(uint8_t), alignment) {
+ImageU8Impl::ImageU8Impl(int32_t newWidth, int32_t newHeight) :
+  ImageImpl(newWidth, newHeight, roundUp(newWidth * sizeof(uint8_t), DSR_MAXIMUM_ALIGNMENT), sizeof(uint8_t)) {
 }
 }
 
 
 IMAGE_DEFINITION(ImageU8Impl, 1, uint8_t, uint8_t);
 IMAGE_DEFINITION(ImageU8Impl, 1, uint8_t, uint8_t);

+ 2 - 2
Source/DFPSR/image/ImageU8.h

@@ -31,12 +31,12 @@ namespace dsr {
 class ImageU8Impl : public ImageImpl {
 class ImageU8Impl : public ImageImpl {
 public:
 public:
 	static const int32_t channelCount = 1;
 	static const int32_t channelCount = 1;
-	static const int32_t pixelSize = channelCount;
+	//static const int32_t pixelSize = channelCount;
 	// Inherit constructors
 	// Inherit constructors
 	using ImageImpl::ImageImpl;
 	using ImageImpl::ImageImpl;
 	// Constructors
 	// Constructors
 	ImageU8Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset);
 	ImageU8Impl(int32_t newWidth, int32_t newHeight, int32_t newStride, Buffer buffer, intptr_t startOffset);
-	ImageU8Impl(int32_t newWidth, int32_t newHeight, int32_t alignment);
+	ImageU8Impl(int32_t newWidth, int32_t newHeight);
 	// Macro defined functions
 	// Macro defined functions
 	IMAGE_DECLARATION(ImageU8Impl, 1, uint8_t, uint8_t);
 	IMAGE_DECLARATION(ImageU8Impl, 1, uint8_t, uint8_t);
 };
 };

+ 2 - 2
Source/DFPSR/image/draw.cpp

@@ -1100,10 +1100,10 @@ static void resize_aux(ImageU8Impl& target, const ImageU8Impl& source, bool inte
 
 
 // Creating an image to replacedImage with the same pack order as originalImage when applicable to the image format.
 // Creating an image to replacedImage with the same pack order as originalImage when applicable to the image format.
 static ImageRgbaU8Impl createWithSamePackOrder(const ImageRgbaU8Impl& originalImage, int32_t width, int32_t height) {
 static ImageRgbaU8Impl createWithSamePackOrder(const ImageRgbaU8Impl& originalImage, int32_t width, int32_t height) {
-	return ImageRgbaU8Impl(width, height, originalImage.packOrder.packOrderIndex, DSR_DEFAULT_ALIGNMENT);
+	return ImageRgbaU8Impl(width, height, originalImage.packOrder.packOrderIndex);
 }
 }
 static ImageU8Impl createWithSamePackOrder(const ImageU8Impl& originalImage, int32_t width, int32_t height) {
 static ImageU8Impl createWithSamePackOrder(const ImageU8Impl& originalImage, int32_t width, int32_t height) {
-	return ImageU8Impl(width, height, DSR_DEFAULT_ALIGNMENT);
+	return ImageU8Impl(width, height);
 }
 }
 
 
 template <typename IMAGE_TYPE>
 template <typename IMAGE_TYPE>

+ 121 - 0
Source/DFPSR/settings.h

@@ -0,0 +1,121 @@
+// zlib open source license
+//
+// Copyright (c) 2024 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+#ifndef DFPSR_SETTINGS
+#define DFPSR_SETTINGS
+	// Determine which SIMD extensions to use in base/simd.h.
+	// Use the standard compiler flags for enabling SIMD extensions.
+	//   If your compiler uses a different macro name to indicate the presence of a SIMD extension, you can add them here to enable the USE_* macros.
+	// You can compile different versions of the program for different capabilities.
+	//   SSE2 and NEON are usually enabled by default on instruction sets where they are not optional, which is good if you just want one release that is fast enough.
+	//   AVX with 256-bit float SIMD is useful for sound and physics that can be computed without integers.
+	//   AVX2 with full 256-bit SIMD is useful for 3D graphics and heavy 2D filters where memory bandwidth is not the bottleneck.
+	#if defined(__SSE2__)
+		#define USE_SSE2 // Comment out this line to test without SSE2
+		#ifdef USE_SSE2
+			#ifdef __SSSE3__
+				#define USE_SSSE3 // Comment out this line to test without SSSE3
+			#endif
+			#ifdef __AVX__
+				#define USE_AVX // Comment out this line to test without AVX
+				#ifdef USE_AVX
+					#ifdef __AVX2__
+						#define USE_AVX2 // Comment out this line to test without AVX2
+					#endif
+				#endif
+			#endif
+		#endif
+	#elif defined(__ARM_NEON)
+		#define USE_NEON // Comment out this line to test without NEON
+	#endif
+
+	// Enable the EMULATE_X_256BIT_SIMD macro to force use of 256-bit vectors even when there is no hardware instructions supporting it.
+	//   To get real 256-bit SIMD on an Intel processor with AVX2 hardware instructions, enable the AVX2 compiler flag for the library and your project (which is -mavx2 in GCC).
+	//#define EMULATE_256BIT_X_SIMD
+
+	// Enable the EMULATE_F_256BIT_SIMD macro to force use of 256-bit float vectors even when there is no hardware instructions supporting it.
+	//   To get real 256-bit float SIMD on an Intel processor with AVX hardware instructions, enable the AVX compiler flag for the library and your project (which is -mavx in GCC).
+	//#define EMULATE_256BIT_F_SIMD
+
+	// A platform independent summary of which features are enabled.
+	#ifdef USE_SSE2
+		// We have hardware support for 128-bit SIMD, which is enough to make memory bandwidth the bottleneck for light computation.
+		#define USE_BASIC_SIMD
+		#ifdef USE_AVX
+			// We have hardware support for 256-bit float SIMD, so that we get a performance boost from using F32x8 or its alias F32xF instead of F32x4
+			#define USE_256BIT_F_SIMD
+			#ifdef USE_AVX2
+				// We also have hardware support for the other 256-bit SIMD types, pushing the size of an X vector and default alignment to 256 bits.
+				//   F32xX will now refer to F32x8
+				//   I32xX will now refer to I32x8
+				//   U32xX will now refer to U32x8
+				//   U16xX will now refer to U16x16
+				//   U8xX will now refer to U8x32
+				#define USE_256BIT_X_SIMD
+			#endif
+		#endif
+	#endif
+	#ifdef USE_NEON
+		#define USE_BASIC_SIMD
+	#endif
+
+	// Get the largest size of SIMD vectors for memory alignment.
+	#ifdef USE_256BIT_F_SIMD
+		#define DSR_LARGEST_VECTOR_SIZE 32
+	#else
+		#define DSR_LARGEST_VECTOR_SIZE 16
+	#endif
+
+	// Endianness
+	//   Compile with C++ 2020 or later to detect endianness automatically.
+	//   Or define the DSR_BIG_ENDIAN macro externally when building for big-endian targets.
+	#if (__cplusplus >= 202002L)
+		#include <bit>
+		#if (std::endian::native == std::endian::big)
+			// We detected a big endian target.
+			#define DSR_BIG_ENDIAN
+		#elif (std::endian::native == std::endian::little)
+			// We detected a little endian target.
+			// TODO: Insert a compiler message here just to test that the check works with C++ 2020.
+		#else
+			// We detected a mixed endian target!
+			#error "The DFPSR framework does not work on mixed-endian systems, because it relies on a linear relation between memory addresses and bit shifting!\n"
+		#endif
+	#endif
+
+	// TODO: Create a function that checks CPUID when available on the platform, to give warnings if the computer does not meet the system requirements of the specific build.
+	// Size of cache lines used to protect different threads from accidental sharing cache line across independent memory allocations.
+	//   Must be a power of two, and no less than the largest cache line among all CPU cores that might run the program.
+	//   Can be assigned using the DSR_THREAD_SAFE_ALIGNMENT macro externally or changed here.
+	#ifndef DSR_THREAD_SAFE_ALIGNMENT
+		#define DSR_THREAD_SAFE_ALIGNMENT 64
+	#endif
+
+	// When allocating memory for being reused many times for different purposes, we need to know the maximum alignment that will be required ahead of time.
+	//   Here we define it as the maximum of the largest SIMD vector and the thread safe alignment.
+	#if (DSR_LARGEST_VECTOR_SIZE > DSR_THREAD_SAFE_ALIGNMENT)
+		#define DSR_MAXIMUM_ALIGNMENT DSR_LARGEST_VECTOR_SIZE
+	#else
+		#define DSR_MAXIMUM_ALIGNMENT DSR_THREAD_SAFE_ALIGNMENT
+	#endif
+#endif

+ 1 - 1
Source/tools/builder/buildProject.bat

@@ -27,7 +27,7 @@ set BUILDER_EXECUTABLE=%BUILDER_FOLDER%builder.exe
 echo BUILDER_EXECUTABLE = %BUILDER_EXECUTABLE%
 echo BUILDER_EXECUTABLE = %BUILDER_EXECUTABLE%
 set DFPSR_LIBRARY=%BUILDER_FOLDER%..\..\DFPSR
 set DFPSR_LIBRARY=%BUILDER_FOLDER%..\..\DFPSR
 echo DFPSR_LIBRARY = %DFPSR_LIBRARY%
 echo DFPSR_LIBRARY = %DFPSR_LIBRARY%
-set BUILDER_SOURCE=%BUILDER_FOLDER%\code\main.cpp %BUILDER_FOLDER%\code\Machine.cpp %BUILDER_FOLDER%\code\generator.cpp %BUILDER_FOLDER%\code\analyzer.cpp %BUILDER_FOLDER%\code\expression.cpp %DFPSR_LIBRARY%\collection\collections.cpp %DFPSR_LIBRARY%\api\fileAPI.cpp %DFPSR_LIBRARY%\api\bufferAPI.cpp %DFPSR_LIBRARY%\api\stringAPI.cpp %DFPSR_LIBRARY%\api\timeAPI.cpp %DFPSR_LIBRARY%\base\SafePointer.cpp %DFPSR_LIBRARY%\base\virtualStack.cpp
+set BUILDER_SOURCE=%BUILDER_FOLDER%\code\main.cpp %BUILDER_FOLDER%\code\Machine.cpp %BUILDER_FOLDER%\code\generator.cpp %BUILDER_FOLDER%\code\analyzer.cpp %BUILDER_FOLDER%\code\expression.cpp %DFPSR_LIBRARY%\collection\collections.cpp %DFPSR_LIBRARY%\api\fileAPI.cpp %DFPSR_LIBRARY%\api\bufferAPI.cpp %DFPSR_LIBRARY%\api\stringAPI.cpp %DFPSR_LIBRARY%\api\timeAPI.cpp %DFPSR_LIBRARY%\base\SafePointer.cpp %DFPSR_LIBRARY%\base\virtualStack.cpp %DFPSR_LIBRARY%\base\heap.cpp
 echo BUILDER_SOURCE = %BUILDER_SOURCE%
 echo BUILDER_SOURCE = %BUILDER_SOURCE%
 
 
 echo Change CPP_COMPILER_FOLDER and CPP_COMPILER_PATH in %BUILDER_FOLDER%\buildProject.bat if you are not using %CPP_COMPILER_PATH% as your compiler.
 echo Change CPP_COMPILER_FOLDER and CPP_COMPILER_PATH in %BUILDER_FOLDER%\buildProject.bat if you are not using %CPP_COMPILER_PATH% as your compiler.

+ 1 - 1
Source/tools/builder/buildProject.sh

@@ -41,7 +41,7 @@ if [ -e "${BUILDER_EXECUTABLE}" ]; then
 else
 else
 	echo "Building the Builder build system for first time use."
 	echo "Building the Builder build system for first time use."
 	LIBRARY_PATH="$(realpath ${BUILDER_FOLDER}/../../DFPSR)"
 	LIBRARY_PATH="$(realpath ${BUILDER_FOLDER}/../../DFPSR)"
-	SOURCE_CODE="${BUILDER_FOLDER}/code/main.cpp ${BUILDER_FOLDER}/code/Machine.cpp ${BUILDER_FOLDER}/code/generator.cpp ${BUILDER_FOLDER}/code/analyzer.cpp ${BUILDER_FOLDER}/code/expression.cpp ${LIBRARY_PATH}/collection/collections.cpp ${LIBRARY_PATH}/api/fileAPI.cpp ${LIBRARY_PATH}/api/bufferAPI.cpp ${LIBRARY_PATH}/api/stringAPI.cpp ${LIBRARY_PATH}/api/timeAPI.cpp ${LIBRARY_PATH}/base/SafePointer.cpp ${LIBRARY_PATH}/base/virtualStack.cpp"
+	SOURCE_CODE="${BUILDER_FOLDER}/code/main.cpp ${BUILDER_FOLDER}/code/Machine.cpp ${BUILDER_FOLDER}/code/generator.cpp ${BUILDER_FOLDER}/code/analyzer.cpp ${BUILDER_FOLDER}/code/expression.cpp ${LIBRARY_PATH}/collection/collections.cpp ${LIBRARY_PATH}/api/fileAPI.cpp ${LIBRARY_PATH}/api/bufferAPI.cpp ${LIBRARY_PATH}/api/stringAPI.cpp ${LIBRARY_PATH}/api/timeAPI.cpp ${LIBRARY_PATH}/base/SafePointer.cpp ${LIBRARY_PATH}/base/virtualStack.cpp ${LIBRARY_PATH}/base/heap.cpp"
 	"${CPP_COMPILER_PATH}" -o "${BUILDER_EXECUTABLE}" ${SOURCE_CODE} -std=c++14 -lstdc++
 	"${CPP_COMPILER_PATH}" -o "${BUILDER_EXECUTABLE}" ${SOURCE_CODE} -std=c++14 -lstdc++
 	if [ $? -eq 0 ]; then
 	if [ $? -eq 0 ]; then
 		echo "Completed building the Builder build system."
 		echo "Completed building the Builder build system."

+ 10 - 0
Source/windowManagers/X11Window.cpp

@@ -9,6 +9,7 @@
 #include "../DFPSR/api/drawAPI.h"
 #include "../DFPSR/api/drawAPI.h"
 #include "../DFPSR/api/timeAPI.h"
 #include "../DFPSR/api/timeAPI.h"
 #include "../DFPSR/gui/BackendWindow.h"
 #include "../DFPSR/gui/BackendWindow.h"
+#include "../DFPSR/base/heap.h"
 
 
 // According to this documentation, XInitThreads doesn't have to be used if a mutex is wrapped around all the calls to XLib.
 // According to this documentation, XInitThreads doesn't have to be used if a mutex is wrapped around all the calls to XLib.
 //   https://tronche.com/gui/x/xlib/display/XInitThreads.html
 //   https://tronche.com/gui/x/xlib/display/XInitThreads.html
@@ -721,6 +722,12 @@ void X11Window::prefetchEvents() {
 	}
 	}
 }
 }
 
 
+int destroyImage(XImage *image) {
+	dsr::heap_free((uint8_t*)image->data);
+	image->data = nullptr;
+	return 1;
+}
+
 // Locked because it overrides
 // Locked because it overrides
 void X11Window::resizeCanvas(int width, int height) {
 void X11Window::resizeCanvas(int width, int height) {
 	windowLock.lock();
 	windowLock.lock();
@@ -745,7 +752,10 @@ void X11Window::resizeCanvas(int width, int height) {
 				);
 				);
 				// When the canvas image buffer is garbage collected, the destructor will call XLib to free the memory
 				// When the canvas image buffer is garbage collected, the destructor will call XLib to free the memory
 				XImage *image = this->canvasX[b];
 				XImage *image = this->canvasX[b];
+				// Tell the buffer to deallocate the XImage instead of just freeing the allocation, because X11 might still use the data.
 				dsr::image_dangerous_replaceDestructor(this->canvas[b], [image](uint8_t *data) { XDestroyImage(image); });
 				dsr::image_dangerous_replaceDestructor(this->canvas[b], [image](uint8_t *data) { XDestroyImage(image); });
+				// And when X11 frees the image, we say how to delete the memory allocation from our heap allocator.
+				image->f.destroy_image = destroyImage;
 			}
 			}
 		}
 		}
 	windowLock.unlock();
 	windowLock.unlock();